{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE NamedFieldPuns #-}
{-----------------------------------------------------------------------------
    reactive-banana
------------------------------------------------------------------------------}
module Reactive.Banana.Prim.Mid.Evaluation
    ( step
    , applyDependencyChanges
    ) where

import Control.Monad
    ( join )
import Control.Monad.IO.Class
    ( liftIO )

import qualified Reactive.Banana.Prim.Low.GraphGC as GraphGC
import qualified Reactive.Banana.Prim.Low.OrderedBag as OB
import qualified Reactive.Banana.Prim.Low.Ref as Ref
import           Reactive.Banana.Prim.Mid.Plumbing
import           Reactive.Banana.Prim.Mid.Types

{-----------------------------------------------------------------------------
    Evaluation step
------------------------------------------------------------------------------}
-- | Evaluate all the pulses in the graph,
-- Rebuild the graph as necessary and update the latch values.
step :: Inputs -> Step
step (inputs,pulses)
        Network{ nTime = time1
        , nOutputs = outputs1
        , nAlwaysP = alwaysP
        , nGraphGC
        }
    = do

    -- evaluate pulses
    ((_, (latchUpdates, outputs)), dependencyChanges, os)
            <- runBuildIO (time1, alwaysP)
            $  runEvalP pulses
            $  evaluatePulses inputs nGraphGC

    latchUpdates                               -- update latch values from pulses
    applyDependencyChanges dependencyChanges   -- rearrange graph topology
        nGraphGC
    GraphGC.removeGarbage nGraphGC             -- remove unreachable pulses
    let actions :: [(Output, EvalO)]
        actions = OB.inOrder outputs outputs1  -- EvalO actions in proper order

        state2 :: Network
        !state2 = Network
            { nTime    = next time1
            , nOutputs = OB.inserts outputs1 os
            , nAlwaysP = alwaysP
            , nGraphGC
            }
    return (runEvalOs $ map snd actions, state2)

runEvalOs :: [EvalO] -> IO ()
runEvalOs = mapM_ join

{-----------------------------------------------------------------------------
    Dependency changes
------------------------------------------------------------------------------}
-- | Apply all dependency changes to the 'GraphGC'.
applyDependencyChanges :: DependencyChanges -> Dependencies -> IO ()
applyDependencyChanges changes g = do
    sequence_ [applyDependencyChange c g | c@(InsertEdge _ _) <- changes]
    sequence_ [applyDependencyChange c g | c@(ChangeParentTo _ _) <- changes]

applyDependencyChange
    :: DependencyChange SomeNode SomeNode -> Dependencies -> IO ()
applyDependencyChange (InsertEdge parent child) g =
    GraphGC.insertEdge (parent, child) g
applyDependencyChange (ChangeParentTo child parent) g = do
    GraphGC.clearPredecessors child g
    GraphGC.insertEdge (parent, child) g

{-----------------------------------------------------------------------------
    Traversal in dependency order
------------------------------------------------------------------------------}
-- | Update all pulses in the graph, starting from a given set of nodes
evaluatePulses :: [SomeNode] -> Dependencies -> EvalP ()
evaluatePulses inputs g = do
    action <- liftIO $ GraphGC.walkSuccessors_ inputs evaluateWeakNode g
    action

evaluateWeakNode :: Ref.WeakRef SomeNodeD -> EvalP GraphGC.Step
evaluateWeakNode w = do
    mnode <- liftIO $ Ref.deRefWeak w
    case mnode of
        Nothing -> pure GraphGC.Stop
        Just node -> evaluateNode node

-- | Recalculate a given node and return all children nodes
-- that need to evaluated subsequently.
evaluateNode :: SomeNode -> EvalP GraphGC.Step
evaluateNode someNode = do
    node <- Ref.read someNode
    case node of
        P PulseD{_evalP,_keyP} -> {-# SCC evaluateNodeP #-} do
            ma <- _evalP
            writePulseP _keyP ma
            pure $ case ma of
                Nothing -> GraphGC.Stop
                Just _  -> GraphGC.Next
        L lw -> {-# SCC evaluateLatchWrite #-} do
            evaluateLatchWrite lw
            pure GraphGC.Stop
        O o -> {-# SCC evaluateNodeO #-} do
            m <- _evalO o -- calculate output action
            rememberOutput (someNode,m)
            pure GraphGC.Stop

evaluateLatchWrite :: LatchWriteD -> EvalP ()
evaluateLatchWrite LatchWriteD{_evalLW,_latchLW} = do
    time   <- askTime
    mlatch <- liftIO $ Ref.deRefWeak _latchLW -- retrieve destination latch
    case mlatch of
        Nothing    -> pure ()
        Just latch -> do
            a <- _evalLW                    -- calculate new latch value
            -- liftIO $ Strict.evaluate a   -- see Note [LatchStrictness]
            rememberLatchUpdate $           -- schedule value to be set later
                Ref.modify' latch $ \l ->
                    a `seq` l { _seenL = time, _valueL = a }
