module Csound.Typed.Control.Osc(
    OscRef, OscHost, OscPort, OscAddress, OscType,
    initOsc, listenOsc, sendOsc,
    OscVal, listenOscVal
) where
import Data.Boolean (ifB, (==*))
import Csound.Dynamic(Rate(..))
import Csound.Typed.Types
import Csound.Typed.GlobalState hiding (oscInit, oscListen, oscSend)
import qualified Csound.Typed.GlobalState as C(oscInit, oscListen, oscSend)
import Csound.Typed.Control.Ref
newtype OscRef = OscRef { unOscRef :: D }
type OscPort = Int
type OscAddress = String
type OscType = String
type OscHost = String
initOsc :: OscPort -> OscRef
initOsc port = OscRef $ fromGE $ getOscPortHandle port
listenOsc :: forall a . Tuple a => OscRef -> OscAddress -> OscType -> Evt a
listenOsc oscRef oscAddr oscType = Evt $ \bam -> do
    resRef <- initOscRef oscType
    cond <- listen resRef
    when1 cond $ bam =<< readRef resRef
    where
        listen :: Tuple a => Ref a -> SE BoolSig
        listen ref = fmap (==* 1) $ csdOscListen ref oscRef oscAddr oscType
        csdOscListen :: Tuple a => Ref a -> OscRef -> OscAddress -> OscType -> SE Sig
        csdOscListen (Ref refVars) oscHandle addr ty =
            fmap fromGE $ fromDep $ hideGEinDep $ do
                expOscHandle <- toGE $ unOscRef oscHandle
                expAddr <- toGE $ text addr
                expOscType <- toGE $ text ty
                return $ C.oscListen expOscHandle expAddr expOscType refVars
        initOscRef :: OscType -> SE (Ref a)
        initOscRef typeStr = fmap Ref $ newLocalVars (fmap getOscRate typeStr) (fromTuple $ (defTuple :: a))
        getOscRate :: Char -> Rate
        getOscRate x = case x of
            'a' -> Ar
            's' -> Sr
            'i' -> Kr
            'f' -> Kr
            _   -> Kr
sendOsc :: forall a . Tuple a => OscHost -> OscPort -> OscAddress -> OscType -> Evt a -> SE ()
sendOsc host port addr ty evts = do
    flagRef <- newRef (0 :: Sig)
    valRef  <- newRef defTuple
    runEvt evts $ \a -> do
        flag <- readRef flagRef
        writeRef flagRef (flag + 1)
        writeRef valRef  a
    flag <- readRef flagRef
    value <- readRef valRef
    send flag value
    where
        send :: Tuple a => Sig -> a -> SE ()
        send trig as = SE $ hideGEinDep $ do
            args <- fromTuple as
            expTrig <- toGE trig
            expHost <- toGE $ text $ host
            expPort <- toGE $ int  $ port
            expAddr <- toGE $ text $ addr
            expTy   <- toGE $ text $ ty
            return $ C.oscSend $ expTrig : expHost : expPort : expAddr : expTy : args
class Tuple a => OscVal a where
    getOscTypes :: a -> String
    getOscRef :: a -> SE (Ref a)
instance OscVal Sig where
    getOscTypes = const "f"
    getOscRef = newCtrlRef
instance OscVal Str where
    getOscTypes = const "s"
    getOscRef = newRef
instance (OscVal a, OscVal b) => OscVal (a, b) where
    getOscTypes (a, b) = getOscTypes a ++ getOscTypes b
    getOscRef (a, b) = do
        refA <- getOscRef a
        refB <- getOscRef b
        return $ concatRef refA refB
instance (OscVal a, OscVal b, OscVal c) => OscVal (a, b, c) where
    getOscTypes (a, b, c) = getOscTypes a ++ getOscTypes b ++ getOscTypes c
    getOscRef (a, b, c) = do
        refA <- getOscRef a
        refB <- getOscRef b
        refC <- getOscRef c
        return $ concatRef3 refA refB refC
instance (OscVal a, OscVal b, OscVal c, OscVal d) => OscVal (a, b, c, d) where
    getOscTypes (a, b, c, d) = getOscTypes a ++ getOscTypes b ++ getOscTypes c ++ getOscTypes d
    getOscRef (a, b, c, d) = do
        refA <- getOscRef a
        refB <- getOscRef b
        refC <- getOscRef c
        refD <- getOscRef d
        return $ concatRef4 refA refB refC refD
instance (OscVal a, OscVal b, OscVal c, OscVal d, OscVal e) => OscVal (a, b, c, d, e) where
    getOscTypes (a, b, c, d, e) = getOscTypes a ++ getOscTypes b ++ getOscTypes c ++ getOscTypes d ++ getOscTypes e
    getOscRef (a, b, c, d, e) = do
        refA <- getOscRef a
        refB <- getOscRef b
        refC <- getOscRef c
        refD <- getOscRef d
        refE <- getOscRef e
        return $ concatRef5 refA refB refC refD refE
listenOscVal :: (Tuple a, OscVal a) => OscRef -> String -> a -> SE a
listenOscVal port path initVal = do
    ref <- getOscRef initVal
    runEvt (listenOsc port path (getOscTypes initVal)) $ \a -> writeRef ref a
    readRef ref