{- |
Clocks implemented in the 'ScheduleT' monad transformer
can always be scheduled (by construction).
-}

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
module FRP.Rhine.Schedule.Trans where

-- dunai
import Data.MonadicStreamFunction.InternalCore

-- rhine
import Control.Monad.Schedule
import FRP.Rhine.Clock
import FRP.Rhine.Schedule


-- * Universal schedule for the 'ScheduleT' monad transformer

-- | Two clocks in the 'ScheduleT' monad transformer
--   can always be canonically scheduled.
--   Indeed, this is the purpose for which 'ScheduleT' was defined.
schedule
  :: ( Monad m
     , Clock (ScheduleT (Diff (Time cl1)) m) cl1
     , Clock (ScheduleT (Diff (Time cl1)) m) cl2
     , Time cl1 ~ Time cl2
     , Ord (Diff (Time cl1))
     , Num (Diff (Time cl1))
     )
  => Schedule (ScheduleT (Diff (Time cl1)) m) cl1 cl2
schedule = Schedule {..}
  where
    initSchedule cl1 cl2 = do
      (runningClock1, initTime) <- initClock cl1
      (runningClock2, _)        <- initClock cl2
      return
        ( runningSchedule cl1 cl2 runningClock1 runningClock2
        , initTime
        )

    -- Combines the two individual running clocks to one running clock.
    runningSchedule
      :: ( Monad m
         , Clock (ScheduleT (Diff (Time cl1)) m) cl1
         , Clock (ScheduleT (Diff (Time cl2)) m) cl2
         , Time cl1 ~ Time cl2
         , Ord (Diff (Time cl1))
         , Num (Diff (Time cl1))
         )
      => cl1 -> cl2
      -> MSF (ScheduleT (Diff (Time cl1)) m) () (Time cl1, Tag cl1)
      -> MSF (ScheduleT (Diff (Time cl1)) m) () (Time cl2, Tag cl2)
      -> MSF (ScheduleT (Diff (Time cl1)) m) () (Time cl1, Either (Tag cl1) (Tag cl2))
    runningSchedule cl1 cl2 rc1 rc2 = MSF $ \_ -> do
      -- Race both clocks against each other
      raceResult <- race (unMSF rc1 ()) (unMSF rc2 ())
      case raceResult of
        -- The first clock ticks first...
        Left  (((time, tag1), rc1'), cont2) -> return
          -- so we can emit its time stamp...
          ( (time, Left tag1)
          -- and continue.
          , runningSchedule cl1 cl2 rc1' (MSF $ const cont2)
          )
        -- The second clock ticks first...
        Right (cont1, ((time, tag2), rc2')) -> return
          -- so we can emit its time stamp...
          ( (time, Right tag2)
          -- and continue.
          , runningSchedule cl1 cl2 (MSF $ const cont1) rc2'
          )