{-|
Module      : Game.GoreAndAsh.Actor.Collection
Description : Handling dynamic collections of actors
Copyright   : (c) Anton Gushcha, 2015-2016
License     : BSD3
Maintainer  : ncrashed@gmail.com
Stability   : experimental
Portability : POSIX
-}
module Game.GoreAndAsh.Actor.Collection(
    dynCollection
  , dDynCollection
  , DynCollection(..)
  , module ReExport
  ) where

import Control.Monad 
import Control.Wire
import Control.Wire.Unsafe.Event 
import Data.Filterable 
import Prelude hiding ((.), id)
import qualified Data.Foldable as F 

import Game.GoreAndAsh
import Game.GoreAndAsh.Actor.API
import Game.GoreAndAsh.Actor.Indexed

import Game.GoreAndAsh.Actor.Collection.Data as ReExport

-- | Helper that performs monadic action over value of event or returns default value
--
-- Note: the function is isomorphic to @Data.Maybe.maybe@
onEvent :: Monad m => b -> Event a -> (a -> m b) -> m b
onEvent def e f = case e of 
  NoEvent -> return def 
  Event a -> f a 

-- | Makes dynamic collection of wires.
--
-- * First input of wire is input for each inner wire.
--
-- * Second input is event for adding several wires to collection.
-- 
-- * Third input is event for removing several wires from collection.
--
-- * Wire returns list of outputs of inner wires.
--
-- Note: if ihibits one of the wires, it is removed from output result during its inhibition
dynCollection :: forall m i a b c c2 . (ActorMonad m, Eq i, DynCollection c, FilterConstraint c (GameWireIndexed m i a b), FilterConstraint c (Either () b), F.Foldable c2) => 
    (c (GameActor m i a b)) -- ^ Inital set of wires
    -> GameWire m (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b)
dynCollection initialActors = mkGen $ \ds input -> do 
  arrs <- sequence initialActors
  go arrs ds input
  where 
  go :: c (GameWireIndexed m i a b)
    -> GameTime
    -> (a, Event (c (GameActor m i a b)), Event (c2 i))
    -> GameMonadT m (Either () (c b), GameWire m (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b))
  go currentWires ds (a, addEvent, removeEvent) = do

    -- Adding new wires
    newAddedWires <- onEvent currentWires addEvent $ \newActors -> do 
      addWires <- sequence newActors
      return $ currentWires `concatDynColl` addWires

    -- Removing wires
    newRemovedWires <- onEvent newAddedWires removeEvent $ \ids ->  
      return $ F.foldl' (\acc i -> fFilter ((/= i) . indexedId) acc) newAddedWires ids

    -- Calculating outputs
    (bs, newWiresCntrls) <- liftM unzipDynColl $ mapM (\w -> stepWire w ds (Right a)) $ indexedWire <$> newRemovedWires
    let newWires = uncurry updateIndexedWire <$> (fmap const newWiresCntrls `zipDynColl` newRemovedWires)

    return $ length newWires `seq` (Right (rightsDynColl bs), mkGen $ go newWires)

-- | Makes dynamic collection of wires.
--
-- * First input of wire is input for each inner wire.
--
-- * Second input is event for adding several wires to collection.
--
-- * Third input is event for removing several wires from collection.
--
-- * Wire returns list of outputs of inner wires.
--
-- Note: it is delayed version of dynCollection, removing and adding of agents performs on next step after current.
--
-- Note: if ihibits one of the wires, it is removed from output result while it inhibits.
dDynCollection :: forall m i a b c c2 . (ActorMonad m, Eq i, DynCollection c, FilterConstraint c (GameWireIndexed m i a b), FilterConstraint c (Either () b), F.Foldable c2) =>
    (c (GameActor m i a b)) -- ^ Inital set of wires
    -> GameWire m (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b)
dDynCollection initialActors = mkGen $ \ds input -> do 
  arrs <- sequence initialActors
  go arrs ds input
  where 
  go :: c (GameWireIndexed m i a b)
    -> GameTime
    -> (a, Event (c (GameActor m i a b)), Event (c2 i))
    -> GameMonadT m (Either () (c b), GameWire m (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b))
  go currentWires ds (a, addEvent, removeEvent) = do
    -- Calculating outputs
    (bs, newWiresCntrls) <- liftM unzipDynColl $ mapM (\w -> stepWire w ds (Right a)) $ indexedWire <$> currentWires
    let newWires = uncurry updateIndexedWire <$> (fmap const newWiresCntrls `zipDynColl` currentWires)

    -- Adding new wires
    newAddedWires <- onEvent newWires addEvent $ \newActors -> do 
      addWires <- sequence newActors 
      return $ newWires `concatDynColl` addWires

    -- Removing wires
    newRemovedWires <- onEvent newAddedWires removeEvent $ \ids ->  
      return $ F.foldl' (\acc i -> fFilter ((/= i) . indexedId) acc) newAddedWires ids

    return $ length newRemovedWires `seq` (Right (rightsDynColl bs), mkGen $ go newRemovedWires)