{-# LANGUAGE ScopedTypeVariables #-}
-- | A reactor is an object to hold a bunch of reactions, which create other reactions and events and respond to events.
module Data.Reactor   (Reactor (..), mkReactor) where

import Data.List (nub)
import Data.Maybe (fromMaybe, catMaybes)

import Control.Applicative ((<$>))

import Control.Monad (foldM, ap) 

import Control.Monad.State (MonadState, get, put)
import Control.Monad.Writer (runWriterT, listen) 
import Control.Monad.Reader (local, runReaderT)

import Control.Arrow (second)

import Data.Reactor.Untypeds (toUntyped)
import Data.Reactor.Reaction (External, Recover, Reaction)
import Data.Reactor.MinimalGraph (Index, MinimalGraph(..), mkMinimalGraph)
import Data.Reactor.Pruned (Pruned (..), expand, serialize, restore)
import Data.Reactor.Operational  (OperationalLayer, Operational (..), mkOperationalPruned)
import Data.Reactor.Serialization (Serialization, SerialReactor)

-- a Pruned object for Operationals
type ReaTree m = Pruned (OperationalLayer m) (Maybe Recover,[Index])

-- | The reactor object. Once created this closures control its lifecycle. Updaters return Nothing when the reactor is wrapped around no reactions.
data Reactor m c = Reactor {
	-- | Update itself digesting a new event
	insertExternals :: [External] -> m (Maybe (Reactor m c)), 
	-- | Regenerate itself from a serialization
	restoreReactor :: SerialReactor c -> m (Maybe (Reactor m c)),
	-- | Serialize its internals, for later restoring.
	serializeReactor :: SerialReactor c

serialize' :: ReaTree m -> ([Maybe Recover], [Index])
serialize'  = second (concat) . unzip . serialize

insertSerialization 	:: c 		--  reactor global state
			-> External 	--  event to happen 
			-> [ReaTree m] 	--  reaction trees
			-> Serialization c --  serialization object
			-> (Index,Serialization c) --  updated object

insertSerialization c e xs (MinimalGraph add' _ _) = let (mrs,iss) = unzip $ map serialize' xs in 
		add' ((c,e,mrs),nub . concat $ iss)

purgeSerialization :: [ReaTree m] ->  Serialization c -> Maybe (Serialization c)
purgeSerialization ns (MinimalGraph _ resize' _) = resize' (nub . concat . map (snd . serialize') $ ns)

-- wrap a reaction 
mkReaTree ::(Monad m, Functor m) =>  Reaction m -> ReaTree m
mkReaTree r =  mkOperationalPruned $ Operational Nothing (Right r)

-- create the reactor object closure
zero :: (Monad m, Functor m) => [Reaction m] -> (Serialization c,[ReaTree m])
zero rs = (mkMinimalGraph, map mkReaTree rs)	
-- create a reactor object from the closure. Fails if the ReaTree list is empty, which means all reactions are over
new :: (Functor m, MonadState c m) => [Reaction m] -> (Serialization c,[ReaTree m]) -> Maybe (Reactor m c)
new rs (dg,ns) = case ns of
	[] -> Nothing
	_ -> Just $ Reactor (insert rs (dg',ns)) (restore' rs) (map (fst . serialize') ns, values dg') 
	where dg' = fromMaybe 
			(error "Restore graph reduction failed")
			$ purgeSerialization ns dg 

-- create a new reactor restoring from a serialization
restore' :: forall m c . (Functor m, MonadState c m) => [Reaction m] -> SerialReactor c -> m (Maybe (Reactor m c))
restore' rs (actual,ecs) = new rs <$> second (ctxz actual) <$> foldM k (zero rs) ecs where
	ctxz :: [[Maybe Recover]] -> [ReaTree m] -> [ReaTree m]
	ctxz mrs ns = map (\(mr,n) -> restore n $ zip mr $ error "restoring dependencies") $ zip mrs ns
	k (dg,ns) (c,e,mrs) = if length mrs /= length ns 
		then error "Restore failed in the numbers of base reactions" 
		else do put c 
			insert' (dg,ctxz mrs ns) e

-- create a reactor from the closure inserting some events
insert :: (Functor m, MonadState c m) => [Reaction m] -> (Serialization c,[ReaTree m]) -> [External] -> m (Maybe (Reactor m c))
insert rs (dg,ns) es = new rs <$> foldM insert' (dg,ns) es

-- insert one event updating the closure
insert' :: (Functor m, MonadState c m) => (Serialization c,[ReaTree m]) -> External -> m (Serialization c,[ReaTree m])
insert' (dg,ns) e = do
	c <- get
	let (i,dg') = insertSerialization c e ns dg
	ns' <- catMaybes <$> mapM (react (i,e)) ns 
	return (dg' ,ns') 

-- core reaction. Consumes all events , the firestarter and recursively all the produced events
react :: (Monad m, Functor m) =>   (Index,External) -> ReaTree m  -> m (Maybe (ReaTree m))
react (i,e) p = fmap fst . runWriterT . flip runReaderT (i,toUntyped e) $ z (Just p) where
	z Nothing  = return Nothing
	z (Just p') = do
		(p'',es) <- listen $ expand p'
		if null es then return p'' else case p'' of
			Nothing -> return Nothing -- information leak	
			p''' -> let k mp e' = local (\(i',_) -> (i',e')) $ z mp
				in foldM k p''' es

-- | build a reactor object  from a list of basic reactions
mkReactor :: (Functor m, MonadState c m) 
	=> [Reaction m] 	-- ^ list of base reactions
	-> Reactor m c	-- ^ a fresh reactor object

mkReactor = fromMaybe (error "cannot make a reactor with no base reactions") . (new `ap` zero)