{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# OPTIONS -Wall #-}

module Data.ZoomCache.PCM.Internal (
    -- * Functions
      readSummaryPCM
    , fromSummaryPCM
    , initSummaryPCMBounded
    , mkSummaryPCM
    , appendSummaryPCM
    , updateSummaryPCM
) where

import Blaze.ByteString.Builder
import Control.Applicative ((<$>))
import Control.Monad (replicateM)
import Data.Iteratee (Iteratee)
import qualified Data.Iteratee as I
import qualified Data.ListLike as LL
import Data.Monoid
import Data.Word

import Data.ZoomCache.Codec
import Data.ZoomCache.PCM.Types

----------------------------------------------------------------------

readSummaryPCM :: (I.Nullable s, LL.ListLike s Word8,
                   Functor m, Monad m,
                   ZoomPCM a)
               => Iteratee s m (SummaryData (PCM a))
readSummaryPCM = do
    [mn,mx] <- replicateM 2 (unPCM <$> readRaw)
    [avg,rms] <- replicateM 2 readDouble64be
    return (pcmMkSummary mn mx avg rms)
{-# INLINABLE readSummaryPCM #-}

fromSummaryPCM :: ZoomPCM a
               => SummaryData (PCM a) -> Builder
fromSummaryPCM s = mconcat $
    map pcmFromRaw [pcmMin s, pcmMax s] ++
    map fromDouble [pcmAvg s, pcmRMS s]
{-# INLINABLE fromSummaryPCM #-}

initSummaryPCMBounded :: (Bounded a, ZoomPCM a)
                      => TimeStamp -> SummaryWork (PCM a)
initSummaryPCMBounded entry = pcmMkSummaryWork entry maxBound minBound 0.0 0.0
{-# INLINEABLE initSummaryPCMBounded #-}

mkSummaryPCM :: ZoomPCM a
             => TimeStampDiff -> SummaryWork (PCM a)
             -> SummaryData (PCM a)
mkSummaryPCM (TSDiff dur) sw =
    pcmMkSummary (pcmWorkMin sw) (pcmWorkMax sw)
                 (pcmWorkSum sw / fromIntegral dur)
                 (sqrt $ (pcmWorkSumSq sw) / fromIntegral dur)
{-# INLINEABLE mkSummaryPCM #-}

appendSummaryPCM :: ZoomPCM a
                 => TimeStampDiff -> SummaryData (PCM a)
                 -> TimeStampDiff -> SummaryData (PCM a)
                 -> SummaryData (PCM a)
appendSummaryPCM (TSDiff dur1) s1 (TSDiff dur2) s2 = pcmMkSummary
    (min (pcmMin s1) (pcmMin s2))
    (max (pcmMax s1) (pcmMax s2))
    (((pcmAvg s1 * fromIntegral dur1) + (pcmAvg s2 * fromIntegral dur2)) / fromIntegral durSum)
    (sqrt $ ((pcmRMS s1 * pcmRMS s1 * fromIntegral dur1) +
             (pcmRMS s2 * pcmRMS s2 * fromIntegral dur2)) /
            fromIntegral durSum)
    where
        !durSum = dur1 + dur2
{-# INLINEABLE appendSummaryPCM #-}

updateSummaryPCM :: ZoomPCM a
                 => TimeStamp -> PCM a
                 -> SummaryWork (PCM a)
                 -> SummaryWork (PCM a)
updateSummaryPCM t (PCM d) sw =
    pcmMkSummaryWork t (min (pcmWorkMin sw) d)
                       (max (pcmWorkMax sw) d)
                       ((pcmWorkSum sw) + realToFrac (d * dur))
                       ((pcmWorkSumSq sw) + realToFrac (d*d * dur))
    where
        !dur = fromIntegral $ (unTS t) - (unTS (pcmWorkTime sw))
{-# INLINEABLE updateSummaryPCM #-}