{-# 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 (catch, 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