{-# LANGUAGE Arrows #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}

{- |
The type of a complete Rhine program:
A signal network together with a matching clock value.
-}
module FRP.Rhine.Type where

-- dunai
import Data.MonadicStreamFunction

-- rhine
import FRP.Rhine.Clock
import FRP.Rhine.Clock.Proxy
import FRP.Rhine.Reactimation.ClockErasure
import FRP.Rhine.ResamplingBuffer (ResamplingBuffer)
import FRP.Rhine.SN
import FRP.Rhine.Schedule (In, Out)

{- |
A 'Rhine' consists of a 'SN' together with a clock of matching type 'cl'.

It is a reactive program, possibly with open inputs and outputs.
If the input and output types 'a' and 'b' are both '()',
that is, the 'Rhine' is "closed",
then it is a standalone reactive program
that can be run with the function 'flow'.

Otherwise, one can start the clock and the signal network jointly as a monadic stream function,
using 'eraseClock'.
-}
data Rhine m cl a b = Rhine
  { forall (m :: Type -> Type) cl a b. Rhine m cl a b -> SN m cl a b
sn :: SN m cl a b
  , forall (m :: Type -> Type) cl a b. Rhine m cl a b -> cl
clock :: cl
  }

instance (GetClockProxy cl) => ToClockProxy (Rhine m cl a b) where
  type Cl (Rhine m cl a b) = cl

{- |
Start the clock and the signal network,
effectively hiding the clock type from the outside.

Since the caller will not know when the clock @'In' cl@ ticks,
the input 'a' has to be given at all times, even those when it doesn't tick.
-}
eraseClock ::
  (Monad m, Clock m cl, GetClockProxy cl) =>
  Rhine m cl a b ->
  m (MSF m a (Maybe b))
eraseClock :: forall (m :: Type -> Type) cl a b.
(Monad m, Clock m cl, GetClockProxy cl) =>
Rhine m cl a b -> m (MSF m a (Maybe b))
eraseClock Rhine {cl
SN m cl a b
clock :: cl
sn :: SN m cl a b
clock :: forall (m :: Type -> Type) cl a b. Rhine m cl a b -> cl
sn :: forall (m :: Type -> Type) cl a b. Rhine m cl a b -> SN m cl a b
..} = do
  (MSF m () (Time cl, Tag cl)
runningClock, Time cl
initTime) <- forall (m :: Type -> Type) cl.
Clock m cl =>
cl -> RunningClockInit m (Time cl) (Tag cl)
initClock cl
clock
  -- Run the main loop
  forall (m :: Type -> Type) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ proc a
a -> do
    (Time cl
time, Tag cl
tag) <- MSF m () (Time cl, Tag cl)
runningClock -< ()
    forall (m :: Type -> Type) cl a b.
(Monad m, Clock m cl, GetClockProxy cl) =>
Time cl
-> SN m cl a b -> MSF m (Time cl, Tag cl, Maybe a) (Maybe b)
eraseClockSN Time cl
initTime SN m cl a b
sn -< (Time cl
time, Tag cl
tag, a
a forall (f :: Type -> Type) a b. Functor f => a -> f b -> f a
<$ forall cl. ClockProxy cl -> Tag cl -> Maybe (Tag (In cl))
inTag (forall a. ToClockProxy a => a -> ClockProxy (Cl a)
toClockProxy SN m cl a b
sn) Tag cl
tag)

{- |
Loop back data from the output to the input.

Since output and input will generally tick at different clocks,
the data needs to be resampled.
-}
feedbackRhine ::
  ( Clock m (In cl)
  , Clock m (Out cl)
  , Time (In cl) ~ Time cl
  , Time (Out cl) ~ Time cl
  ) =>
  ResamplingBuffer m (Out cl) (In cl) d c ->
  Rhine m cl (a, c) (b, d) ->
  Rhine m cl a b
feedbackRhine :: forall (m :: Type -> Type) cl d c a b.
(Clock m (In cl), Clock m (Out cl), Time (In cl) ~ Time cl,
 Time (Out cl) ~ Time cl) =>
ResamplingBuffer m (Out cl) (In cl) d c
-> Rhine m cl (a, c) (b, d) -> Rhine m cl a b
feedbackRhine ResamplingBuffer m (Out cl) (In cl) d c
buf Rhine {cl
SN m cl (a, c) (b, d)
clock :: cl
sn :: SN m cl (a, c) (b, d)
clock :: forall (m :: Type -> Type) cl a b. Rhine m cl a b -> cl
sn :: forall (m :: Type -> Type) cl a b. Rhine m cl a b -> SN m cl a b
..} =
  Rhine
    { sn :: SN m cl a b
sn = forall (m :: Type -> Type) cl d c a b.
(Clock m (In cl), Clock m (Out cl), Time (In cl) ~ Time cl,
 Time (Out cl) ~ Time cl) =>
ResBuf m (Out cl) (In cl) d c
-> SN m cl (a, c) (b, d) -> SN m cl a b
Feedback ResamplingBuffer m (Out cl) (In cl) d c
buf SN m cl (a, c) (b, d)
sn
    , cl
clock :: cl
clock :: cl
clock
    }