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 like 'eve_',
-- It is polymorphic in the Monad it operates over, so you may use it with any
-- custom base monad which implements 'MonadIO'. Upon termination of the app it
-- returns the final 'AppState'.
eve :: (MonadIO m, Typeable m) => AppT AppState m () -> m AppState
eve initialize = do
  chan <- liftIO newChan
  execEve (def & asyncQueue .~ Just chan) $ do
    initialize
    dispatchEvent_ Init
    dispatchEvent_ AfterInit
    eventLoop chan
    dispatchEvent_ Exit

-- | 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.
-- Here's a simple example:
--
-- > import Eve
-- >
-- > initialize = App ()
-- > initialize = do
-- >   addListener_ myListener
-- >   asyncEventProvider myProvider
-- >
-- > startApp :: IO ()
-- > startApp = eve_ initialize
eve_ :: App () -> IO ()
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