{-----------------------------------------------------------------------------
    Reactive Banana
    
    Linking any implementation to an event-based framework
------------------------------------------------------------------------------}
module Reactive.Banana.Implementation (
    -- * Synopsis
    -- | Run event networks and hook them up to existing event-based frameworks.
    
    -- * Implementation
    PushIO, run,

    -- * Using existing event-based frameworks
    -- $Prepare
    Prepare, prepareEvents, reactimate, AddHandler, fromAddHandler, liftIO,
    
    module Data.Dynamic,
    ) where

import Reactive.Banana.PushIO as Implementation
-- import Reactive.Banana.Model hiding (Event, Behavior, run)
import qualified Reactive.Banana.Model as Model
import Data.Dynamic

import Data.List (nub)
import Control.Applicative
import Control.Monad.RWS
import Data.IORef

{-----------------------------------------------------------------------------
    PushIO specific functions
------------------------------------------------------------------------------}
type Flavor = PushIO

input :: Typeable a => Channel -> Model.Event PushIO a
input = event . Input

compileHandlers :: Model.Event Flavor (IO ()) -> IO [(Channel, Universe -> IO ())]
compileHandlers network = do
    -- compile network
    let network' = Implementation.unEvent network
    (paths,cache) <- Implementation.compile (invalidRef, Reactimate network')
    -- reduce to one path per channel
    let paths1 = groupChannelsBy (\p q x -> p x >> q x) paths

    -- prepare threading the cache as state
    rcache <- newIORef emptyCache
    writeIORef rcache cache
    let run m = do
            cache <- readIORef rcache
            (_,cache') <- runRun m cache
            writeIORef rcache cache'
        paths2 = map (\(i,p) -> (i, run . p)) $ paths1
    
    return paths2


-- FIXME: make this faster
groupChannelsBy :: (a -> a -> a) -> [(Channel, a)] -> [(Channel, a)]
groupChannelsBy f xs = [(i, foldr1 f [x | (j,x) <- xs, i == j]) | i <- channels]
    where channels = nub . map fst $ xs

{-----------------------------------------------------------------------------
    Setting up an event network
------------------------------------------------------------------------------}
{-$Prepare

    After having read all about 'Event's and 'Behavior's,
    you want to hook things up to an existing event-based framework,
    like @wxHaskell@ or @Gtk2Hs@.
    How do you do that?

    To do that, you have to use the 'Prepare' monad.
    The typical setup looks like this:
    
> main = do
>   ... -- other initialization
>
>   -- initialize event network
>   prepareEvents $ do
>       -- obtain  Event  from functions that register event handlers
>       emouse    <- fromAddHandler (registerMouseEvent window)
>       ekeyboard <- fromAddHandler (registerKeyEvent window)
>   
>       -- build event network
>       let
>           behavior1 = accumB ...
>           ...
>           event15 = union event13 event14
>   
>       -- animate relevant event occurences
>       reactimate $ fmap print event15
>       reactimate $ fmap drawCircle eventCircle
>
>   ... -- start the GUI framework here
    
    In short, you use 'fromAddHandler' to obtain /input events/;
    the library will register corresponding event handlers
    with your event-based framework.
    
    To animate /output events/, you use the 'reactimate' function.
    
    The whole setup has to be wrapped into a call to 'prepareEvents'.
    
    The 'Prepare' monad is an instance of 'MonadIO',
    so 'IO' is allowed inside. However, you can't pass anything
    of type @Event@ or @Behavior@ outside the 'prepareEvents' call;
    this is intentional.
    (You can probably circumvent this with mutable variables,
    but there is a 99,8% chance that earth will be suspended
    by time-traveling zygohistomorphisms
    if you do that; you have been warned.)

-}

type AddHandler'  = (Channel, (Universe -> IO ()) -> IO ())
type Preparations = ([Model.Event Flavor (IO ())], [AddHandler'])
newtype Prepare a = Prepare { unPrepare :: RWST () Preparations Channel IO a }

instance Monad (Prepare) where
    return  = Prepare . return
    m >>= k = Prepare $ unPrepare m >>= unPrepare . k
instance MonadIO Prepare where
    liftIO  = Prepare . liftIO

-- | Animate an output event.
-- Executes the 'IO' action whenever the event occurs.
reactimate :: Model.Event PushIO (IO ()) -> Prepare ()
reactimate e = Prepare $ tell ([e], [])

-- | Wrap around the 'Prepare' monad to set up an event network.
prepareEvents :: Prepare () -> IO ()
prepareEvents (Prepare m) = do
    (_,_,(outputs,inputs)) <- runRWST m () 0
    let
        -- union of all  reactimates
        network = mconcat outputs :: Model.Event PushIO (IO ())
    -- compile network
    paths <- compileHandlers network
    -- register event handlers
    sequence_ . map snd . applyChannels inputs $ paths

-- FIXME: make this faster
applyChannels :: [(Channel, a -> b)] -> [(Channel, a)] -> [(Channel, b)]
applyChannels fs xs =
    [(i, f x) | (i,f) <- fs, (j,x) <- xs, i == j]

-- | A value of type @AddHandler a@ is just an IO function that registers
-- callback functions, also known as event handlers. 
type AddHandler a = (a -> IO ()) -> IO ()

-- | Obtain an 'Event' from an 'AddHandler'.
-- This will register a callback function such that
-- an event will occur whenever the callback function is called.
fromAddHandler :: Typeable a => AddHandler a -> Prepare (Model.Event PushIO a)
fromAddHandler addHandler = Prepare $ do
        channel <- newChannel
        let addHandler' k = addHandler $ k . toUniverse channel
        tell ([], [(channel, addHandler')])
        return $ input channel
    where
    newChannel = do c <- get; put $! c+1; return c

{-----------------------------------------------------------------------------
    Run function for testing
------------------------------------------------------------------------------}
-- | Running an event network for the purpose of easy testing.
run :: Typeable a
    => (Model.Event PushIO a -> Model.Event PushIO b) -> [a] -> IO [[b]]
run f xs = do
    oref <- newIORef []

    href <- newIORef []
    let addHandler k = modifyIORef href (++[k])

    prepareEvents $ do
        e <- fromAddHandler addHandler
        reactimate $ fmap (\b -> modifyIORef oref (++[b])) (f e)

    handler <- (\ks x -> mapM ($ x) ks) <$> readIORef href

    forM xs $ \x -> do
        handler x
        bs <- readIORef oref
        writeIORef oref []
        return bs