-- | 'Conduit's that remove or set the sequence numbers and time stamps in
--   'Stream's. The functions in this module lift the functions in 'SyncStream's
--   to conduits.
module Data.MediaBus.Conduit.SyncStream
  ( assumeSynchronizedC
  , setSequenceNumberAndTimestampC
  , convertTimestampC
  , setTimestampFromDurationsC
  , removeTimestampC
  ) where

import Control.Monad.State.Strict
import Control.DeepSeq (NFData)
import Data.Conduit
import Data.Conduit.Lift
import Data.MediaBus.Basics.Ticks
import Data.MediaBus.Conduit.Stream
import Data.MediaBus.Media.Stream
import Data.MediaBus.Media.SyncStream

-- * Combined Sequence number and timestamp calculation

-- | Assign sequence numbers and timestamps to the 'Frame's in a 'SyncStream',
-- starting both the sequence number and timestamp at @0@.
-- This functions is /strict/ and uses 'setSequenceNumberAndTimestamp'
-- under the hood.
-- Inorder to calculate only the 'timestamp' of a stream use
-- 'setTimestampFromDurationsC'.
setSequenceNumberAndTimestampC
  :: (Monad m, KnownRate r, HasDuration d, Num s, CanBeTicks r t)
  => ConduitM (SyncStream i p d) (Stream i s (Ticks r t) p d) m ()
setSequenceNumberAndTimestampC =
  evalStateC
    (0, 0)
    (awaitForever
       (\sIn -> do
          sOut <- lift (state (setSequenceNumberAndTimestamp sIn))
          yield sOut))

-- | Remove the sequence numbers and time stamps from a 'Stream'.
-- It's much more explicit to use a 'SyncStream' instead of a 'Stream'.
-- For example, when a library function aggregates 'Frame's but doesn't regard
-- the sequence numbers and time stamps, using 'SyncStream' indicates to users
-- of that library, that the function does not handle any gaps and/or out of order
-- packages or discrepancies in the time stamps and e.g. frame durations.
--
-- The user then knows, that she has to add functions to that conduit to accomodate
-- for that.
--
-- This functions is /strict/ and uses 'assumeSynchronized'
-- under the hood.
assumeSynchronizedC
  :: (Monad m, KnownRate r, HasDuration d, Num s, CanBeTicks r t)
  => ConduitM (SyncStream i p d) (Stream i s (Ticks r t) p d) m ()
assumeSynchronizedC =
  evalStateC
    (0, 0)
    (awaitForever
       (\sIn -> do
          sOut <- lift (state (setSequenceNumberAndTimestamp sIn))
          yield sOut))

-- * Timestamp calculation and conversion

-- | Set the timestamp of each element in the conduit.
-- The timestamp of each element is calculated from the sum of the durations of
-- the previous elements and the start time stamp @t0@.
--
-- The input elements must be instances of 'HasTimestamp' but with the important
-- condition, that the input timestamp is always /unit/ i.e. @()@.
-- This prevents /meaningful/ timestamps from being overwritten.
--
-- Use 'removeTimestampC' to explicitly remove a timestamp.
setTimestampFromDurationsC
  :: forall m r t a.
     ( Monad m
     , CanBeTicks r t
     , HasDuration a
     , HasTimestamp a
     , GetTimestamp a ~ ()
     )
  => Ticks r t -> Conduit a m (SetTimestamp a (Ticks r t))
setTimestampFromDurationsC t0 = evalStateC t0 (awaitForever go)
  where
    go !sb = lift (state (setTimestampFromDurations sb)) >>= yield

-- | Explicitly remove a timestamp, by setting the timestamp to @()@.
removeTimestampC
  :: (Monad m, HasTimestamp a)
  => Conduit a m (SetTimestamp a ())
removeTimestampC = awaitForever (yield . removeTimestamp)

-- | Recalculate all timestamps in a 'Stream'. This function is strict in its arguments.
convertTimestampC
  :: forall proxy0 proxy1 m r t r' t' i s c p.
     (NFData t, NFData t', CanBeTicks r t, CanBeTicks r' t', Monad m, NFData t')
  => proxy0 '( r, t)
  -> proxy1 '( r', t')
  -> Conduit (Stream i s (Ticks r t) p c) m (Stream i s (Ticks r' t') p c)
convertTimestampC _ _ = mapTimestampC' convertTicks