{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, NoMonomorphismRestriction, MultiParamTypeClasses #-}
-- | Operational values wrap reactions with some historical tag. Index is taken from "Data.Reactor.MinimalGraph" and is a key to an happened External event.
module Data.Reactor.Operational  (Operational (..), OperationalLayer, mkOperationalPruned)

import Data.Typeable (cast)
import Control.Monad.Trans (lift) 
import Control.Monad.Writer (WriterT, tell) 
import Control.Monad.Reader (ReaderT, ask)

import Data.Reactor.Untypeds (Serial (Serial))
import Data.Reactor.Reaction (Reaction (..), step, Internal)
import Data.Reactor.MinimalGraph (Index)
import Data.Reactor.Pruned (mkPruned, Pruned)

-- | 'Reaction' contextualized with its history.
data Operational m = Operational {
	-- | Nothing if the reaction is a base reaction  or just an index to the contextualized event which borned it
	borned :: Maybe Index,
	-- |  an index to the event that killed the reaction or the alive reaction
	alive :: Either Index (Reaction m)

-- | The operational transformer. For reactions to react we store the index to external event and the actual internal event in the reader and store the new events produced by reactions in the writer
type OperationalLayer m = ReaderT (Index,Internal) (WriterT [Internal] m)

-- | Pruned object builder for 'Operational' values 
mkOperationalPruned :: (Functor m, Monad m) 
	=> Operational m		-- ^ the Operational for the base
        -> Pruned (OperationalLayer m) (Maybe Serial, [Index]) -- ^ the fresh pruned object 

mkOperationalPruned = mkPruned opexpand opprune oprestore opserialize where
	opexpand :: (Functor m, Monad m) => Operational m -> OperationalLayer m (Operational m, [Operational m])
	opexpand c@(Operational _ (Left _)) = return (c,[]) 
	opexpand (Operational borned' (Right r)) = do 
		(i,e) <- ask
		(ad,xs') <- case step r e of
			Nothing -> return $ (Right r,[])
			Just k -> lift $ do 
				(xs,es,mrea) <- lift k
				tell es
				return (maybe (Left i) Right mrea, xs)
		return (Operational borned' ad,map (Operational (Just i) . Right) xs')

	opserialize (Operational (Just i) (Right (Reaction _ v))) = (Just (Serial v),[i])
	opserialize (Operational (Just i) (Left j)) = (Nothing, [i,j])
	opserialize (Operational Nothing (Left j)) = (Nothing, [j])
	opserialize (Operational Nothing (Right (Reaction _ v))) = (Just (Serial v), [])

	oprestore (Operational bnd (Right (Reaction f _))) (Just (Serial x),_)  = case cast x of
		Nothing -> error "Restoring type corrupt"
		Just b' -> Operational bnd (Right (Reaction f b'))
	oprestore _ (Just _,_)  = error "Restoring a state to to dead reaction"
	oprestore  (Operational _ (Right _)) (Nothing,_) = error "No state to restore an operational"
	oprestore c (Nothing,_)  = c

	opprune (Operational _ (Left _)) = True
	opprune  _ = False