content: -- |
  -- Module      : FRP.Yampa.Hybrid
  -- Copyright   : (c) Ivan Perez, 2014-2022
  --               (c) George Giorgidze, 2007-2012
  --               (c) Henrik Nilsson, 2005-2006
  --               (c) Antony Courtney and Henrik Nilsson, Yale University, 2003-2004
  -- License     : BSD-style (see the LICENSE file in the distribution)
  --
  -- Maintainer  : ivan.perez@keera.co.uk
  -- Stability   : provisional
  -- Portability : non-portable (GHC extensions)
  --
  -- Discrete to continuous-time signal functions.
  module FRP.Yampa.Hybrid
      (
        -- * Wave-form generation
        hold
      , dHold
      , trackAndHold
      , dTrackAndHold
  
        -- * Accumulators
      , accum
      , accumHold
      , dAccumHold
      , accumBy
      , accumHoldBy
      , dAccumHoldBy
      , accumFilter
      )
    where
  
  -- External imports
  import Control.Arrow (arr, (>>>))
  
  -- Internal imports
  import FRP.Yampa.Delays       (iPre)
  import FRP.Yampa.Event        (Event (..))
  import FRP.Yampa.InternalCore (SF, epPrim)
  
  -- * Wave-form generation
  
  -- | Zero-order hold.
  --
  -- Converts a discrete-time signal into a continuous-time signal, by holding the
  -- last value until it changes in the input signal. The given parameter may be
  -- used for time zero, and until the first event occurs in the input signal, so
  -- hold is always well-initialized.
  --
  -- >>> embed (hold 1) (deltaEncode 0.1 [NoEvent, NoEvent, Event 2, NoEvent, Event 3, NoEvent])
  -- [1,1,2,2,3,3]
  hold :: a -> SF (Event a) a
  hold aInit = epPrim f () aInit
    where
      f _ a = ((), a, a)
  
  -- | Zero-order hold with a delay.
  --
  -- Converts a discrete-time signal into a continuous-time signal, by holding the
  -- last value until it changes in the input signal. The given parameter is used
  -- for time zero (until the first event occurs in the input signal), so 'dHold'
  -- shifts the discrete input by an infinitesimal delay.
  --
  -- >>> embed (dHold 1) (deltaEncode 0.1 [NoEvent, NoEvent, Event 2, NoEvent, Event 3, NoEvent])
  -- [1,1,1,2,2,3]
  dHold :: a -> SF (Event a) a
  dHold a0 = hold a0 >>> iPre a0
  
  -- | Tracks input signal when available, holding the last value when the input
  -- is 'Nothing'.
  --
  -- This behaves similarly to 'hold', but there is a conceptual difference, as it
  -- takes a signal of input @Maybe a@ (for some @a@) and not @Event@.
  --
  -- >>> embed (trackAndHold 1) (deltaEncode 0.1 [Nothing, Nothing, Just 2, Nothing, Just 3, Nothing])
  -- [1,1,2,2,3,3]
  trackAndHold :: a -> SF (Maybe a) a
  trackAndHold aInit = arr (maybe NoEvent Event) >>> hold aInit
  
  -- | Tracks input signal when available, holding the last value when the input
  -- is 'Nothing', with a delay.
  --
  -- This behaves similarly to 'hold', but there is a conceptual difference, as it
  -- takes a signal of input @Maybe a@ (for some @a@) and not @Event@.
  --
  -- >>> embed (dTrackAndHold 1) (deltaEncode 0.1 [Nothing, Nothing, Just 2, Nothing, Just 3, Nothing])
  -- [1,1,1,2,2,3]
  dTrackAndHold :: a -> SF (Maybe a) a
  dTrackAndHold aInit = trackAndHold aInit >>> iPre aInit
  
  -- * Accumulators
  
  -- | Given an initial value in an accumulator, it returns a signal function that
  -- processes an event carrying transformation functions. Every time an 'Event'
  -- is received, the function inside it is applied to the accumulator, whose new
  -- value is outputted in an 'Event'.
  accum :: a -> SF (Event (a -> a)) (Event a)
  accum aInit = epPrim f aInit NoEvent
    where
      f a g = (a', Event a', NoEvent) -- Accumulator, output if Event, output if
                                      -- no event
        where
          a' = g a
  
  -- | Zero-order hold accumulator (always produces the last outputted value until
  -- an event arrives).
  accumHold :: a -> SF (Event (a -> a)) a
  accumHold aInit = epPrim f aInit aInit
    where
      f a g = (a', a', a') -- Accumulator, output if Event, output if no event
        where
          a' = g a
  
  -- | Zero-order hold accumulator with delayed initialization (always produces
  -- the last outputted value until an event arrives, but the very initial output
  -- is always the given accumulator).
  dAccumHold :: a -> SF (Event (a -> a)) a
  dAccumHold aInit = accumHold aInit >>> iPre aInit
  
  -- | Accumulator parameterized by the accumulation function.
  accumBy :: (b -> a -> b) -> b -> SF (Event a) (Event b)
  accumBy g bInit = epPrim f bInit NoEvent
    where
      f b a = (b', Event b', NoEvent)
        where
          b' = g b a
  
  -- | Zero-order hold accumulator parameterized by the accumulation function.
  accumHoldBy :: (b -> a -> b) -> b -> SF (Event a) b
  accumHoldBy g bInit = epPrim f bInit bInit
    where
      f b a = (b', b', b')
        where
          b' = g b a
  
  -- | Zero-order hold accumulator parameterized by the accumulation function with
  -- delayed initialization (initial output sample is always the given
  -- accumulator).
  dAccumHoldBy :: (b -> a -> b) -> b -> SF (Event a) b
  dAccumHoldBy f aInit = accumHoldBy f aInit >>> iPre aInit
  
  -- | Accumulator parameterized by the accumulator function with filtering,
  -- possibly discarding some of the input events based on whether the second
  -- component of the result of applying the accumulation function is 'Nothing' or
  -- 'Just' x for some x.
  accumFilter :: (c -> a -> (c, Maybe b)) -> c -> SF (Event a) (Event b)
  accumFilter g cInit = epPrim f cInit NoEvent
    where
      f c a = case g c a of
                (c', Nothing) -> (c', NoEvent, NoEvent)
                (c', Just b)  -> (c', Event b, NoEvent)
  