module Eve.Internal.Run
  ( eve
  , eve_
  ) where

import Eve.Internal.Actions
import Eve.Internal.Listeners
import Eve.Internal.Events
import Eve.Internal.States()
import Eve.Internal.AppState

import Control.Concurrent.Chan
import Control.Monad
import Control.Monad.State
import Control.Lens
import Data.Default
import Data.Typeable

-- | This runs your application. It accepts an initialization block (which
-- is the same as any other 'App' or 'Action' block, which
-- registers event listeners and event providers. Note that nothing in this
-- block should use 'dispatchEvent' since it is possible that not all listeners
-- have yet been registered. You can use the 'afterInit' trigger to dispatch
-- any events you'd like to run at start-up.
--
-- It is polymorphic in the Monad it operates over, so you may use it with any 
-- custom base monad which implements 'MonadIO'.
--
-- If you don't need this functionality; the easiest way to get started is to simply
-- call it like so:
--
-- > import Eve
-- >
-- > initialize = App ()
-- > initialize = do
-- >   addListener ...
-- >   ...
-- >
-- > startApp :: IO ()
-- > startApp = eve_ initialize
eve :: (MonadIO m, Typeable m) => AppT AppState m () -> m AppState
eve initialize = do
  chan <- liftIO newChan
  execApp (def & asyncQueue .~ Just chan) $ do
    initialize
    dispatchEvent_ Init
    dispatchEvent_ AfterInit
    eventLoop chan
    dispatchEvent_ Exit

-- | 'eve' with '()' as its return value.
eve_ :: (MonadIO m, Typeable m) => AppT AppState m () -> m ()
eve_ = void . eve

-- | This is the main event loop, it runs recursively forever until something
-- sets the exit status. It runs the pre-event listeners, then checks if any
-- async events have finished, then runs the post event listeners and repeats.
eventLoop :: (MonadIO m, HasEvents base, Typeable m) => Chan (AppT base m ()) -> AppT base m ()
eventLoop chan = do
  dispatchEvent_ BeforeEvent
  join . liftIO $ readChan chan
  dispatchEvent_ AfterEvent
  shouldExit <- isExiting
  unless shouldExit $ eventLoop chan