content: {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE GADTs #-}
  {-# LANGUAGE RankNTypes #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE TypeFamilies #-}
  
  {- |
  Asynchronous signal networks are combinations of clocked signal functions ('ClSF's)
  and matching 'ResamplingBuffer's,
  all satisfying the appropriate clock type constraints.
  
  This module defines the 'SN' type,
  combinators are found in a submodule.
  -}
  module FRP.Rhine.SN (
    module FRP.Rhine.SN,
    module FRP.Rhine.SN.Type,
  ) where
  
  -- base
  import Control.Monad (join)
  
  -- transformers
  import Control.Monad.Trans.Reader (reader)
  
  -- automata
  import Data.Stream.Result (Result (..))
  
  -- rhine
  import FRP.Rhine.ClSF.Core
  import FRP.Rhine.Clock
  import FRP.Rhine.Clock.Proxy
  import FRP.Rhine.Clock.Util (genTimeInfo)
  import FRP.Rhine.Reactimation.ClockErasure
  import FRP.Rhine.ResamplingBuffer
  import FRP.Rhine.SN.Type
  import FRP.Rhine.Schedule
  
  {- | A synchronous automaton is the basic building block.
    For such an 'SN', data enters and leaves the system at the same rate as it is processed.
  -}
  synchronous ::
    forall cl m a b.
    (cl ~ In cl, cl ~ Out cl, Monad m, Clock m cl, GetClockProxy cl) =>
    ClSF m cl a b ->
    SN m cl a b
  synchronous clsf = SN $ reader $ \initialTime -> proc (time, tag, Just a) -> do
    b <- eraseClockClSF (getClockProxy @cl) initialTime clsf -< (time, tag, a)
    returnA -< Just b
  {-# INLINE synchronous #-}
  
  -- | Two 'SN's may be sequentially composed if there is a matching 'ResamplingBuffer' between them.
  sequential ::
    ( Clock m clab
    , Clock m clcd
    , Clock m (Out clab)
    , Clock m (Out clcd)
    , Clock m (In clab)
    , Clock m (In clcd)
    , GetClockProxy clab
    , GetClockProxy clcd
    , Time clab ~ Time clcd
    , Time clab ~ Time (Out clab)
    , Time clcd ~ Time (In clcd)
    , Monad m
    ) =>
    SN m clab a b ->
    ResamplingBuffer m (Out clab) (In clcd) b c ->
    SN m clcd c d ->
    SN m (SequentialClock clab clcd) a d
  -- A sequentially composed signal network may either be triggered in its first component,
  -- or its second component. In either case,
  -- the resampling buffer (which connects the two components) may be triggered,
  -- but only if the outgoing clock of the first component ticks,
  -- or the incoming clock of the second component ticks.
  sequential sn1 resBuf sn2 = SN $ reader $ \initialTime ->
    let
      proxy1 = toClockProxy sn1
      proxy2 = toClockProxy sn2
     in
      proc (time, tag, maybeA) -> do
        resBufIn <- case tag of
          Left tagL -> do
            maybeB <- eraseClockSN initialTime sn1 -< (time, tagL, maybeA)
            returnA -< Left <$> ((time,,) <$> outTag proxy1 tagL <*> maybeB)
          Right tagR -> do
            returnA -< Right . (time,) <$> inTag proxy2 tagR
        maybeC <- mapMaybeS $ eraseClockResBuf (outProxy proxy1) (inProxy proxy2) initialTime resBuf -< resBufIn
        case tag of
          Left _ -> do
            returnA -< Nothing
          Right tagR -> do
            eraseClockSN initialTime sn2 -< (time, tagR, join maybeC)
  {-# INLINE sequential #-}
  
  -- | Two 'SN's with the same input and output data may be parallely composed.
  parallel snL snR = SN $ reader $ \initialTime -> proc (time, tag, maybeA) -> do
    case tag of
      Left tagL -> eraseClockSN initialTime snL -< (time, tagL, maybeA)
      Right tagR -> eraseClockSN initialTime snR -< (time, tagR, maybeA)
  {-# INLINE parallel #-}
  
  -- | A 'ClSF' can always be postcomposed onto an 'SN' if the clocks match on the output.
  postcompose sn clsf = SN $ reader $ \initialTime ->
    let
      proxy = toClockProxy sn
     in
      proc input@(time, tag, _) -> do
        bMaybe <- eraseClockSN initialTime sn -< input
        mapMaybeS $ eraseClockClSF (outProxy proxy) initialTime clsf -< (time,,) <$> outTag proxy tag <*> bMaybe
  {-# INLINE postcompose #-}
  
  -- | A 'ClSF' can always be precomposed onto an 'SN' if the clocks match on the input.
  precompose clsf sn = SN $ reader $ \initialTime ->
    let
      proxy = toClockProxy sn
     in
      proc (time, tag, aMaybe) -> do
        bMaybe <- mapMaybeS $ eraseClockClSF (inProxy proxy) initialTime clsf -< (time,,) <$> inTag proxy tag <*> aMaybe
        eraseClockSN initialTime sn -< (time, tag, bMaybe)
  {-# INLINE precompose #-}
  
  {- | Data can be looped back to the beginning of an 'SN',
    but it must be resampled since the 'Out' and 'In' clocks are generally different.
  -}
  feedbackSN ResamplingBuffer {buffer, put, get} sn = SN $ reader $ \initialTime ->
    let
      proxy = toClockProxy sn
     in
      feedback buffer $ proc ((time, tag, aMaybe), buf) -> do
        (cMaybe, buf') <- case inTag proxy tag of
          Nothing -> do
            returnA -< (Nothing, buf)
          Just tagIn -> do
            timeInfo <- genTimeInfo (inProxy proxy) initialTime -< (time, tagIn)
            Result buf' c <- arrM $ uncurry get -< (timeInfo, buf)
            returnA -< (Just c, buf')
        bdMaybe <- eraseClockSN initialTime sn -< (time, tag, (,) <$> aMaybe <*> cMaybe)
        case (,) <$> outTag proxy tag <*> bdMaybe of
          Nothing -> do
            returnA -< (Nothing, buf')
          Just (tagOut, (b, d)) -> do
            timeInfo <- genTimeInfo (outProxy proxy) initialTime -< (time, tagOut)
            buf'' <- arrM $ uncurry $ uncurry put -< ((timeInfo, d), buf')
            returnA -< (Just b, buf'')
  {-# INLINE feedbackSN #-}
  
  -- | Bypass the signal network by forwarding data in parallel through a 'ResamplingBuffer'.
  firstResampling sn buf = SN $ reader $ \initialTime ->
    let
      proxy = toClockProxy sn
     in
      proc (time, tag, acMaybe) -> do
        bMaybe <- eraseClockSN initialTime sn -< (time, tag, fst <$> acMaybe)
        let
          resBufInput = case (inTag proxy tag, outTag proxy tag, snd <$> acMaybe) of
            (Just tagIn, _, Just c) -> Just $ Left (time, tagIn, c)
            (_, Just tagOut, _) -> Just $ Right (time, tagOut)
            _ -> Nothing
        dMaybe <- mapMaybeS $ eraseClockResBuf (inProxy proxy) (outProxy proxy) initialTime buf -< resBufInput
        returnA -< (,) <$> bMaybe <*> join dMaybe
  {-# INLINE firstResampling #-}
  