content: -- |
  -- Copyright  : (c) Ivan Perez, 2017-2023
  -- License    : BSD3
  -- Maintainer : ivan.perez@keera.co.uk
  --
  -- Streams and stream manipulation API.
  --
  -- The evaluation of Dunai MSFs, especially for testing purposes, needs the
  -- generation of suitable input streams.
  --
  -- While some streams can be generated randomly using QuickCheck, it is
  -- sometimes useful to be able to preprend or adapt an input stream. It is also
  -- useful to debug programs when you have recorded input streams using Haskell
  -- Titan.
  --
  -- This module defines types for input streams, as well as an API to create,
  -- examine and combine streams. It also provides evaluation functions that are
  -- needed to apply an MSF to a stream and obtain an output stream and a
  -- continuation MSF.
  module FRP.Dunai.Stream where
  
  -- External imports
  import Control.Monad.Trans.MSF.Reader          (ReaderT, readerS, runReaderS)
  import Data.MonadicStreamFunction              (MSF)
  import Data.MonadicStreamFunction.InternalCore (unMSF)
  
  -- * Types
  
  -- | A stream of samples, with their sampling times.
  type SignalSampleStream a = SampleStream (DTime, a)
  
  -- | A stream of samples, with no sampling time.
  type SampleStream a = [a]
  
  -- | DTime is the time type for lengths of sample intervals. Conceptually,
  -- DTime = R+ = { x in R | x > 0 }.
  type DTime = Double
  
  -- ** Creation
  
  -- | Group a series of samples with a series of time deltas.
  --
  -- The first sample will have no delta. Unused samples and deltas will be
  -- dropped.
  groupDeltas :: [a] -> [DTime] -> SignalSampleStream a
  groupDeltas xs ds = zip (0:ds) xs
  
  -- * Obtain samples
  
  -- | Turn a stream with sampling times into a list of values.
  samples :: SignalSampleStream a -> [a]
  samples = map snd
  
  -- | Return the first sample in a signal sample stream.
  firstSample :: SignalSampleStream a -> a
  firstSample = head . samples
  
  -- | Return the last sample in a signal sample stream.
  lastSample :: SignalSampleStream a -> a
  lastSample = last . samples
  
  -- * Stream manipulation
  
  -- ** Merging
  
  -- | Merge two streams, using an auxiliary function to merge samples that fall
  -- at the exact same sampling time.
  sMerge :: (a -> a -> a)
         -> SignalSampleStream a
         -> SignalSampleStream a
         -> SignalSampleStream a
  sMerge _ []                xs2               = xs2
  sMerge _ xs1               []                = xs1
  sMerge f ((dt1, x1) : xs1) ((dt2, x2) : xs2)
    | dt1 == dt2 = (dt1, f x1 x2) : sMerge f xs1 xs2
    | dt1 <  dt2 = (dt1, x1) : sMerge f xs1 ((dt2 - dt1, x2) : xs2)
    | otherwise  = (dt2, x2) : sMerge f ((dt1 - dt2, x1) : xs1) xs2
  
  -- ** Concatenating
  
  -- | Concatenate two sample streams, separating them by a given time delta.
  sConcat :: SignalSampleStream a -> SignalSampleStream a -> SignalSampleStream a
  sConcat xs1 xs2 = xs1 ++ xs2
  
  -- ** Refining
  
  -- | Refine a signal sample stream by establishing the maximum time delta.
  --
  -- If two samples are separated by a time delta bigger than the given max DT,
  -- the former is replicated as many times as necessary.
  sRefine :: DTime -> a -> SignalSampleStream a -> SignalSampleStream a
  sRefine _     _  []             = []
  sRefine maxDT a0 ((dt, a) : as)
    | dt > maxDT = (maxDT, a0) : sRefine maxDT a0 ((dt - maxDT, a) : as)
    | otherwise  = (dt, a) : sRefine maxDT a as
  
  -- | Refine a stream by establishing the maximum time delta.
  --
  -- If two samples are separated by a time delta bigger than the given max DT,
  -- the auxiliary interpolation function is used to determine the intermediate
  -- sample.
  refineWith :: (a -> a -> a)
             -> DTime
             -> a
             -> SignalSampleStream a
             -> SignalSampleStream a
  refineWith _           _     _  []             = []
  refineWith interpolate maxDT a0 ((dt, a) : as)
      | dt > maxDT
      = (maxDT, interpolate a0 a) :
          refineWith interpolate maxDT a' ((dt - maxDT, a) : as)
      | otherwise
      = (dt, a) : refineWith interpolate maxDT a as
    where
      a' = interpolate a0 a
  
  -- ** Clipping (dropping samples)
  
  -- | Clip a signal sample stream at a given number of samples.
  sClipAfterFrame :: Int -> SignalSampleStream a -> SignalSampleStream a
  sClipAfterFrame = take
  
  -- | Clip a signal sample stream after a certain (non-zero) time.
  sClipAfterTime :: DTime -> SignalSampleStream a -> SignalSampleStream a
  sClipAfterTime _  []              = []
  sClipAfterTime dt ((dt', x) : xs)
    | dt < dt'  = []
    | otherwise = (dt', x) : sClipAfterTime (dt - dt') xs
  
  -- | Drop the first n samples of a signal sample stream. The time deltas are
  -- not re-calculated.
  sClipBeforeFrame :: Int -> SignalSampleStream a -> SignalSampleStream a
  sClipBeforeFrame 0 xs@(_:_) = xs
  sClipBeforeFrame _ xs@[_]   = xs
  sClipBeforeFrame n xs       = sClipBeforeFrame (n - 1) xs
  
  -- | Drop the first samples of a signal sample stream up to a given time. The
  -- time deltas are not re-calculated to match the original stream.
  sClipBeforeTime :: DTime -> SignalSampleStream a -> SignalSampleStream a
  sClipBeforeTime dt xs
      | dt <= 0        = xs
      | length xs == 1 = xs
      | dt < dt'       = (dt' - dt, x') : xs'
      | otherwise      = sClipBeforeTime (dt - dt') ((0, x') : xs')
    where
      (_ : (dt', x') : xs') = xs
  
  -- | Evaluate an SF with a 'SignalSampleStream', obtaining an output stream and
  -- a continuation.
  --
  -- You should never use this for actual execution in your applications, only
  -- for testing.
  evalSF :: Monad m
         => MSF (ReaderT DTime m) a b
         -> SignalSampleStream a
         -> m (SampleStream b, MSF (ReaderT DTime m) a b)
  evalSF fsf as = do
      (ss, msf') <- evalMSF msf'' as
      return (ss, readerS msf')
    where
      msf'' = runReaderS fsf
  
  -- | Evaluate an MSF with a 'SampleStream', obtaining an output stream and a
  -- continuation.
  --
  -- You should never use this for actual execution in your applications, only
  -- for testing.
  evalMSF :: Monad m
          => MSF m a b
          -> SampleStream a
          -> m (SampleStream b, MSF m a b)
  evalMSF fsf []     = return ([], fsf)
  evalMSF fsf (a:as) = do
    (b, fsf')   <- unMSF fsf a
    (bs, fsf'') <- evalMSF fsf' as
    let outputStrm = b : bs
    return (outputStrm, fsf'')
  