{-# LINE 1 "Codec/Audio/Vorbis/File.hsc" #-}
{-# LANGUAGE ForeignFunctionInterface, EmptyDataDecls, ScopedTypeVariables,
{-# LINE 2 "Codec/Audio/Vorbis/File.hsc" #-}
             DeriveDataTypeable #-}
-- | High level API for decoding Ogg-Vorbis files or streams.
-- This module is intended to be imported qualified, e.g.
--
-- > import qualified Codec.Audio.Vorbis.File as V
module Codec.Audio.Vorbis.File (
        -- * Opening file or streams
        openFile,
        openCallbacks,
        withFile,
        withCallbacks,
        File,
        -- * File metadata
        info,
        Info(..),
        Channels(..),
        -- * Read data from file
        read,
        Endianness(..),
        getSystemEndianness,
        WordSize(..),
        Signedness(..),
        -- * Close file
        close
    ) where

import Control.Applicative
import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Unsafe as B
import qualified Data.ByteString.Internal as B
import Control.Exception
import Data.Int
import Data.Typeable
import Foreign hiding (new)
import Foreign.C
import Prelude hiding (read)
import System.IO (SeekMode(..), hClose, hSeek, hTell, IOMode(..))
import qualified System.IO as IO
import System.Endian


{-# LINE 45 "Codec/Audio/Vorbis/File.hsc" #-}

{-# LINE 46 "Codec/Audio/Vorbis/File.hsc" #-}

type ReadFunc = Ptr Word8 -> CSize -> CSize -> Ptr () -> IO CSize
type SeekFunc = Ptr () -> Int64 -> CInt -> IO CInt
type CloseFunc = Ptr () -> IO CInt
type TellFunc  = Ptr () -> IO CLong

foreign import ccall safe "new_OggVorbis_File" new_OggVorbis_File
    :: FunPtr ReadFunc -> FunPtr SeekFunc -> FunPtr CloseFunc -> FunPtr TellFunc -> Ptr CInt -> IO (Ptr File_struct)

foreign import ccall "free_OggVorbis_File" free_OggVorbis_File
    :: Ptr File_struct -> IO ()

foreign import ccall "wrapper" mkReadFunc
    :: ReadFunc -> IO (FunPtr ReadFunc)

foreign import ccall "wrapper" mkSeekFunc
    :: SeekFunc -> IO (FunPtr SeekFunc)

foreign import ccall "wrapper" mkCloseFunc
    :: CloseFunc -> IO (FunPtr CloseFunc)

foreign import ccall "wrapper" mkTellFunc
    :: TellFunc -> IO (FunPtr TellFunc)

foreign import ccall safe "info_OggVorbis_File" info_OggVorbis_File
    :: Ptr File_struct -> Ptr CInt -> Ptr CInt -> Ptr CLong -> IO CInt

foreign import ccall safe "ov_read" ov_read
    :: Ptr File_struct -> Ptr CChar -> CInt -> CInt -> CInt -> CInt -> Ptr CInt -> IO CLong

data File_struct
data File = File (Ptr File_struct) (FunPtr ReadFunc) (FunPtr CloseFunc) (FunPtr SeekFunc) (FunPtr TellFunc)

data OggVorbisStatus =
      OV_EREAD
    | OV_EFAULT
    | OV_EIMPL
    | OV_EINVAL
    | OV_ENOTVORBIS
    | OV_EBADHEADER
    | OV_EVERSION
    | OV_ENOTAUDIO
    | OV_EBADPACKET
    | OV_EBADLINK
    | OV_ENOSEEK
    | OV_BADINFO         -- Ours
    deriving (Eq, Show)

data OggVorbisException = OggVorbisException OggVorbisStatus
    deriving (Eq, Show, Typeable)

instance Exception OggVorbisException where

throwVorbisError :: CInt -> IO a
throwVorbisError err = do
    let status = case err of
            (-128)      -> OV_EREAD
{-# LINE 103 "Codec/Audio/Vorbis/File.hsc" #-}
            (-129)     -> OV_EFAULT
{-# LINE 104 "Codec/Audio/Vorbis/File.hsc" #-}
            (-130)      -> OV_EIMPL
{-# LINE 105 "Codec/Audio/Vorbis/File.hsc" #-}
            (-131)     -> OV_EINVAL
{-# LINE 106 "Codec/Audio/Vorbis/File.hsc" #-}
            (-132) -> OV_ENOTVORBIS
{-# LINE 107 "Codec/Audio/Vorbis/File.hsc" #-}
            (-133) -> OV_EBADHEADER
{-# LINE 108 "Codec/Audio/Vorbis/File.hsc" #-}
            (-134)   -> OV_EVERSION
{-# LINE 109 "Codec/Audio/Vorbis/File.hsc" #-}
            (-135)  -> OV_ENOTAUDIO
{-# LINE 110 "Codec/Audio/Vorbis/File.hsc" #-}
            (-136) -> OV_EBADPACKET
{-# LINE 111 "Codec/Audio/Vorbis/File.hsc" #-}
            (-137)   -> OV_EBADLINK
{-# LINE 112 "Codec/Audio/Vorbis/File.hsc" #-}
            (-138)    -> OV_ENOSEEK
{-# LINE 113 "Codec/Audio/Vorbis/File.hsc" #-}
            _                      -> error $ "Codec.Audio.Vorbis.File.throwVorbisError: bad error: "++show err
    throwIO $ OggVorbisException status

-- | Open the specified Ogg-Vorbis file for decoding.
openFile :: FilePath -> IO File
openFile path = do
    h <- IO.openFile path ReadMode
    let readFunc size nmemb = B.hGet h (size * nmemb)
        closeFunc = hClose h
        seekFunc = hSeek h
        tellFunc = hTell h
    openCallbacks readFunc closeFunc (Just (seekFunc, tellFunc))

-- | Decode Ogg-Vorbis using the specified callbacks to do the back-end I/O.
-- Seek and tell functions are optional.
openCallbacks :: (Int -> Int -> IO ByteString)    -- ^ Read function, taking size and nmemb
              -> IO ()                            -- ^ Close function
              -> Maybe (SeekMode -> Integer -> IO (), IO Integer)  -- ^ Seek and tell functions
              -> IO File
openCallbacks readFunc closeFunc mSeekTell = do
    c_readFunc <- mkReadFunc $ \buf size nmemb _ -> do
        r <- readFunc (fromIntegral size) (fromIntegral nmemb)
        B.unsafeUseAsCStringLen r $ \(rbuf, rlen) -> B.memcpy (castPtr buf) (castPtr rbuf) rlen
        return $ fromIntegral (B.length r) `div` size
      `catch` \(_ :: IOException) ->
        return (-1)
    c_closeFunc <- mkCloseFunc $ \_ -> do
        closeFunc
        return 0
      `catch` \(_ :: IOException) ->
        return (-1)
    (c_seekFunc, c_tellFunc) <- case mSeekTell of
        Just (seekFunc, tellFunc) -> do
            c_seekFunc <- mkSeekFunc $ \_ offset whence -> do
                let mode = case whence of
                        (0) -> AbsoluteSeek
{-# LINE 149 "Codec/Audio/Vorbis/File.hsc" #-}
                        (1) -> RelativeSeek
{-# LINE 150 "Codec/Audio/Vorbis/File.hsc" #-}
                        (2) -> SeekFromEnd
{-# LINE 151 "Codec/Audio/Vorbis/File.hsc" #-}
                        _                 -> error $ "Codec.Audio.Vorbis.File.new: bad whence in seek: "++show whence
                seekFunc mode (fromIntegral offset)
                return 0
              `catch` \(_ :: IOException) ->
                return (-1)
            c_tellFunc <- mkTellFunc $ \_ -> do
                fromIntegral <$> tellFunc
              `catch` \(_ :: IOException) ->
                return (-1)
            return (c_seekFunc, c_tellFunc)
        Nothing -> return (nullFunPtr, nullFunPtr)
    alloca $ \p_error -> do
        f <- new_OggVorbis_File c_readFunc c_seekFunc c_closeFunc c_tellFunc p_error
        if f /= nullPtr then
            return $ File f c_readFunc c_closeFunc c_seekFunc c_tellFunc
          else do
            freeFunPtrs c_readFunc c_closeFunc c_seekFunc c_tellFunc
            err <- peek p_error
            throwVorbisError err

-- | Close the file once we've finished with it. Must be used with the handles returned
-- by 'openFile' and 'openCallbacks'.
close :: File -> IO ()
close (File c_f c_readFunc c_closeFunc c_seekFunc c_tellFunc) = do
    free_OggVorbis_File c_f
    freeFunPtrs c_readFunc c_closeFunc c_seekFunc c_tellFunc

freeFunPtrs :: FunPtr ReadFunc -> FunPtr CloseFunc -> FunPtr SeekFunc -> FunPtr TellFunc -> IO ()
freeFunPtrs c_readFunc c_seekFunc c_closeFunc c_tellFunc = do
    when (c_seekFunc /= nullFunPtr) $ freeHaskellFunPtr c_seekFunc
    when (c_tellFunc /= nullFunPtr) $ freeHaskellFunPtr c_tellFunc
    freeHaskellFunPtr c_readFunc
    freeHaskellFunPtr c_closeFunc

-- | Open the specified Ogg-Vorbis file for decoding.
--
-- Opens it using the loan pattern: Guaranteed to call 'close' for you on
-- completion (exception safe), so you must not call 'close' explicitly.
withFile :: FilePath -> (File -> IO a) -> IO a
withFile path code = bracket (openFile path) close code

-- | Decode Ogg-Vorbis using the specified callbacks to do the back-end I/O.
-- Seek and tell functions are optional.
--
-- Opens it using the loan pattern: Guaranteed to call 'close' for you on
-- completion (exception safe), so you must not call 'close' explicitly.
withCallbacks :: (Int -> Int -> IO ByteString)    -- ^ Read function, taking size and nmemb
              -> IO ()                            -- ^ Close function
              -> Maybe (SeekMode -> Integer -> IO (), IO Integer)  -- ^ Seek and tell functions
              -> (File -> IO a)
              -> IO a
withCallbacks r c mST code = bracket (openCallbacks r c mST) close code

data Info = Info {
        inVersion  :: Int,
        inChannels :: Channels,
        inRate     :: Int
    }
    deriving Show

data Channels = Mono | Stereo deriving (Eq, Ord, Show)

info :: File -> IO Info
info (File c_f _ _ _ _) =
    alloca $ \p_version ->
    alloca $ \p_channels ->
    alloca $ \p_rate -> do
        ret <- info_OggVorbis_File c_f p_version p_channels p_rate
        if ret == 0 then
            Info <$> (fromIntegral <$> peek p_version)
                 <*> (unmarshallChannels <$> peek p_channels)
                 <*> (fromIntegral <$> peek p_rate)
          else do
            throwIO $ OggVorbisException OV_BADINFO
  where
    unmarshallChannels 1 = Mono
    unmarshallChannels 2 = Stereo
    unmarshallChannels c = error $ "Codec.Audio.Vorbis.info: can't unmarshall no. of channels: "++show c

data Signedness = Signed | Unsigned deriving (Eq, Ord, Show)
data WordSize = EightBit | SixteenBit deriving (Eq, Ord, Show)

-- | Read data from the file. Returns the data block and the number of the current
-- logical bitstream. 'Nothing' for end of file.
read :: File
     -> Int          -- ^ Maximum bytes to read (will typically return less than this)
     -> Endianness   -- ^ How to encode the samples as bytes
     -> WordSize     -- ^ Desired word size
     -> Signedness   -- ^ Whether you want signed or unsigned values
     -> IO (Maybe (ByteString, Int)) 
read (File f _ _ _ _) bytes endianness wordsize signedness =
    allocaBytes bytes $ \buf ->
    alloca $ \p_bitstream -> go buf p_bitstream
  where
    go buf p_bitstream = do
        ret <- ov_read f buf (fromIntegral bytes) c_endianness c_wordsize c_signedness p_bitstream
        case ret of
            -- Deal with hole by just continuing to read. We might want to improve this
            -- in future.
            (-3) -> go buf p_bitstream
{-# LINE 251 "Codec/Audio/Vorbis/File.hsc" #-}
            0 -> return Nothing
            n | n > 0 -> do
                bs <- B.create (fromIntegral n) (\bs_buf -> B.memcpy bs_buf (castPtr buf) (fromIntegral n))
                bitstream <- fromIntegral <$> peek p_bitstream
                return $ Just (bs, bitstream)
            _ -> throwVorbisError (fromIntegral ret)
    c_endianness = case endianness of
        LittleEndian -> 0
        BigEndian    -> 1
    c_wordsize = case wordsize of
        EightBit     -> 1
        SixteenBit   -> 2
    c_signedness = case signedness of
        Unsigned     -> 0
        Signed       -> 1