{-|
Module      : Pakoras.Conduit
Description : Routes signals to and from user-code.
Stability   : experimental

The job of 'Conduit' is to spawn the engine when 'start' is called,
and then route signals to their targets afterwards.
-}
module Pakoras.Conduit (
  start
  ) where

import Pakoras.Base

import qualified Pakoras.EngineSignals

import qualified Data.Map.Strict as Map
import Control.Concurrent
import Data.Binary
import Data.Traversable
import FRP.Sodium

mergeEventList :: [Event Signal] -> Event Signal
{-|
  Merges a list of events into one event.
-}
mergeEventList [s]    = s
mergeEventList (x:xs) = merge x (mergeEventList xs)

start :: Configuration -> IO (Event Signal, Signal -> Reactive ())
{-|
  The 'start' function will take a 'Pakoras.Base.Configuration' object
  and start an instance of the engine. It will then return an
  event, which will serve as the communication point between
  user-code and the engine.
-}
start config = do
    (trafficIn, pushIn) <- sync newEvent -- traffic coming into engine
    (trafficOut, pushOut) <- sync newEvent -- traffic going to user-code

    -- initialize modules
    (engineSignals, pushToEngineSignals) <- Pakoras.EngineSignals.start

    -- compile lists and maps
    let eventList   = [ -- list of events that need to be listened to
                        trafficIn,
                        engineSignals
                      ]

    let pushMap     = Map.fromList  [ -- map of (targets, push methods)
                                      (Conduit,       pushOut),
                                      (EngineSignals, pushToEngineSignals)
                                    ]

    -- merge eventList into one
    let mergedEvent = mergeEventList eventList;

    -- redirects all traffic to appropriate places
    -- defined here because of dependency on initialized modules
    let {
      pipeTraffic :: Signal -> IO ();
      pipeTraffic sig | t == All            = do traverse (\push -> sync $ push s) pushMap; return ()
                      | otherwise           = sync $ (Map.findWithDefault pushOut t pushMap) $ s
                       where t = signalTarget sig
                             s = sig
    }

    -- create pipe
    _ <- sync $ listen mergedEvent (\ sig -> do {_ <- forkIO $ pipeTraffic sig; return ()})

    -- return conduit-out and conduit-in push method
    return (trafficOut, pushIn)
