packages feed

imprevu-0.1.0: src/Imprevu/Events.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DeriveDataTypeable #-}

-- | All the building blocks to allow rules to build events.
module Imprevu.Events
--   onEvent, onEvent_, onEventOnce,
--   delEvent,
--   getEvents, getEvent,
--   getIntermediateResults,
--   schedule, schedule_, schedule', schedule'_,
--   getCurrentTime,
--   oneWeek, oneDay, oneHour, oneMinute,
--   timeEvent, messageEvent, victoryEvent, playerEvent, ruleEvent,
--   signalEvent, inputFormSignal,
--   liftEvent
    where

import Imprevu.Types
import Imprevu.SysMgt
import Control.Monad.Except
import Data.Typeable
import Data.Time hiding (getCurrentTime)
import Data.Time.Recurrence hiding (filter)
import Data.List
import Data.Maybe
import Safe

class (Typeable n, Applicative n, Monad n) => EvMgt n where
   --Events management
   onEvent         :: (Typeable a, Show a) => EventM n a -> ((EventNumber, a) -> n ()) -> n EventNumber
   delEvent        :: EventNumber -> n Bool
   getEvents       :: n [EventInfoN n]
   sendMessage     :: (Typeable a, Show a, Eq a) => Msg a -> a -> n ()

type Msg m = Signal String m

partial :: (MonadError String n) => String -> n (Maybe a) -> n a
partial s nm = do
   m <- nm
   case m of
      Just a -> return a
      Nothing -> throwError s

-- * Events

-- | register a callback on an event, disregard the event number
onEvent_ :: (Typeable a, Show a, EvMgt n) => EventM n a -> (a -> n ()) -> n EventNumber
onEvent_ e h = onEvent e (\(_, d) -> h d)

-- | set an handler for an event that will be triggered only once
onEventOnce :: (Typeable a, Show a, EvMgt n) => EventM n a -> (a -> n ()) -> n EventNumber
onEventOnce e h = do
    let handler (en, ed) = delEvent en >> h ed
    onEvent e handler

getEvent :: (EvMgt n) => EventNumber -> n (Maybe (EventInfoN n))
getEvent en = find (\(EventInfo en2 _ _ evst _) -> en == en2 && evst == SActive) <$> getEvents

getIntermediateResults :: (EvMgt n) => EventNumber -> n (Maybe [(ClientNumber, SomeData)])
getIntermediateResults en = do
   mev <- getEvent en
   case mev of
      Just ev -> return $ Just $ mapMaybe getInputResult (_env ev)
      Nothing -> return Nothing

getInputResult :: SignalOccurence -> Maybe (ClientNumber, SomeData)
getInputResult = undefined
--getInputResult (SignalOccurence (SignalData (InputS _ cn) r) _) = Just (cn, SomeData r)
--getInputResult _ = Nothing

-- | on the provided schedule, the supplied function will be called
schedule :: (EvMgt n, SysMgt n) => Schedule Freq -> (UTCTime -> n ()) -> n ()
schedule sched f = do
    now <- getCurrentTime
    let next = head $ starting now sched
    if next == now then executeAndScheduleNext f sched now
                   else void $ onEventOnce (timeEvent next) $ executeAndScheduleNext f sched

executeAndScheduleNext :: (EvMgt n) => (UTCTime -> n ()) -> Schedule Freq -> UTCTime -> n ()
executeAndScheduleNext f sched now = do
   f now
   let rest = drop 1 $ starting now sched
   when (rest /= []) $ void $ onEventOnce (timeEvent $ head rest) $ executeAndScheduleNext f sched

schedule_ :: (EvMgt n, SysMgt n) => Schedule Freq -> n () -> n ()
schedule_ ts f = schedule ts (const f)

--at each time provided, the supplied function will be called
schedule' :: (EvMgt n, SysMgt n) => [UTCTime] -> (UTCTime -> n ()) -> n ()
schedule' sched f = do
   let sched' = sort sched
   now <- getCurrentTime
   let nextMay = headMay $ filter (>=now) sched'
   case nextMay of
       Just next -> if next == now then executeAndScheduleNext' f sched' now
                                  else void $ onEventOnce (timeEvent next) $ executeAndScheduleNext' f sched'
       Nothing -> return ()

executeAndScheduleNext' :: (EvMgt n) => (UTCTime -> n ()) -> [UTCTime] -> UTCTime -> n ()
executeAndScheduleNext' f sched now = do
   f now
   let rest = drop 1 sched
   when (rest /= []) $ void $ onEventOnce (timeEvent $ head rest) $ executeAndScheduleNext' f sched


--schedule'_ :: [UTCTime] -> Nomex () -> Nomex ()
--schedule'_ ts f = schedule' ts (const f)

-- * Individual events

-- | Build an event firing at a specific time
timeEvent :: UTCTime -> EventM n UTCTime
timeEvent t = SignalEvent $ Signal t

-- | Build a message event, that can be intercepted by another rule
-- this is useful for message-passing style of communication
messageEvent :: (Typeable a, Show a, Eq a) => Msg a -> EventM n a
messageEvent m = SignalEvent m

-- | Build a event firing immediatly, yelding the value of the Nomex
liftEvent :: n a -> EventM n a
liftEvent = LiftEvent

-- * internals

 -- Embed a single Signal as an EventM
signalEvent    :: (Eq s, Typeable s, Show s, Typeable e, Show e, Eq e) => s -> EventM n e
signalEvent = SignalEvent . Signal

-- Embed a single Signal as an EventM
inputEvent    :: (Typeable e, Show e, Eq e) => InputField -> ClientNumber -> EventM n e
inputEvent i cn = SignalEvent $ Signal $ Input i cn