{-# Language TupleSections #-}
module Csound.Typed.GlobalState.GE(
    GE, Dep, History(..), withOptions, withHistory, getOptions, evalGE, execGE,
    getHistory, putHistory,
    -- * Globals
    onGlobals, bpmVar,
    -- * Midi
    MidiAssign(..), Msg(..), renderMidiAssign, saveMidi, saveToMidiInstr,
    MidiCtrl(..), saveMidiCtrl, renderMidiCtrl,
    -- * Instruments
    saveAlwaysOnInstr, onInstr, saveUserInstr0, getSysExpr,
    -- * Named instruments
    saveNamedInstr,
    -- * Total duration
    TotalDur(..), pureGetTotalDurForF0, getTotalDurForTerminator,
    setDurationForce, setDuration, setDurationToInfinite,
    -- * Notes
    addNote,
    -- * GEN routines
    GenId,
    saveGen, saveTabs, getNextGlobalGenId,
    saveWriteGen, saveWriteTab,
    -- * Sf2
    saveSf, sfTable,
    -- * Band-limited waves
    saveBandLimitedWave,
    -- * Strings
    saveStr,
    -- * Cache
    GetCache, SetCache, withCache,
    -- * Guis
    newGuiHandle, saveGuiRoot, saveDefKeybdPanel, appendToGui,
    newGuiVar, getPanels, guiHandleToVar,
    guiInstrExp,
    listenKeyEvt, Key(..), KeyEvt(..), Guis(..),
    getKeyEventListener,
    -- * OSC
    getOscPortHandle,
    -- * Macros
    MacrosInit(..), readMacrosDouble, readMacrosString, readMacrosInt,
    -- * Cabbage Guis
    cabbage,
    -- * Hrtf pan
    simpleHrtfmove, simpleHrtfstat,
    -- * Udo plugins
    addUdoPlugin, renderUdoPlugins
) where

import Paths_csound_expression_typed

import Control.Applicative
import Control.Monad
import Data.Boolean
import Data.Default
import qualified Data.IntMap as IM
import qualified Data.Map    as M

import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.State.Strict
import Control.Monad.Trans.Reader

import Csound.Dynamic hiding (readMacrosDouble, readMacrosString, readMacrosInt)
import qualified Csound.Dynamic as D(readMacrosDouble, readMacrosString, readMacrosInt)

import Csound.Typed.GlobalState.Options
import Csound.Typed.GlobalState.Cache
import Csound.Typed.GlobalState.Elements hiding(saveNamedInstr, addUdoPlugin)
import Csound.Typed.Constants(infiniteDur)
import Csound.Typed.GlobalState.Opcodes(hrtfmove, hrtfstat, primInstrId)

import Csound.Typed.Gui.Gui(Panel(..), Win(..), GuiNode, GuiHandle(..), restoreTree, guiMap, mapGuiOnPanel, defText)
import qualified Csound.Typed.Gui.Cabbage.CabbageLang as Cabbage
import qualified Csound.Typed.Gui.Cabbage.Cabbage     as Cabbage

import qualified Csound.Typed.GlobalState.Elements as E(saveNamedInstr, addUdoPlugin)

type Dep a = DepT GE a

-- global side effects
newtype GE a = GE { unGE :: ReaderT Options (StateT History IO) a }

runGE :: GE a -> Options -> History -> IO (a, History)
runGE (GE f) opt hist = runStateT (runReaderT f opt) hist

evalGE :: Options -> GE a -> IO a
evalGE options a = fmap fst $ runGE a options def

execGE :: Options -> GE a -> IO History
execGE options a = fmap snd $ runGE a options def

instance Functor GE where
    fmap f = GE . fmap f . unGE

instance Applicative GE where
    pure = return
    (<*>) = ap

instance Monad GE where
    return = GE . return
    ma >>= mf = GE $ unGE ma >>= unGE . mf

instance MonadIO GE where
    liftIO = GE . liftIO . liftIO

data History = History
    { genMap            :: GenMap
    , writeGenMap       :: WriteGenMap
    , globalGenCounter  :: Int
    , stringMap         :: StringMap
    , sfMap             :: SfMap
    , midiMap           :: MidiMap GE
    , globals           :: Globals
    , instrs            :: Instrs
    , udoPlugins        :: [UdoPlugin]
    , namedInstrs       :: NamedInstrs
    , midis             :: [MidiAssign]
    , midiCtrls         :: [MidiCtrl]
    , totalDur          :: Maybe TotalDur
    , alwaysOnInstrs    :: [InstrId]
    , notes             :: [(InstrId, CsdEvent)]
    , userInstr0        :: Dep ()
    , bandLimitedMap    :: BandLimitedMap
    , cache             :: Cache GE
    , guis              :: Guis
    , oscListenPorts    :: OscListenPorts
    , cabbageGui        :: Maybe Cabbage.Lang
    , macrosInits       :: MacrosInits }

instance Default History where
    def = History def def def def def def def def def def def def def def def (return ()) def def def def def def

data Msg = Msg
data MidiAssign = MidiAssign MidiType Channel InstrId
data MidiCtrl   = MidiCtrl E E E

renderMidiAssign :: Monad m => MidiAssign -> DepT m ()
renderMidiAssign (MidiAssign ty chn instrId) = case ty of
    Massign         -> massign chn instrId
    Pgmassign mn    -> pgmassign chn instrId mn
    where
        massign n instr = depT_ $ opcs "massign" [(Xr, [Ir,Ir])] [int n, prim $ PrimInstrId instr]
        pgmassign pgm instr mchn = depT_ $ opcs "pgmassign" [(Xr, [Ir,Ir,Ir])] ([int pgm, prim $ PrimInstrId instr] ++ maybe [] (return . int) mchn)

renderMidiCtrl :: Monad m => MidiCtrl -> DepT m ()
renderMidiCtrl (MidiCtrl chno ctrlno val) = initc7 chno ctrlno val
    where
        initc7 :: Monad m => E -> E -> E -> DepT m ()
        initc7 a b c = depT_ $ opcs "initc7" [(Xr, [Ir, Ir, Ir])] [a, b, c]

data TotalDur = ExpDur E | InfiniteDur

getTotalDurForTerminator :: GE E
getTotalDurForTerminator = fmap (getTotalDurForTerminator' . totalDur) getHistory

pureGetTotalDurForF0 :: Maybe TotalDur -> Double
pureGetTotalDurForF0 = toDouble . maybe InfiniteDur id
    where
        toDouble x = case x of
            _           -> infiniteDur

getTotalDurForTerminator' :: Maybe TotalDur -> E
getTotalDurForTerminator' = toExpr . maybe InfiniteDur id
    where
        toExpr x = case x of
            InfiniteDur -> infiniteDur
            ExpDur e    -> e

setDurationToInfinite :: GE ()
setDurationToInfinite = setTotalDur InfiniteDur

setDuration :: E -> GE ()
setDuration = setTotalDur . ExpDur

setDurationForce :: E -> GE ()
setDurationForce = setTotalDur . ExpDur

saveStr :: String -> GE E
saveStr = fmap prim . onStringMap . newString
    where onStringMap = onHistory stringMap (\val h -> h{ stringMap = val })

getNextGlobalGenId :: GE Int
getNextGlobalGenId = onHistory globalGenCounter (\a h -> h{ globalGenCounter = a }) nextGlobalGenCounter

saveGen :: Gen -> GE Int
saveGen = onGenMap . newGen

onGenMap = onHistory genMap (\val h -> h{ genMap = val })

saveWriteGen :: Gen -> GE E
saveWriteGen = onWriteGenMap . newWriteGen

saveWriteTab :: Int -> GE E
saveWriteTab = onWriteGenMap . newWriteTab

onWriteGenMap = onHistory writeGenMap (\val h -> h{ writeGenMap = val })

saveTabs :: [Gen] -> GE E
saveTabs = onGenMap . fmap int . newTabOfGens

onSfMap :: State SfMap a -> GE a
onSfMap = onHistory sfMap (\val h -> h{ sfMap = val })

saveSf :: SfSpec -> GE Int
saveSf = onSfMap . newSf

sfTable :: History -> [(SfSpec, Int)]
sfTable = M.toList . idMapContent . sfMap

saveBandLimitedWave :: BandLimited -> GE BandLimitedId
saveBandLimitedWave = onBandLimitedMap . saveBandLimited
    where onBandLimitedMap = onHistory
                (\a -> (bandLimitedMap a))
                (\(blm) h -> h { bandLimitedMap = blm})

setTotalDur :: TotalDur -> GE ()
setTotalDur = onTotalDur . modify . const . Just
    where onTotalDur = onHistory totalDur (\a h -> h { totalDur = a })

saveMidi :: MidiAssign -> GE ()
saveMidi ma = onMidis $ modify (ma: )
    where onMidis = onHistory midis (\a h -> h { midis = a })

saveToMidiInstr :: MidiType -> Channel -> Dep () -> GE ()
saveToMidiInstr ty chn expr = onMidiMap (saveMidiInstr ty chn expr)
    where onMidiMap = modifyHistoryField midiMap (\a h -> h { midiMap = a })

saveMidiCtrl :: MidiCtrl -> GE ()
saveMidiCtrl ma = onMidis $ modify (ma: )
    where onMidis = onHistory midiCtrls (\a h -> h { midiCtrls = a })

saveUserInstr0 :: Dep () -> GE ()
saveUserInstr0 expr = onUserInstr0 $ modify ( >> expr)
    where onUserInstr0 = onHistory userInstr0 (\a h -> h { userInstr0 = a })

getSysExpr :: InstrId -> GE (Dep ())
getSysExpr terminatorInstrId = do
    e1 <- withHistory $ clearGlobals . globals
    dt <- getTotalDurForTerminator
    let e2 = event_i $ Event (primInstrId terminatorInstrId) dt 0.01 []
    return $ e1 >> e2
    where clearGlobals = snd . renderGlobals

saveAlwaysOnInstr :: InstrId -> GE ()
saveAlwaysOnInstr instrId = onAlwaysOnInstrs $ modify (instrId : )
    where onAlwaysOnInstrs = onHistory alwaysOnInstrs (\a h -> h { alwaysOnInstrs = a })

addNote :: InstrId -> CsdEvent -> GE ()
addNote instrId evt = modifyHistory $ \h -> h { notes = (instrId, evt) : notes h }

{-
setMasterInstrId :: InstrId -> GE ()
setMasterInstrId masterId = onMasterInstrId $ put masterId
    where onMasterInstrId = onHistory masterInstrId (\a h -> h { masterInstrId = a })
-}
----------------------------------------------------------------------
-- state modifiers

withOptions :: (Options -> a) -> GE a
withOptions f = GE $ asks f

getOptions :: GE Options
getOptions = withOptions id

getHistory :: GE History
getHistory = GE $ lift get

putHistory :: History -> GE ()
putHistory h = GE $ lift $ put h

withHistory :: (History -> a) -> GE a
withHistory f = GE $ lift $ fmap f get

modifyHistory :: (History -> History) -> GE ()
modifyHistory = GE . lift . modify

modifyHistoryField :: (History -> a) -> (a -> History -> History) -> (a -> a) -> GE ()
modifyHistoryField getter setter f = modifyHistory (\h -> setter (f $ getter h) h)

modifyWithHistory :: (History -> (a, History)) -> GE a
modifyWithHistory f = GE $ lift $ state f

-- update fields

onHistory :: (History -> a) -> (a -> History -> History) -> State a b -> GE b
onHistory getter setter st = GE $ ReaderT $ \_ -> StateT $ \history ->
    let (res, s1) = runState st (getter history)
    in  return (res, setter s1 history)

type UpdField a b = State a b -> GE b

onInstr :: UpdField Instrs a
onInstr = onHistory instrs (\a h -> h { instrs = a })

onGlobals :: UpdField Globals a
onGlobals = onHistory globals (\a h -> h { globals = a })

----------------------------------------------------------------------
-- named instruments

saveNamedInstr :: String -> InstrBody -> GE ()
saveNamedInstr name body = onNamedInstrs $ E.saveNamedInstr name body
    where onNamedInstrs = onHistory namedInstrs (\a h -> h { namedInstrs = a })

----------------------------------------------------------------------
-- cache

-- midi functions

type GetCache a b = a -> Cache GE -> Maybe b

fromCache :: GetCache a b -> a -> GE (Maybe b)
fromCache f key = withHistory $ f key . cache

type SetCache a b = a -> b -> Cache GE -> Cache GE

toCache :: SetCache a b -> a -> b -> GE ()
toCache f key val = modifyHistory $ \h -> h { cache = f key val (cache h) }

withCache :: TotalDur -> GetCache key val -> SetCache key val -> key -> GE val -> GE val
withCache dur lookupResult saveResult key getResult = do
    ma <- fromCache lookupResult key
    res <- case ma of
        Just a      -> return a
        Nothing     -> do
            r <- getResult
            toCache saveResult key r
            return r
    setTotalDur dur
    return res

--------------------------------------------------------
-- guis

data Guis = Guis
    { guiStateNewId     :: Int
    , guiStateInstr     :: DepT GE ()
    , guiStateToDraw    :: [GuiNode]
    , guiStateRoots     :: [Panel]
    , guiKeyEvents      :: KeyCodeMap }

-- it maps integer key codes to global variables
-- that acts like sensors.
type KeyCodeMap = IM.IntMap Var

instance Default Guis where
    def = Guis 0 (return ()) [] [] def

newGuiHandle :: GE GuiHandle
newGuiHandle = modifyWithHistory $ \h ->
    let (n, g') = bumpGuiStateId $ guis h
    in  (GuiHandle n, h{ guis = g' })

guiHandleToVar :: GuiHandle -> Var
guiHandleToVar (GuiHandle n) = Var GlobalVar Ir ('h' : show n)

newGuiVar :: GE (Var, GuiHandle)
newGuiVar = liftA2 (,) (onGlobals $ newPersistentGlobalVar Kr 0) newGuiHandle

modifyGuis :: (Guis -> Guis) -> GE ()
modifyGuis f = modifyHistory $ \h -> h{ guis = f $ guis h }

appendToGui :: GuiNode -> DepT GE () -> GE ()
appendToGui gui act = modifyGuis $ \st -> st
    { guiStateToDraw = gui : guiStateToDraw st
    , guiStateInstr  = guiStateInstr st >> act }

saveGuiRoot :: Panel -> GE ()
saveGuiRoot g = modifyGuis $ \st ->
    st { guiStateRoots = g : guiStateRoots st }

saveDefKeybdPanel :: GE ()
saveDefKeybdPanel = saveGuiRoot $ Single (Win "" Nothing g) isKeybd
    where
        g = defText "keyboard listener"
        isKeybd = True

bumpGuiStateId :: Guis -> (Int, Guis)
bumpGuiStateId s = (guiStateNewId s, s{ guiStateNewId = succ $ guiStateNewId s })

getPanels :: History -> [Panel]
getPanels h = fmap (mapGuiOnPanel (restoreTree m)) $ guiStateRoots $ guis h
    where m = guiMap $ guiStateToDraw $ guis h

-- have to be executed after all instruments
guiInstrExp :: GE (DepT GE ())
guiInstrExp = withHistory (guiStateInstr . guis)


-- key codes

-- | Keyboard events.
data KeyEvt = Press Key | Release Key
    deriving (Show, Eq)

-- | Keys.
data Key
    = CharKey Char
    | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | Scroll
    | CapsLook | LeftShift | RightShift | LeftCtrl | RightCtrl | Enter | LeftAlt | RightAlt | LeftWinKey | RightWinKey
    | Backspace | ArrowUp | ArrowLeft | ArrowRight | ArrowDown
    | Insert | Home | PgUp | Delete | End | PgDown
    | NumLock | NumDiv | NumMul | NumSub | NumHome | NumArrowUp
    | NumPgUp | NumArrowLeft | NumSpace | NumArrowRight | NumEnd
    | NumArrowDown | NumPgDown | NumIns | NumDel | NumEnter | NumPlus
    | Num7 | Num8 | Num9 | Num4 | Num5 | Num6 | Num1 | Num2 | Num3 | Num0 | NumDot
    deriving (Show, Eq)

keyToCode :: Key -> Int
keyToCode x = case x of
    CharKey a -> fromEnum a
    F1 -> 446
    F2 -> 447
    F3 -> 448
    F4 -> 449
    F5 -> 450
    F6 -> 451
    F7 -> 452
    F8 -> 453
    F9 -> 454
    F10 -> 456
    F11 -> 457
    F12 -> 458
    Scroll-> 276
    CapsLook -> 485
    LeftShift -> 481
    RightShift -> 482
    LeftCtrl -> 483
    RightCtrl -> 484
    Enter -> 269
    LeftAlt -> 489
    RightAlt -> 490
    LeftWinKey -> 491
    RightWinKey -> 492
    Backspace -> 264
    ArrowUp -> 338
    ArrowLeft -> 337
    ArrowRight -> 339
    ArrowDown -> 340
    Insert -> 355
    Home -> 336
    PgUp -> 341
    Delete -> 511
    End -> 343
    PgDown -> 342

    NumLock -> 383
    NumDiv -> 431
    NumMul -> 426
    NumSub -> 429
    NumHome -> 436
    NumArrowUp -> 438
    NumPgUp -> 341
    NumArrowLeft -> 337
    NumSpace -> 267
    NumArrowRight -> 339
    NumEnd -> 343
    NumArrowDown -> 340
    NumPgDown -> 342
    NumIns -> 355
    NumDel -> 511
    NumEnter -> 397
    NumPlus -> 427

    Num7 -> 439
    Num8 -> 440
    Num9 -> 441
    Num4 -> 436
    Num5 -> 437
    Num6 -> 438
    Num1 -> 433
    Num2 -> 434
    Num3 -> 435
    Num0 -> 432
    NumDot -> 430

keyEvtToCode :: KeyEvt -> Int
keyEvtToCode x = case x of
    Press k   -> keyToCode k
    Release k -> negate $ keyToCode k

listenKeyEvt :: KeyEvt -> GE Var
listenKeyEvt evt = do
    hist <- getHistory
    let g      = guis hist
        keyMap = guiKeyEvents g
        code   = keyEvtToCode evt

    case IM.lookup code keyMap of
        Just var -> return var
        Nothing  -> do
            var <- onGlobals $ newClearableGlobalVar Kr 0
            hist2 <- getHistory
            let newKeyMap = IM.insert code var keyMap
                newG      = g { guiKeyEvents = newKeyMap }
                hist3     = hist2 { guis = newG }
            putHistory hist3
            return var

-- assumes that first instrument id is 18 and 17 is free to use.
keyEventInstrId :: InstrId
keyEventInstrId = intInstrId 17

keyEventInstrBody :: KeyCodeMap -> GE InstrBody
keyEventInstrBody keyMap = execDepT $ do
    let keys     = flKeyIn
        isChange = changed keys ==* 1
    when1 Kr isChange $ do
        whens Kr (fmap (uncurry $ listenEvt keys) events) doNothing
    where
        doNothing = return ()

        listenEvt keySig keyCode var = (keySig ==* int keyCode, writeVar var 1)

        events = IM.toList keyMap

        flKeyIn :: E
        flKeyIn = opcs "FLkeyIn" [(Kr, [])] []

getKeyEventListener :: GE (Maybe Instr)
getKeyEventListener = do
    h <- getHistory
    if (IM.null $ guiKeyEvents $ guis h)
        then return Nothing
        else do
            saveAlwaysOnInstr keyEventInstrId
            body <- keyEventInstrBody $ guiKeyEvents $ guis h
            return $ Just (Instr keyEventInstrId body)

-----------------------------------------------
-- osc port listen

getOscPortHandle :: Int -> GE E
getOscPortHandle port = onOscPorts (fmap inlineVar $ getOscPortVar port)
    where
        onOscPorts = onHistory (\h -> (oscListenPorts h, globals h)) (\(ports, gs) h -> h { oscListenPorts = ports, globals = gs })

-----------------------------------------------
-- cabbage

cabbage :: Cabbage.Cab -> GE ()
cabbage cab = modifyHistory $ \h -> h { cabbageGui = Just $ Cabbage.runCab cab }

-----------------------------------------------
-- head pan

simpleHrtfmove :: E -> E -> E -> E -> E ->  E -> GE (E, E)
simpleHrtfmove a1 a2 a3 a4 a5 a6 = do
    (left, right) <- getHrtfFiles
    return $ hrtfmove a1 a2 a3 left right a4 a5 a6

simpleHrtfstat :: E -> E -> E -> E -> E -> GE (E, E)
simpleHrtfstat a1 a2 a3 a4 a5 = do
    (left, right) <- getHrtfFiles
    return $ hrtfstat a1 a2 a3 left right a4 a5

getHrtfFiles :: GE (E, E)
getHrtfFiles = do
    sr <- fmap defSampleRate getOptions
    (left, right) <- liftIO $ hrtfFileNames sr
    return (str left, str right)

hrtfFileNames :: Int -> IO (String, String)
hrtfFileNames sr = liftA2 (,) (getDataFileName (name "left" sr)) (getDataFileName (name "right" sr))
    where name dir n = concat ["data/hrtf-", show n, "-", dir, ".dat"]

-----------------------------------------------
-- read macros

readMacrosDouble :: String -> Double -> GE E
readMacrosDouble = readMacrosBy D.readMacrosDouble MacrosInitDouble

readMacrosString :: String -> String -> GE E
readMacrosString = readMacrosBy D.readMacrosString MacrosInitString

readMacrosInt :: String -> Int -> GE E
readMacrosInt    = readMacrosBy D.readMacrosInt    MacrosInitInt

readMacrosBy :: (String ->  E) -> (String -> a -> MacrosInit) -> String -> a -> GE E
readMacrosBy reader allocator name initValue = do
    onMacrosInits $ initMacros $ allocator name initValue
    return $ reader name
    where onMacrosInits = onHistory macrosInits (\val h -> h { macrosInits = val })

-----------------------------------------------
-- udo plugins

addUdoPlugin :: UdoPlugin -> GE ()
addUdoPlugin p = onUdo (E.addUdoPlugin p)
    where onUdo = onHistory udoPlugins (\val h -> h{ udoPlugins = val })

renderUdoPlugins :: History -> IO String
renderUdoPlugins h = fmap concat $ mapM getUdoPluginBody $ getUdoPluginNames $ udoPlugins h

getUdoPluginBody :: String -> IO String
getUdoPluginBody name = readFile =<< getDataFileName filename
    where filename = concat ["data/opcodes/", name, ".udo"]