module Csound.Typed.GlobalState.Instr where

import Control.Monad
import Data.Map

import Csound.Dynamic
import qualified Csound.Typed.GlobalState.Elements as C

import Csound.Typed.Types.MixSco
import Csound.Typed.Types.Prim
import Csound.Typed.GlobalState.GE
import Csound.Typed.GlobalState.SE
import Csound.Typed.GlobalState.Options
import Csound.Typed.GlobalState.Cache
import Csound.Typed.GlobalState.Opcodes(turnoff2, exitnow, servantUpdateChnAlive, servantUpdateChnRetrig)
import Csound.Typed.GlobalState.Elements(getInstrIds)

data Arity = Arity
    { arityIns      :: Int
    , arityOuts     :: Int }

type InsExp = SE [E]
type EffExp = [E] -> SE [E]
type UnitExp = SE ()

saveInstr :: SE () -> GE InstrId
saveInstr a = onInstr . C.saveInstr =<< execSE a

livenessWatch :: Arity -> SE ()
livenessWatch arity = fromDep_ $ servantUpdateChnAlive (C.chnPargId $ arityIns arity)

retrigWatch :: Arity -> SE ()
retrigWatch arity = fromDep_ $ servantUpdateChnRetrig (C.chnPargId $ arityIns arity)

saveSourceInstrCachedWithLivenessWatch :: Arity -> InsExp -> GE InstrId
saveSourceInstrCachedWithLivenessWatch arity instr = saveInstr $ do
    toOut =<< instr
    livenessWatch arity 
    where toOut = SE . C.sendChn (arityIns arity) (arityOuts arity)

saveSourceInstrCachedWithLivenessWatchAndRetrig :: Arity -> InsExp -> GE InstrId
saveSourceInstrCachedWithLivenessWatchAndRetrig arity instr = saveInstr $ do
    toOut =<< instr
    retrigWatch arity
    livenessWatch arity    
    where toOut = SE . C.sendChn (arityIns arity) (arityOuts arity)

saveSourceInstrCachedWithLivenessWatchAndRetrigAndEvtLoop :: Arity -> InsExp -> UnitExp -> GE (InstrId, InstrId)
saveSourceInstrCachedWithLivenessWatchAndRetrigAndEvtLoop arity instr evtInstr = do 
    instrId <- saveSourceInstrCachedWithLivenessWatchAndRetrig arity instr
    evtInstrId <- saveInstr (evtInstr >> retrigWatch evtInstrArity >> livenessWatch evtInstrArity)
    return (instrId, evtInstrId)
    where 
        evtInstrArity = Arity 0 0
        
saveSourceInstrCached :: Arity -> InsExp -> GE InstrId
saveSourceInstrCached arity instr = saveInstr $ toOut =<< instr
    where toOut = SE . C.sendChn (arityIns arity) (arityOuts arity)

saveSourceInstrCached_ :: UnitExp -> GE InstrId
saveSourceInstrCached_ instr = saveInstr instr

saveSourceInstrCachedWithLivenessWatch_ :: Arity -> UnitExp -> GE InstrId
saveSourceInstrCachedWithLivenessWatch_ arity instr = saveInstr $ 
    instr >> livenessWatch arity

saveEffectInstr :: Arity -> EffExp -> GE InstrId
saveEffectInstr arity eff = saveInstr $ setOuts =<< eff =<< getIns
    where 
        setOuts = SE . C.writeChn (C.chnRefFromParg 5 (arityOuts arity))
        getIns  = SE $ C.readChn  $ C.chnRefFromParg 4 (arityIns  arity)

saveMixInstr :: Int -> CsdEventList M -> GE InstrId
saveMixInstr arity a = do
    setDuration =<< toGE (csdEventListDur a)
    saveInstr $ SE $ C.sendOut arity =<< renderMixSco arity a

saveMixInstr_ :: CsdEventList M -> GE (DepT GE ())
saveMixInstr_ a = do
    setDuration =<< toGE (csdEventListDur a)
    return $ renderMixSco_ a

saveMasterInstr :: Arity -> InsExp -> GE ()
saveMasterInstr arity sigs = do
    gainLevel <- fmap defGain getOptions 
    saveAlwaysOnInstr =<< (saveInstr $ (SE . C.sendOut (arityOuts arity) . C.safeOut gainLevel) =<< sigs)

saveMidiInstr :: C.MidiType -> C.Channel -> Arity -> InsExp -> GE [E]
saveMidiInstr midiType channel arity instr = do
    setDurationToInfinite
    vars <- onGlobals $ sequence $ replicate (arityOuts arity) (C.newClearableGlobalVar Ar 0)
    let expr = (SE . zipWithM_ (appendVarBy (+)) vars) =<< instr
    instrId <- saveInstr expr
    saveMidi $ MidiAssign midiType channel instrId
    return $ fmap readOnlyVar vars 

saveMidiMap :: GE ()
saveMidiMap = do
    m <- fmap midiMap getHistory
    mapM_ (\(C.MidiKey midiType channel, instrExpr) -> saveMidiInstr_ midiType channel (SE instrExpr)) $ toList m

saveMidiInstr_ :: C.MidiType -> C.Channel -> UnitExp -> GE ()
saveMidiInstr_ midiType channel instr = do    
    instrId <- saveInstr instr
    saveMidi $ MidiAssign midiType channel instrId   

saveIns0 :: Int -> [Rate] -> SE [E] -> GE [E]
saveIns0 arity rates as = do
    vars <- onGlobals $ zipWithM C.newPersistentGlobalVar rates (replicate arity 0)
    saveUserInstr0 $ unSE $ (SE . zipWithM_ writeVar vars) =<< as 
    return $ fmap readOnlyVar vars

terminatorInstr :: GE (SE ())
terminatorInstr = do
    ids <- fmap (getInstrIds . instrs) getHistory
    return $ fromDep_ $ (mapM_ turnoff2 $ fmap instrIdE ids) >> exitnow