{-# LANGUAGE RecursiveDo #-}

{- | A process that is triggered, stepped for a while, then finished.

Useful to drive animations and filter other events and behaviors.

@
-- Set up a 1 second countdown
(startingE, startedE, starting) <-
  Course.setup (startE $> 1.0) $
    tickE $> \old ->
      if old > dt then
        Right (old - dt)
      else
        Left ()

-- Prevent click events after starting the countdown
let clicks = fmap (Course.whenIdle starting) allClicks
@
-}

module Engine.ReactiveBanana.Course
  ( Course(..)
  , setup

    -- * General state-aware event filters
  , when
  , unless

    -- * Course state event filters
  , whenIdle
  , whenActive
  , whenFinished

    -- * Course state predicates
  , isIdle
  , isActive
  , isFinished
  ) where

import RIO hiding (when, unless)

import Data.Maybe (fromJust)
import Data.These (These(..))
import Reactive.Banana qualified as RB
import Reactive.Banana.Frameworks qualified as RBF

data Course a
  = Idle -- ^ Waiting for a trigger event
  | Active a -- ^ Processing step events
  | Finished -- ^ A final event has fired
  deriving (Course a -> Course a -> Bool
(Course a -> Course a -> Bool)
-> (Course a -> Course a -> Bool) -> Eq (Course a)
forall a. Eq a => Course a -> Course a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Course a -> Course a -> Bool
$c/= :: forall a. Eq a => Course a -> Course a -> Bool
== :: Course a -> Course a -> Bool
$c== :: forall a. Eq a => Course a -> Course a -> Bool
Eq, Eq (Course a)
Eq (Course a)
-> (Course a -> Course a -> Ordering)
-> (Course a -> Course a -> Bool)
-> (Course a -> Course a -> Bool)
-> (Course a -> Course a -> Bool)
-> (Course a -> Course a -> Bool)
-> (Course a -> Course a -> Course a)
-> (Course a -> Course a -> Course a)
-> Ord (Course a)
Course a -> Course a -> Bool
Course a -> Course a -> Ordering
Course a -> Course a -> Course a
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
forall {a}. Ord a => Eq (Course a)
forall a. Ord a => Course a -> Course a -> Bool
forall a. Ord a => Course a -> Course a -> Ordering
forall a. Ord a => Course a -> Course a -> Course a
min :: Course a -> Course a -> Course a
$cmin :: forall a. Ord a => Course a -> Course a -> Course a
max :: Course a -> Course a -> Course a
$cmax :: forall a. Ord a => Course a -> Course a -> Course a
>= :: Course a -> Course a -> Bool
$c>= :: forall a. Ord a => Course a -> Course a -> Bool
> :: Course a -> Course a -> Bool
$c> :: forall a. Ord a => Course a -> Course a -> Bool
<= :: Course a -> Course a -> Bool
$c<= :: forall a. Ord a => Course a -> Course a -> Bool
< :: Course a -> Course a -> Bool
$c< :: forall a. Ord a => Course a -> Course a -> Bool
compare :: Course a -> Course a -> Ordering
$ccompare :: forall a. Ord a => Course a -> Course a -> Ordering
Ord, Int -> Course a -> ShowS
[Course a] -> ShowS
Course a -> String
(Int -> Course a -> ShowS)
-> (Course a -> String) -> ([Course a] -> ShowS) -> Show (Course a)
forall a. Show a => Int -> Course a -> ShowS
forall a. Show a => [Course a] -> ShowS
forall a. Show a => Course a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Course a] -> ShowS
$cshowList :: forall a. Show a => [Course a] -> ShowS
show :: Course a -> String
$cshow :: forall a. Show a => Course a -> String
showsPrec :: Int -> Course a -> ShowS
$cshowsPrec :: forall a. Show a => Int -> Course a -> ShowS
Show, (forall a b. (a -> b) -> Course a -> Course b)
-> (forall a b. a -> Course b -> Course a) -> Functor Course
forall a b. a -> Course b -> Course a
forall a b. (a -> b) -> Course a -> Course b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
<$ :: forall a b. a -> Course b -> Course a
$c<$ :: forall a b. a -> Course b -> Course a
fmap :: forall a b. (a -> b) -> Course a -> Course b
$cfmap :: forall a b. (a -> b) -> Course a -> Course b
Functor)

setup
  :: RB.Event a -- ^ Trigger event
  -> RB.Event (a -> Either final a) -- ^ Step event
  -> RBF.MomentIO (RB.Event a, RB.Event final, RB.Behavior (Course a)) -- ^ (active event, finished event, current state)
setup :: forall a final.
Event a
-> Event (a -> Either final a)
-> MomentIO (Event a, Event final, Behavior (Course a))
setup Event a
triggerE Event (a -> Either final a)
stepE = mdo
  (Event (Maybe final)
e, Behavior (Course a)
b) <- Course a
-> Event (Course a -> (Maybe final, Course a))
-> MomentIO (Event (Maybe final), Behavior (Course a))
forall (m :: * -> *) acc x.
MonadMoment m =>
acc -> Event (acc -> (x, acc)) -> m (Event x, Behavior acc)
RB.mapAccum Course a
forall a. Course a
Idle (Event (Course a -> (Maybe final, Course a))
 -> MomentIO (Event (Maybe final), Behavior (Course a)))
-> Event (Course a -> (Maybe final, Course a))
-> MomentIO (Event (Maybe final), Behavior (Course a))
forall a b. (a -> b) -> a -> b
$
    (These a (a -> Either final a)
 -> Course a -> (Maybe final, Course a))
-> Event (These a (a -> Either final a))
-> Event (Course a -> (Maybe final, Course a))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap These a (a -> Either final a)
-> Course a -> (Maybe final, Course a)
forall {a} {a} {a}.
These a (a -> Either a a) -> Course a -> (Maybe a, Course a)
dispatch (Event (These a (a -> Either final a))
 -> Event (Course a -> (Maybe final, Course a)))
-> Event (These a (a -> Either final a))
-> Event (Course a -> (Maybe final, Course a))
forall a b. (a -> b) -> a -> b
$
      Event a
-> Event (a -> Either final a)
-> Event (These a (a -> Either final a))
forall a b. Event a -> Event b -> Event (These a b)
RB.merge Event a
triggerE (Behavior (Course a)
-> Event (a -> Either final a) -> Event (a -> Either final a)
forall a e. Behavior (Course a) -> Event e -> Event e
whenActive Behavior (Course a)
b Event (a -> Either final a)
stepE)

  (Event a
activeE, Handler a
fireActive) <- MomentIO (Event a, Handler a)
forall a. MomentIO (Event a, Handler a)
RBF.newEvent
  Event (IO ()) -> MomentIO ()
RBF.reactimate (Event (IO ()) -> MomentIO ()) -> Event (IO ()) -> MomentIO ()
forall a b. (a -> b) -> a -> b
$ (Event (Maybe final)
e Event (Maybe final) -> Behavior (Course a) -> Event (Course a)
forall a b. Event a -> Behavior b -> Event b
RB.@> Behavior (Course a)
b) Event (Course a) -> (Course a -> IO ()) -> Event (IO ())
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \case
    Active a
a ->
      Handler a
fireActive a
a
    Course a
_ ->
      () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()

  Event final
finishedE <- Event final -> MomentIO (Event final)
forall (m :: * -> *) a. MonadMoment m => Event a -> m (Event a)
RB.once (Event final -> MomentIO (Event final))
-> Event final -> MomentIO (Event final)
forall a b. (a -> b) -> a -> b
$
    (Maybe final -> final) -> Event (Maybe final) -> Event final
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Maybe final -> final
forall a. HasCallStack => Maybe a -> a
fromJust (Event (Maybe final) -> Event final)
-> Event (Maybe final) -> Event final
forall a b. (a -> b) -> a -> b
$ (Maybe final -> Bool) -> Event (Maybe final) -> Event (Maybe final)
forall a. (a -> Bool) -> Event a -> Event a
RB.filterE Maybe final -> Bool
forall a. Maybe a -> Bool
isJust Event (Maybe final)
e
  (Event a, Event final, Behavior (Course a))
-> MomentIO (Event a, Event final, Behavior (Course a))
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Event a
activeE, Event final
finishedE, Behavior (Course a)
b)
  where
    dispatch :: These a (a -> Either a a) -> Course a -> (Maybe a, Course a)
dispatch = \case
      This a
initial -> \case
        Course a
Idle ->
          (Maybe a
forall a. Maybe a
Nothing, a -> Course a
forall a. a -> Course a
Active a
initial)
        Active{} ->
          String -> (Maybe a, Course a)
forall a. HasCallStack => String -> a
error String
"trigger not filtered when active"
        Course a
Finished ->
          String -> (Maybe a, Course a)
forall a. HasCallStack => String -> a
error String
"trigger not filtered when finished"

      That a -> Either a a
step -> \case
        Course a
Idle ->
          String -> (Maybe a, Course a)
forall a. HasCallStack => String -> a
error String
"tick not filtered when idle"
        Active a
current ->
          case a -> Either a a
step a
current of
            Left a
done ->
              (a -> Maybe a
forall a. a -> Maybe a
Just a
done, Course a
forall a. Course a
Finished)
            Right a
next ->
              (Maybe a
forall a. Maybe a
Nothing, a -> Course a
forall a. a -> Course a
Active a
next)
        Course a
Finished ->
          String -> (Maybe a, Course a)
forall a. HasCallStack => String -> a
error String
"tick not filtered when finished"

      These{} ->
        String -> Course a -> (Maybe a, Course a)
forall a. HasCallStack => String -> a
error String
"tick happened on top of trigger"

when :: (Course a -> Bool) -> RB.Behavior (Course a) -> RB.Event e -> RB.Event e
when :: forall a e.
(Course a -> Bool) -> Behavior (Course a) -> Event e -> Event e
when Course a -> Bool
pred Behavior (Course a)
course =
  Behavior (e -> Bool) -> Event e -> Event e
forall a. Behavior (a -> Bool) -> Event a -> Event a
RB.filterApply (Behavior (Course a)
course Behavior (Course a)
-> (Course a -> e -> Bool) -> Behavior (e -> Bool)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> Bool -> e -> Bool
forall a b. a -> b -> a
const (Bool -> e -> Bool) -> (Course a -> Bool) -> Course a -> e -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Course a -> Bool
pred)

unless :: (Course a -> Bool) -> RB.Behavior (Course a) -> RB.Event e -> RB.Event e
unless :: forall a e.
(Course a -> Bool) -> Behavior (Course a) -> Event e -> Event e
unless Course a -> Bool
pred Behavior (Course a)
course =
  Behavior (e -> Bool) -> Event e -> Event e
forall a. Behavior (a -> Bool) -> Event a -> Event a
RB.filterApply (Behavior (Course a)
course Behavior (Course a)
-> (Course a -> e -> Bool) -> Behavior (e -> Bool)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> Bool -> e -> Bool
forall a b. a -> b -> a
const (Bool -> e -> Bool) -> (Course a -> Bool) -> Course a -> e -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool) -> (Course a -> Bool) -> Course a -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Course a -> Bool
pred)

whenIdle :: RB.Behavior (Course a) -> RB.Event e -> RB.Event e
whenIdle :: forall a e. Behavior (Course a) -> Event e -> Event e
whenIdle = (Course a -> Bool) -> Behavior (Course a) -> Event e -> Event e
forall a e.
(Course a -> Bool) -> Behavior (Course a) -> Event e -> Event e
when Course a -> Bool
forall a. Course a -> Bool
isIdle

whenActive :: RB.Behavior (Course a) -> RB.Event e -> RB.Event e
whenActive :: forall a e. Behavior (Course a) -> Event e -> Event e
whenActive = (Course a -> Bool) -> Behavior (Course a) -> Event e -> Event e
forall a e.
(Course a -> Bool) -> Behavior (Course a) -> Event e -> Event e
when Course a -> Bool
forall a. Course a -> Bool
isActive

whenFinished :: RB.Behavior (Course a) -> RB.Event e -> RB.Event e
whenFinished :: forall a e. Behavior (Course a) -> Event e -> Event e
whenFinished = (Course a -> Bool) -> Behavior (Course a) -> Event e -> Event e
forall a e.
(Course a -> Bool) -> Behavior (Course a) -> Event e -> Event e
when Course a -> Bool
forall a. Course a -> Bool
isFinished

isIdle :: Course a -> Bool
isIdle :: forall a. Course a -> Bool
isIdle = \case
  Course a
Idle -> Bool
True
  Course a
_    -> Bool
False

isActive :: Course a -> Bool
isActive :: forall a. Course a -> Bool
isActive = \case
  Active{} -> Bool
True
  Course a
_        -> Bool
False

isFinished :: Course a -> Bool
isFinished :: forall a. Course a -> Bool
isFinished = \case
  Course a
Finished -> Bool
True
  Course a
_        -> Bool
False