{-# LANGUAGE ScopedTypeVariables
            ,FlexibleInstances
            ,MultiParamTypeClasses
            ,DeriveDataTypeable #-}

module Sound.Iteratee.Base (
  -- * Types
  -- ** Internal types
  AudioStreamState (..),
  WritableAudio (..),
  AudioMonad,
  -- *** Functions to work with AudioMonad
  module Control.Monad.Trans.State,
  defaultChunkLength,
  -- ** Audio Format types
  AudioFormat (..),
  SupportedBitDepths (..),
  NumChannels,
  SampleRate,
  BitDepth,
  FrameCount,
  -- ** File Format Types
  SupportedFileFormat (..),
  -- ** Exceptions
  UnknownFileTypeException (..),
  CorruptFileException (..),
  MissingFormatException (..),
  -- * Audio iteratees
  getChannel
)

where

import Prelude as P

import           Control.Exception
import           Control.Monad.Trans.State
import           Control.Monad.IO.Class
import           System.IO
import           Data.Data
import           Data.Nullable
import           Data.NullPoint
import           Data.Iteratee as I
import           Data.Iteratee.Base.ReadableChunk
import           Data.Iteratee.Exception ()
import           Data.ListLike.Vector.Storable ()
import qualified Data.Vector.Storable as V
import           Data.Word

import           Foreign.Marshal.Utils
import           Foreign.ForeignPtr

-- |Information about the AudioStream
data AudioStreamState =
  WaveState !(Maybe Handle) !(Maybe AudioFormat) !Integer !Integer !Integer -- ^ Handle, format, Total bytes written, data bytes written, data chunklen offset
  | NoState
  deriving (Eq, Show)

-- | An enumeration of all file types supported for reading and writing.
data SupportedFileFormat = Raw
                           | Wave
                           deriving (Show, Enum, Bounded, Eq, Data, Typeable)

-- | Common functions for writing audio data
class WritableAudio a where
  emptyState :: a -> AudioStreamState
  initState ::  a -> Handle -> AudioStreamState
  supportedBitDepths :: a -> SupportedBitDepths
  fileType           :: a -> SupportedFileFormat

-- | Audio monad stack (for writing files)
type AudioMonad = StateT AudioStreamState IO

-- | Format of audio data
data AudioFormat = AudioFormat {
  numberOfChannels :: NumChannels, -- ^Number of channels in the audio data
  sampleRate :: SampleRate, -- ^Sample rate of the audio data
  bitDepth :: BitDepth -- ^Bit depth of the audio data
  } deriving (Show, Eq, Data, Typeable)

type NumChannels = Integer
type SampleRate  = Integer
type BitDepth    = Integer
type FrameCount  = Integer

data SupportedBitDepths = Any | Supported [BitDepth]

defaultChunkLength :: Int
defaultChunkLength = 8190

instance V.Storable a => Nullable (V.Vector a) where
  nullC = V.null

instance V.Storable a => NullPoint (V.Vector a) where
  empty = V.empty

instance ReadableChunk (V.Vector Word8) Word8 where
  readFromPtr src blen = liftIO $ do
    fp <- mallocForeignPtrBytes blen
    withForeignPtr fp $ \dest -> copyBytes dest src blen
    return $ V.unsafeFromForeignPtr fp 0 blen

-- | Operate on a single channel of an audio stream.
getChannel ::
  Monad m
  => Int     -- ^ number of channels
  -> Int     -- ^ channel index (1-based)
  -> Enumeratee (V.Vector Double) (V.Vector Double) m a
getChannel 1        m   = \i -> I.drop m >> convStream getChunk i
getChannel numChans chn = unfoldConvStream mkIter chn
 where
  mkIter drp = do
    I.drop drp
    buf <- getChunk
    let tlen = V.length buf
        (nlen,rest) = quotRem tlen numChans
        newbuf = V.generate nlen (\i -> V.unsafeIndex buf (i*numChans))
    return (rest, newbuf)

-- Audio Exceptions

data UnknownFileTypeException =
  UnknownFileTypeException deriving (Eq, Show, Typeable)

instance Exception UnknownFileTypeException where
  toException = iterExceptionToException
  fromException = iterExceptionFromException

instance IException UnknownFileTypeException where

data CorruptFileException = CorruptFileException deriving (Eq, Show, Typeable)

instance Exception CorruptFileException where
  toException = iterExceptionToException
  fromException = iterExceptionFromException

instance IException CorruptFileException where

data MissingFormatException =
  MissingFormatException deriving (Eq, Show, Typeable)

instance Exception MissingFormatException where
  toException = iterExceptionToException
  fromException = iterExceptionFromException

instance IException MissingFormatException where