module Data.Metrics.MovingAverage.ExponentiallyWeighted (
  ExponentiallyWeightedMovingAverage,
  new1MinuteMovingAverage,
  new5MinuteMovingAverage,
  new15MinuteMovingAverage,
  movingAverage,
  clear,
  rate,
  empty,
  update,
  tick
) where
import Control.Lens
import Control.Lens.TH
import Control.Monad.Primitive
import qualified Data.Metrics.MovingAverage as MA
import Data.Metrics.Types (Minutes)
data ExponentiallyWeightedMovingAverage = ExponentiallyWeightedMovingAverage
  { _ewmaUncounted :: !Double
  , _ewmaCurrentRate :: !Double
  , _ewmaInitialized :: !Bool
  , _ewmaInterval :: !Double
  , _ewmaAlpha :: !Double
  } deriving (Show)
makeFields ''ExponentiallyWeightedMovingAverage
makeAlpha :: Double -> Minutes -> Double
makeAlpha i m = 1  exp (negate i / 60 / fromIntegral m)
new1MinuteMovingAverage :: MA.MovingAverage
new1MinuteMovingAverage = movingAverage 5 1
new5MinuteMovingAverage :: MA.MovingAverage
new5MinuteMovingAverage = movingAverage 5 5
new15MinuteMovingAverage :: MA.MovingAverage
new15MinuteMovingAverage = movingAverage 5 15
movingAverage :: Double -> Minutes -> MA.MovingAverage
movingAverage i m = MA.MovingAverage
  { MA.movingAverageClear = clear
  , MA.movingAverageUpdate = update
  , MA.movingAverageTick = tick
  , MA.movingAverageRate = rate
  , MA.movingAverageState = empty i m
  }
clear :: ExponentiallyWeightedMovingAverage -> ExponentiallyWeightedMovingAverage
clear = (initialized .~ False) . (currentRate .~ 0) . (uncounted .~ 0)
rate :: ExponentiallyWeightedMovingAverage -> Double
rate e = (e ^. currentRate) * (e ^. interval)
empty :: Double 
  -> Minutes 
  -> ExponentiallyWeightedMovingAverage
empty i m = ExponentiallyWeightedMovingAverage 0 0 False i $ makeAlpha i m
update :: Double -> ExponentiallyWeightedMovingAverage -> ExponentiallyWeightedMovingAverage
update = (uncounted +~)
tick :: ExponentiallyWeightedMovingAverage -> ExponentiallyWeightedMovingAverage
tick e = uncounted .~ 0 $ initialized .~ True $ updateRate e
  where
    instantRate = (e ^. uncounted) / (e ^. interval)
    updateRate a = if a ^. initialized
      then currentRate +~ ((a ^. alpha) * (instantRate  a ^. currentRate)) $ a
      else currentRate .~ instantRate $ a