{-----------------------------------------------------------------------------
    reactive-banana
------------------------------------------------------------------------------}
{-# LANGUAGE ExistentialQuantification #-}
module Reactive.Banana.Prim.Types where

import           Control.Monad.Trans.Class
import           Control.Monad.Trans.RWS.Lazy
import           Control.Monad.Trans.State
import           Data.Functor.Identity
import qualified Data.HashMap.Strict          as Map
import qualified Data.HashSet                 as Set
import           Data.Hashable
import           Data.Monoid
import           Data.Unique.Really
import qualified Data.Vault.Lazy              as Lazy
import           System.IO.Unsafe                       (unsafePerformIO)

import           Reactive.Banana.Prim.Cached
import qualified Reactive.Banana.Prim.Dated        as Dated
import qualified Reactive.Banana.Prim.Dependencies as Deps

type Deps = Deps.Deps

{-----------------------------------------------------------------------------
    Graph
------------------------------------------------------------------------------}
-- | A 'Graph' represents the connections between pulses and events.
data Graph = Graph
    { grDeps        :: Deps SomeNode   -- dependency information
    , grCache       :: Lazy.Vault      -- cache for the monad
    , grAlwaysP     :: Pulse ()        -- special pulse that always fires
    , grOutputCount :: !Position       -- ensure declaration order
    }
type Position = Integer

instance Show Graph where show = showDeps . grDeps

-- | A 'Network' represents the state of a pulse/latch network,
-- which consists of a 'Graph' and the values of all accumulated latches
-- in the network.
data Network = Network
    { nGraph       :: Graph
    , nLatchValues :: Dated.Vault
    , nTime        :: Dated.Time
    }

instance Show Network where show = show . nGraph

type Inputs        = (Lazy.Vault, [SomeNode])
type EvalNetwork a = Network -> IO (a, Network)
type Step          = EvalNetwork (IO ())

-- | Lenses for the 'Graph' and the 'Network' type
updateGraph       f = \s -> s { nGraph       = f (nGraph s) }
updateLatchValues f = \s -> s { nLatchValues = f (nLatchValues s) }
updateDeps        f = \s -> s { grDeps       = f (grDeps s) }
updateCache       f = \s -> s { grCache      = f (grCache s) }
updateOutputCount f = \s -> s { grOutputCount = f (grOutputCount s) }

emptyGraph :: Graph
emptyGraph = unsafePerformIO $ do
    uid <- newUnique
    return $ Graph
        { grDeps        = Deps.empty
        , grCache       = Lazy.empty
        , grAlwaysP     = Pulse
            { evaluateP = return Deps.Children
            , getValueP = const $ Just ()
            , uidP      = uid
            , nameP     = "alwaysP"
            }
        , grOutputCount = 0
        }

-- | The 'Network' that contains no pulses or latches.
emptyNetwork :: Network
emptyNetwork = Network
    { nGraph       = emptyGraph
    , nLatchValues = Dated.empty
    , nTime        = Dated.beginning
    }

-- The 'Build' monad is used to change the graph, for example to
-- * add nodes
-- * change dependencies
-- * add inputs or outputs
type BuildT  = RWST () BuildConf Network
type Build   = BuildT Identity 
type BuildIO = BuildT IO

type BuildConf = [IO ()] -- liftIOLater

{- Note [BuildT]

It is very convenient to be able to perform some IO functions
while (re)building a network graph. At the same time,
we need a good  MonadFix  instance to build recursive networks.
These requirements clash, so the solution is to split the types
into a pure variant and IO variant, the former having a good
MonadFix  instance while the latter can do arbitrary IO.

-}

{-----------------------------------------------------------------------------
    Pulse and Latch
------------------------------------------------------------------------------}
{-
    evaluateL/P
        calculates the next value and makes sure that it's cached
    getValueL/P
        retrieves the current value
    uidL/P
        used for dependency tracking and evaluation order
    nameP
        used for debugging
-}

data Pulse a = Pulse
    { evaluateP :: EvalP Deps.Continue
    , getValueP :: Lazy.Vault -> Maybe a
    , uidP      :: Unique
    , nameP     :: String
    }

data Latch a = Latch
    { getValueL :: Future (Dated.Box a)
    }

data LatchWrite = LatchWrite
    { evaluateL :: EvalP EvalL
    , uidL      :: Unique
    }

data Output = Output
    { evaluateO :: EvalP EvalO
    , uidO      :: Unique
    , positionO :: Position
    }

type EvalP = StateT Lazy.Vault BuildIO
    -- state: current pulse values

type Future = Dated.Dated
type EvalL  = Endo Dated.Vault
type EvalO  = Future (IO ())

nop :: EvalO
nop = return $ return ()

-- | Existential quantification for dependency tracking
data SomeNode
    = forall a. P (Pulse a)
    | L LatchWrite
    | O Output

instance Show SomeNode where show = show . hash

instance Eq SomeNode where
    (P x) == (P y)  =  uidP x == uidP y
    (L x) == (L y)  =  uidL x == uidL y
    (O x) == (O y)  =  uidO x == uidO y
    _     == _      =  False

uid :: SomeNode -> Unique
uid (P x) = uidP x
uid (L x) = uidL x
uid (O x) = uidO x

instance Hashable SomeNode where
    hashWithSalt s = hashWithSalt s . uid

{-----------------------------------------------------------------------------
    Show functions for debugging
------------------------------------------------------------------------------}
showDeps :: Deps SomeNode -> String
showDeps deps = unlines $
        [ detail node ++
          if null children then "" else " -> " ++ unwords (map short children)
        | node <- nodes
        , let children = Deps.children deps node
        ]
    where
    allChildren = Deps.allChildren deps
    nodes       = Set.toList . Set.fromList $
                  concat [x : xs | (x,xs) <- allChildren]
    dictionary  = Map.fromList $ zip nodes [1..]
    
    short node = maybe "X" show $ Map.lookup node dictionary
    
    detail (P x) = "P " ++ nameP x ++ " " ++ short (P x)
    detail (L x) = "L " ++ short (L x)
    detail (O x) = "O " ++ short (O x)