{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS -Wall #-}
----------------------------------------------------------------------
-- |
-- Module      : Data.ZoomCache.PCM.Enumeratee
-- Copyright   : Conrad Parker
-- License     : BSD3-style (see LICENSE)
--
-- Maintainer  : Conrad Parker <conrad@metadecks.org>
-- Stability   : unstable
-- Portability : unknown
--
-- ZoomCache PCM enumeratee conversions
----------------------------------------------------------------------

module Data.ZoomCache.PCM.Enumeratee (
      enumPCMDouble
    , enumListPCMDouble

    , enumSummaryPCMDouble
    , wholeTrackSummaryPCMDouble

    , enumSummaryListPCMDouble
    , wholeTrackSummaryListPCMDouble
) where

import Control.Applicative ((<$>))
import Control.Monad.Trans (MonadIO)
import Data.ByteString (ByteString)
import Data.Int
import qualified Data.Iteratee as I
import Data.Maybe
import Data.Offset
import Data.Typeable
import Data.TypeLevel.Num hiding ((==))
import Data.ZoomCache
import Data.ZoomCache.Codec
import Data.ZoomCache.NList
import Data.ZoomCache.Multichannel.NList

import Data.ZoomCache.PCM.Types
import Data.ZoomCache.PCM.IEEE754()
import Data.ZoomCache.PCM.Int()

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

rawToPCMDouble :: ZoomRaw -> [PCM Double]
rawToPCMDouble (ZoomRaw xs) | typeOf xs == typeOf (undefined :: [PCM Double]) =
                                 fromMaybe [] (cast xs :: Maybe [PCM Double])
                            | typeOf xs == typeOf (undefined :: [PCM Float]) =
                                            f (cast xs :: Maybe [PCM Float])
                            | typeOf xs == typeOf (undefined :: [PCM Int]) =
                                            f (cast xs :: Maybe [PCM Int])
                            | typeOf xs == typeOf (undefined :: [PCM Int8]) =
                                            f (cast xs :: Maybe [PCM Int8])
                            | typeOf xs == typeOf (undefined :: [PCM Int16]) =
                                            f (cast xs :: Maybe [PCM Int16])
                            | typeOf xs == typeOf (undefined :: [PCM Int32]) =
                                            f (cast xs :: Maybe [PCM Int32])
                            | typeOf xs == typeOf (undefined :: [PCM Int64]) =
                                            f (cast xs :: Maybe [PCM Int64])
                            | otherwise = []
    where
        f :: Real a => Maybe [PCM a] -> [PCM Double]
        f = maybe [] (map (PCM . realToFrac . unPCM))

rawToListPCMDouble :: ZoomRaw -> [[PCM Double]]
rawToListPCMDouble (ZoomRaw xs) | not (null d) = [d]
                                | typeOf xs == typeOf (undefined :: [NList D1 (PCM Double)]) =
                                                l (cast xs :: Maybe [NList D1 (PCM Double)])
                                | typeOf xs == typeOf (undefined :: [NList D1 (PCM Float)]) =
                                                f (cast xs :: Maybe [NList D1 (PCM Float)])
                                | typeOf xs == typeOf (undefined :: [NList D1 (PCM Int)]) =
                                                f (cast xs :: Maybe [NList D1 (PCM Int)])
                                | typeOf xs == typeOf (undefined :: [NList D1 (PCM Int8)]) =
                                                f (cast xs :: Maybe [NList D1 (PCM Int8)])
                                | typeOf xs == typeOf (undefined :: [NList D1 (PCM Int16)]) =
                                                f (cast xs :: Maybe [NList D1 (PCM Int16)])
                                | typeOf xs == typeOf (undefined :: [NList D1 (PCM Int32)]) =
                                                f (cast xs :: Maybe [NList D1 (PCM Int32)])
                                | typeOf xs == typeOf (undefined :: [NList D1 (PCM Int64)]) =
                                                f (cast xs :: Maybe [NList D1 (PCM Int64)])
                                | otherwise = []
    where
        d = rawToPCMDouble (ZoomRaw xs)
        l :: Maybe [NList D1 a] -> [[a]]
        l = maybe [] (map nListToList)
        f :: (ZoomReadable a) => Maybe [NList D1 a] -> [[PCM Double]]
        f = map (rawToPCMDouble . ZoomRaw) . l

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

-- | Coercion of numeric Summary to type Summary Double.
toSummaryPCMDouble :: Typeable a => Summary a -> Maybe (Summary (PCM Double))
toSummaryPCMDouble s | typeOf s == typeOf (undefined :: Summary (PCM Double)) =
                                   id (cast s :: Maybe (Summary (PCM Double)))
                     | typeOf s == typeOf (undefined :: Summary (PCM Float)) =
                               sd <$> (cast s :: Maybe (Summary (PCM Float)))
                     | typeOf s == typeOf (undefined :: Summary (PCM Int)) =
                               sd <$> (cast s :: Maybe (Summary (PCM Int)))
                     | typeOf s == typeOf (undefined :: Summary (PCM Int8)) =
                               sd <$> (cast s :: Maybe (Summary (PCM Int8)))
                     | typeOf s == typeOf (undefined :: Summary (PCM Int16)) =
                               sd <$> (cast s :: Maybe (Summary (PCM Int16)))
                     | typeOf s == typeOf (undefined :: Summary (PCM Int32)) =
                               sd <$> (cast s :: Maybe (Summary (PCM Int32)))
                     | typeOf s == typeOf (undefined :: Summary (PCM Int64)) =
                               sd <$> (cast s :: Maybe (Summary (PCM Int64)))
                     | otherwise = Nothing
    where
        sd :: ZoomPCM a => Summary (PCM a) -> Summary (PCM Double)
        sd s' = s' { summaryData = toSummaryDataPCMDouble (summaryData s') }

toSummaryDataPCMDouble :: ZoomPCM a => SummaryData (PCM a) -> SummaryData (PCM Double)
toSummaryDataPCMDouble s = pcmMkSummary
    (realToFrac . pcmMin $ s)
    (realToFrac . pcmMax $ s)
    (pcmAvg s)
    (pcmRMS s)

toSummaryListPCMDouble :: Typeable a => Summary a -> Maybe [Summary (PCM Double)]
toSummaryListPCMDouble s | isJust sd = (:[]) <$> sd
                         | typeOf s == typeOf (undefined :: Summary (NList D1 (PCM Double))) =
                                   sl <$> (cast s :: Maybe (Summary (NList D1 (PCM Double))))
                         | typeOf s == typeOf (undefined :: Summary (NList D1 (PCM Float))) =
                                  sld <$> (cast s :: Maybe (Summary (NList D1 (PCM Float))))
                         | typeOf s == typeOf (undefined :: Summary (NList D1 (PCM Int))) =
                                  sld <$> (cast s :: Maybe (Summary (NList D1 (PCM Int))))
                         | typeOf s == typeOf (undefined :: Summary (NList D1 (PCM Int8))) =
                                  sld <$> (cast s :: Maybe (Summary (NList D1 (PCM Int8))))
                         | typeOf s == typeOf (undefined :: Summary (NList D1 (PCM Int16))) =
                                  sld <$> (cast s :: Maybe (Summary (NList D1 (PCM Int16))))
                         | typeOf s == typeOf (undefined :: Summary (NList D1 (PCM Int32))) =
                                  sld <$> (cast s :: Maybe (Summary (NList D1 (PCM Int32))))
                         | typeOf s == typeOf (undefined :: Summary (NList D1 (PCM Int64))) =
                                  sld <$> (cast s :: Maybe (Summary (NList D1 (PCM Int64))))
                         | otherwise = Nothing
    where
        sd = toSummaryPCMDouble s
        sl :: Summary (NList D1 a) -> [Summary a]
        sl = summaryNListToList
        sld :: Typeable a => Summary (NList D1 a) -> [Summary (PCM Double)]
        sld = catMaybes . map toSummaryPCMDouble . sl

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

enumPCMDouble :: (Functor m, MonadIO m)
              => I.Enumeratee [Offset Block] [(TimeStamp, PCM Double)] m a
enumPCMDouble = I.joinI . enumPackets . I.mapChunks (concatMap f)
    where
        f :: Packet -> [(TimeStamp, PCM Double)]
        f Packet{..} = zip packetTimeStamps (rawToPCMDouble packetData)

enumListPCMDouble :: (Functor m, MonadIO m)
                  => I.Enumeratee [Offset Block] [(TimeStamp, [PCM Double])] m a
enumListPCMDouble = I.joinI . enumPackets . I.mapChunks (concatMap f)
    where
        f :: Packet -> [(TimeStamp, [PCM Double])]
        f Packet{..} = zip packetTimeStamps (rawToListPCMDouble packetData)

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

-- | Read the summary of an entire track.
wholeTrackSummaryPCMDouble :: (Functor m, MonadIO m)
                           => [IdentifyCodec]
                           -> TrackNo
                           -> I.Iteratee (Offset ByteString) m (Summary (PCM Double))
wholeTrackSummaryPCMDouble identifiers trackNo = I.joinI $ enumCacheFile identifiers .
    I.joinI . filterTracks [trackNo] .  I.joinI . e $ I.last
    where
        e = I.joinI . enumSummaries . I.mapChunks (catMaybes . map toSD)
        toSD :: ZoomSummary -> Maybe (Summary (PCM Double))
        toSD (ZoomSummary s) = toSummaryPCMDouble s

enumSummaryPCMDouble :: (Functor m, MonadIO m)
                     => Int
                     -> I.Enumeratee [Offset Block] [Summary (PCM Double)] m a
enumSummaryPCMDouble level =
    I.joinI . enumSummaryLevel level .
    I.mapChunks (catMaybes . map toSD)
    where
        toSD :: ZoomSummary -> Maybe (Summary (PCM Double))
        toSD (ZoomSummary s) = toSummaryPCMDouble s

-- | Read the summary of an entire track.
wholeTrackSummaryListPCMDouble :: (Functor m, MonadIO m)
                               => [IdentifyCodec]
                               -> TrackNo
                               -> I.Iteratee (Offset ByteString) m [Summary (PCM Double)]
wholeTrackSummaryListPCMDouble identifiers trackNo =
    I.joinI $ enumCacheFile identifiers .
    I.joinI . filterTracks [trackNo] .  I.joinI . e $ I.last
    where
        e = I.joinI . enumSummaries . I.mapChunks (catMaybes . map toSLD)
        toSLD :: ZoomSummary -> Maybe [Summary (PCM Double)]
        toSLD (ZoomSummary s) = toSummaryListPCMDouble s

enumSummaryListPCMDouble :: (Functor m, MonadIO m)
                         => Int
                         -> I.Enumeratee [Offset Block] [[Summary (PCM Double)]] m a
enumSummaryListPCMDouble level =
    I.joinI . enumSummaryLevel level .
    I.mapChunks (catMaybes . map toSLD)
    where
        toSLD :: ZoomSummary -> Maybe [Summary (PCM Double)]
        toSLD (ZoomSummary s) = toSummaryListPCMDouble s
