content: -- |
  -- Module:     FRP.Netwire.Analyze
  -- Copyright:  (c) 2013 Ertugrul Soeylemez
  -- License:    BSD3
  -- Maintainer: Ertugrul Soeylemez <es@ertes.de>
  
  module FRP.Netwire.Analyze
      ( -- * Linear graphs
        lAvg,
        lGraph,
        lGraphN,
  
        -- * Staircase graphs
        sAvg,
        sGraph,
        sGraphN,
  
        -- * Peaks
        highPeak,
        highPeakBy,
        lowPeak,
        lowPeakBy,
  
        -- * Debug
        avgFps,
        framerate
      )
      where
  
  import Control.Wire
  import qualified Data.Foldable as F
  import qualified Data.Sequence as Seq
  import qualified FRP.Netwire.Utils.Timeline as Tl
  import Prelude hiding ((.), id)
  
  
  -- | Average framerate over the last given number of samples.  One
  -- important thing to note is that the value of this wire will generally
  -- disagree with 'sAvg' composed with 'framerate'.  This is expected,
  -- because this wire simply calculates the arithmetic mean, whereas
  -- 'sAvg' will actually integrate the framerate graph.
  --
  -- Note:  This wire is for debugging purposes only, because it exposes
  -- discrete time.  Do not taint your application with discrete time.
  --
  -- * Complexity: O(n) time and space wrt number of samples.
  
  avgFps ::
      (RealFloat b, HasTime t s)
      => Int  -- ^ Number of samples.
      -> Wire s e m a b
  avgFps int | int < 1 = error "avgFps: Non-positive number of samples"
  avgFps int = loop Seq.empty
      where
      intf = fromIntegral int
      afps = (/ intf) . F.foldl' (+) 0
  
      loop ss' =
          mkSF $ \ds _ ->
              let fps = recip . realToFrac . dtime $ ds
                  ss  = Seq.take int (fps Seq.<| ss')
              in if isInfinite fps
                   then (afps ss', loop ss')
                   else ss `seq` (afps ss, loop ss)
  
  
  -- | Current framerate.
  --
  -- Note:  This wire is for debugging purposes only, because it exposes
  -- discrete time.  Do not taint your application with discrete time.
  --
  -- * Inhibits: when the clock stopped ticking.
  
  framerate ::
      (Eq b, Fractional b, HasTime t s, Monoid e)
      => Wire s e m a b
  framerate =
      mkPure $ \ds _ ->
          let dt = realToFrac (dtime ds)
          in (if dt == 0 then Left mempty else Right (recip dt), framerate)
  
  
  -- | High peak.
  --
  -- * Depends: now.
  
  highPeak :: (Ord a) => Wire s e m a a
  highPeak = highPeakBy compare
  
  
  -- | High peak with respect to the given comparison function.
  --
  -- * Depends: now.
  
  highPeakBy :: (a -> a -> Ordering) -> Wire s e m a a
  highPeakBy = peakBy GT
  
  
  -- | Calculate the average of the signal over the given interval (from
  -- now).  This is done by calculating the integral of the corresponding
  -- linearly interpolated graph and dividing it by the interval length.
  -- See 'Tl.linAvg' for details.
  --
  -- Linear interpolation can be slow.  If you don't need it, you can use
  -- the staircase variant 'sAvg'.
  --
  -- Example: @lAvg 2@
  --
  -- * Complexity: O(s) space, O(s) time wrt number of samples in the
  --   interval.
  --
  -- * Depends: now.
  
  lAvg ::
      (Fractional a, Fractional t, HasTime t s)
      => t    -- ^ Interval size.
      -> Wire s e m a a
  lAvg int =
      mkSF $ \ds x ->
          let t = dtime ds in
          (x, loop t (Tl.singleton t x))
  
      where
      loop t' tl' =
          mkSF $ \ds x ->
              let t  = t' + dtime ds
                  t0 = t - int
                  tl = Tl.linCutL t0 (Tl.insert t x tl')
                  a  = Tl.linAvg t0 t tl
              in (a, loop t tl)
  
  
  -- | Produce a linearly interpolated graph for the given points in time,
  -- where the magnitudes of the points are distances from /now/.
  --
  -- Linear interpolation can be slow.  If you don't need it, you can use
  -- the faster staircase variant 'sGraph'.
  --
  -- Example: @lGraph [0, 1, 2]@ will output the interpolated inputs at
  -- /now/, one second before now and two seconds before now.
  --
  -- * Complexity: O(s) space, O(n * log s) time, where s = number of
  --   samples in the interval, n = number of requested data points.
  --
  -- * Depends: now.
  
  lGraph ::
      (Fractional a, Fractional t, HasTime t s)
      => [t]  -- ^ Data points to produce.
      -> Wire s e m a [a]
  lGraph qts =
      mkSF $ \ds x ->
          let t = dtime ds in
          (x <$ qts, loop t (Tl.singleton t x))
  
      where
      earliest = maximum (map abs qts)
  
      loop t' tl' =
          mkSF $ \ds x ->
              let t  = t' + dtime ds
                  tl = Tl.linCutL (t - earliest) (Tl.insert t x tl')
                  ps = map (\qt -> Tl.linLookup (t - abs qt) tl) qts
              in (ps, loop t tl)
  
  
  -- | Graph the given interval from now with the given number of evenly
  -- distributed points in time.  Convenience interface to 'lGraph'.
  --
  -- Linear interpolation can be slow.  If you don't need it, you can use
  -- the faster staircase variant 'sGraphN'.
  --
  -- * Complexity: O(s) space, O(n * log s) time, where s = number of
  --   samples in the interval, n = number of requested data points.
  --
  -- * Depends: now.
  
  lGraphN ::
      (Fractional a, Fractional t, HasTime t s)
      => t    -- ^ Interval to graph from now.
      -> Int  -- ^ Number of data points to produce.
      -> Wire s e m a [a]
  lGraphN int n
      | int <= 0 = error "lGraphN: Non-positive interval"
      | n <= 0   = error "lGraphN: Non-positive number of data points"
  lGraphN int n =
      let n1   = n - 1
          f qt = realToFrac int * fromIntegral qt / fromIntegral n1
      in lGraph (map f [0..n1])
  
  
  -- | Low peak.
  --
  -- * Depends: now.
  
  lowPeak :: (Ord a) => Wire s e m a a
  lowPeak = lowPeakBy compare
  
  
  -- | Low peak with respect to the given comparison function.
  --
  -- * Depends: now.
  
  lowPeakBy :: (a -> a -> Ordering) -> Wire s e m a a
  lowPeakBy = peakBy LT
  
  
  -- | Given peak with respect to the given comparison function.
  
  peakBy ::
      (Eq o)
      => o  -- ^ This ordering means the first argument is larger.
      -> (a -> a -> o)  -- ^ Compare two elements.
      -> Wire s e m a a
  peakBy o comp = mkSFN $ \x -> (x, loop x)
      where
      loop x' =
          mkSFN $ \x ->
              id &&& loop $
              if comp x x' == o then x else x'
  
  
  -- | Calculate the average of the signal over the given interval (from
  -- now).  This is done by calculating the integral of the corresponding
  -- staircase graph and dividing it by the interval length.  See
  -- 'Tl.scAvg' for details.
  --
  -- See also 'lAvg'.
  --
  -- Example: @sAvg 2@
  --
  -- * Complexity: O(s) space, O(s) time wrt number of samples in the
  --   interval.
  --
  -- * Depends: now.
  
  sAvg ::
      (Fractional a, Fractional t, HasTime t s)
      => t    -- ^ Interval size.
      -> Wire s e m a a
  sAvg int =
      mkSF $ \ds x ->
          let t = dtime ds in
          (x, loop t (Tl.singleton t x))
  
      where
      loop t' tl' =
          mkSF $ \ds x ->
              let t  = t' + dtime ds
                  t0 = t - int
                  tl = Tl.scCutL t0 (Tl.insert t x tl')
                  a  = Tl.scAvg t0 t tl
              in (a, loop t tl)
  
  
  -- | Produce a staircase graph for the given points in time, where the
  -- magnitudes of the points are distances from /now/.
  --
  -- See also 'lGraph'.
  --
  -- Example: @sGraph [0, 1, 2]@ will output the inputs at /now/, one
  -- second before now and two seconds before now.
  --
  -- * Complexity: O(s) space, O(n * log s) time, where s = number of
  --   samples in the interval, n = number of requested data points.
  --
  -- * Depends: now.
  
  sGraph ::
      (Fractional t, HasTime t s)
      => [t]  -- ^ Data points to produce.
      -> Wire s e m a [a]
  sGraph qts =
      mkSF $ \ds x ->
          let t = dtime ds in
          (x <$ qts, loop t (Tl.singleton t x))
  
      where
      earliest = maximum (map abs qts)
  
      loop t' tl' =
          mkSF $ \ds x ->
              let t  = t' + dtime ds
                  tl = Tl.scCutL (t - earliest) (Tl.insert t x tl')
                  ps = map (\qt -> Tl.scLookup (t - abs qt) tl) qts
              in (ps, loop t tl)
  
  
  -- | Graph the given interval from now with the given number of evenly
  -- distributed points in time.  Convenience interface to 'sGraph'.
  --
  -- See also 'lGraphN'.
  --
  -- * Complexity: O(s) space, O(n * log s) time, where s = number of
  --   samples in the interval, n = number of requested data points.
  --
  -- * Depends: now.
  
  sGraphN ::
      (Fractional t, HasTime t s)
      => t    -- ^ Interval to graph from now.
      -> Int  -- ^ Number of data points to produce.
      -> Wire s e m a [a]
  sGraphN int n
      | int <= 0 = error "sGraphN: Non-positive interval"
      | n <= 0   = error "sGraphN: Non-positive number of data points"
  sGraphN int n =
      let n1   = n - 1
          f qt = realToFrac int * fromIntegral qt / fromIntegral n1
      in sGraph (map f [0..n1])
  