{-----------------------------------------------------------------------------
    reactive-banana
------------------------------------------------------------------------------}
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
module Reactive.Banana.Prim.Plumbing where

import           Control.Monad
import           Control.Monad.Fix
import           Control.Monad.Trans.Class
import           Control.Monad.Trans.RWS
import qualified Control.Monad.Trans.State as State
import           Data.Function
import           Data.Functor
import           Data.Functor.Identity
import           Data.List
import           Data.Monoid
import           Data.Unique.Really
import qualified Data.Vault.Lazy           as Lazy
import           System.IO.Unsafe                  (unsafePerformIO)

import           Reactive.Banana.Prim.Cached                (HasCache(..))
import qualified Reactive.Banana.Prim.Dated        as Dated
import qualified Reactive.Banana.Prim.Dependencies as Deps
import           Reactive.Banana.Prim.Types

{-----------------------------------------------------------------------------
    Build primitive pulses and latches
------------------------------------------------------------------------------}
-- | Make 'Pulse' from evaluation function
newPulse :: String -> EvalP (Maybe a) -> Build (Pulse a)
newPulse name eval = unsafePerformIO $ do
    key <- Lazy.newKey
    uid <- newUnique
    return $ do
        let write = maybe (return Deps.Done) ((Deps.Children <$) . writePulseP key)
        return $ Pulse
            { evaluateP = {-# SCC evaluateP #-} write =<< eval
            , getValueP = Lazy.lookup key
            , uidP      = uid
            , nameP     = name
            }

-- | 'Pulse' that never fires.
neverP :: Build (Pulse a)
neverP = unsafePerformIO $ do
    uid <- newUnique
    return $ return $ Pulse
        { evaluateP = return Deps.Done
        , getValueP = const Nothing
        , uidP      = uid
        , nameP     = "neverP"
        }

-- | Make new 'Latch' that can be updated.
newLatch :: a -> Build (Pulse a -> Build (), Latch a)
newLatch a = unsafePerformIO $ do
    key <- Dated.newKey
    uid <- newUnique
    return $ do
        let
            write time   = maybe mempty (Endo . Dated.update' key time)
            latchWrite p = LatchWrite
                { evaluateL = {-# SCC evaluateL #-} do
                    time <- lift $ nTime <$> get
                    write (Dated.next time) <$> readPulseP p
                , uidL      = uid
                }
            updateOn p   = P p `addChild` L (latchWrite p)
        return
            (updateOn, Latch { getValueL = Dated.findWithDefault a key })

-- | Make a new 'Latch' that caches a previous computation
cachedLatch :: Dated.Dated (Dated.Box a) -> Latch a
cachedLatch eval = unsafePerformIO $ do
    key <- Dated.newKey
    return $ Latch { getValueL = {-# SCC getValueL #-} Dated.cache key eval }

-- | Add a new output that depends on a 'Pulse'.
--
-- TODO: Return function to unregister the output again.
addOutput :: Pulse EvalO -> Build ()
addOutput p = unsafePerformIO $ do
    uid <- newUnique
    return $ do
        pos <- grOutputCount . nGraph <$> get
        let o = Output
                { evaluateO = {-# SCC evaluateO #-} maybe nop id <$> readPulseP p
                , uidO      = uid
                , positionO = pos
                }
        modify $ updateGraph $ updateOutputCount $ (+1)
        P p `addChild` O o

{-----------------------------------------------------------------------------
    Build monad - add and delete nodes from the graph
------------------------------------------------------------------------------}
runBuildIO :: Network -> BuildIO a -> IO (a, Network)
runBuildIO s1 m = {-# SCC runBuildIO #-} do
    (a,s2,liftIOLaters) <- runRWST m () s1
    sequence_ liftIOLaters          -- execute late IOs
    return (a,s2)

-- Lift a pure  Build  computation into any monad.
-- See note [BuildT]
liftBuild :: Monad m => Build a -> BuildT m a
liftBuild m = RWST $ \r s -> return . runIdentity $ runRWST m r s

readLatchB :: Latch a -> Build a
readLatchB latch = state $ \network ->
    let (a,v) = Dated.runDated (getValueL latch) (nLatchValues network)
    in  (Dated.unBox a, network { nLatchValues = v } )

alwaysP :: Build (Pulse ())
alwaysP = grAlwaysP . nGraph <$> get

instance (MonadFix m, Functor m) => HasCache (BuildT m) where
    retrieve key = Lazy.lookup key . grCache . nGraph <$> get
    write key a  = modify $ updateGraph $ updateCache $ Lazy.insert key a

dependOn :: Pulse child -> Pulse parent -> Build ()
dependOn child parent = (P parent) `addChild` (P child)

changeParent :: Pulse child -> Pulse parent -> Build ()
changeParent child parent =
    modify . updateGraph . updateDeps $ Deps.changeParent (P child) (P parent)

addChild :: SomeNode -> SomeNode -> Build ()
addChild parent child =
    modify . updateGraph . updateDeps $ Deps.addChild parent child

liftIOLater :: IO () -> Build ()
liftIOLater x = tell [x]

{-----------------------------------------------------------------------------
    EvalP - evaluate pulses
------------------------------------------------------------------------------}
runEvalP :: Lazy.Vault -> EvalP (EvalL, [(Position, EvalO)])
    -> BuildIO (Lazy.Vault, EvalL, EvalO)
runEvalP pulse m = do
        ((wl,wo),s) <- State.runStateT m pulse
        return (s,wl, sequence_ <$> sequence (sortOutputs wo))
    where
    sortOutputs = map snd . sortBy (compare `on` fst)

readLatchP :: Latch a -> EvalP a
readLatchP = {-# SCC readLatchP #-} lift . liftBuild . readLatchB

readLatchFutureP :: Latch a -> EvalP (Future a)
readLatchFutureP latch = State.state $ \s -> (Dated.unBox <$> getValueL latch,s)

writePulseP :: Lazy.Key a -> a -> EvalP ()
writePulseP key a = {-# SCC writePulseP #-} State.modify $ Lazy.insert key a

readPulseP :: Pulse a -> EvalP (Maybe a)
readPulseP pulse = {-# SCC readPulseP #-} getValueP pulse <$> State.get

liftBuildIOP :: BuildIO a -> EvalP a
liftBuildIOP = lift

liftBuildP :: Build a -> EvalP a
liftBuildP = liftBuildIOP . liftBuild