-- |
-- Module:     FRP.NetWire.Analyze
-- Copyright:  (c) 2011 Ertugrul Soeylemez
-- License:    BSD3
-- Maintainer: Ertugrul Soeylemez <es@ertes.de>
--
-- Signal analysis.

module FRP.NetWire.Analyze
    ( -- * Changes
      diff,

      -- * Statistics
      -- ** Average
      avg,
      avgAll,
      avgFps,

      -- ** Peak
      highPeak,
      lowPeak,
      peakBy,
    )
    where

import qualified Data.Vector.Unboxed.Mutable as V
import Control.DeepSeq
import Data.Vector.Unboxed.Mutable (IOVector, Unbox)
import FRP.NetWire.Wire


-- | Calculate the average of the signal over the given number of last
-- samples.  This wire has O(n) space complexity and O(1) time
-- complexity.
--
-- If you need an average over all samples ever produced, consider using
-- 'avgAll' instead.

avg :: forall v. (Fractional v, NFData v, Unbox v) => Int -> Wire v v
avg n =
    mkGen $ \_ x -> do
        samples <- V.replicate n (x/d)
        return (Just x, avg' samples x 0)

    where
    avg' :: IOVector v -> v -> Int -> Wire v v
    avg' samples s' cur' =
        mkGen $ \_ ((/d) -> x) -> do
            let cur = let cur = succ cur' in if cur >= n then 0 else cur
            x' <- V.read samples cur
            V.write samples cur x
            let s = s' - x' + x
            s `deepseq` return (Just s, avg' samples s cur)

    d :: v
    d = realToFrac n


-- | Calculate the average of the signal over all samples.
--
-- Please note that somewhat surprisingly this wire runs in constant
-- space and is generally faster than 'avg', but most applications will
-- benefit from averages over only the last few samples.

avgAll :: forall v. (Fractional v, NFData v) => Wire v v
avgAll = mkGen $ \_ x -> return (Just x, avgAll' 1 x)
    where
    avgAll' :: v -> v -> Wire v v
    avgAll' n' a' =
        mkGen $ \_ x ->
            let n = n' + 1
                a = a' - a'/n + x/n in
            n `deepseq` a `deepseq` return (Just a, avgAll' n a)


-- | Calculate the average number of frames per virtual second for the
-- last given number of frames.
--
-- Please note that this wire uses the clock, which you give the network
-- using the stepping functions in "FRP.NetWire.Session".  If this clock
-- doesn't represent real time, then the output of this wire won't
-- either.

avgFps :: forall a. Int -> Wire a Double
avgFps = avgFps' . avg
    where
    avgFps' :: Wire Double Double -> Wire a Double
    avgFps' w' =
        mkGen $ \ws@(wsDTime -> dt) _ -> do
            (ma, w) <- toGen w' ws dt
            return (fmap recip ma, avgFps' w)


-- | Emits an event, whenever the input signal changes.  The event
-- contains the last input value and the time elapsed since the last
-- change.

diff :: forall a. Eq a => Wire a (Event (a, Time))
diff =
    mkGen $ \(wsDTime -> dt) x' ->
        return (Just Nothing, diff' dt x')

    where
    diff' :: Time -> a -> Wire a (Event (a, Time))
    diff' t' x' =
        mkGen $ \(wsDTime -> dt) x ->
            let t = t' + dt in
            if x' == x
              then return (Just Nothing, diff' t x')
              else return (Just (Just (x', t)), diff' 0 x)


-- | Returh the high peak.

highPeak :: (NFData a, Ord a) => Wire a a
highPeak = peakBy compare


-- | Return the low peak.

lowPeak :: (NFData a, Ord a) => Wire a a
lowPeak = peakBy (flip compare)


-- | Return the high peak with the given comparison function.

peakBy :: forall a. NFData a => (a -> a -> Ordering) -> Wire a a
peakBy comp = mkGen $ \_ x -> return (Just x, peakBy' x)
    where
    peakBy' :: a -> Wire a a
    peakBy' p' =
        mkGen $ \_ x -> do
            let p = if comp x p' == GT then x else p'
            p `deepseq` return (Just p, peakBy' p)