--
-- Module      : RawPage
-- Copyright   : (c) Conrad Parker 2006
-- License     : BSD-style
-- Maintainer  : conradp@cse.unsw.edu.au
-- Stability   : experimental
-- Portability : portable

module Codec.Container.Ogg.RawPage (
  OggRawPage (..),
  pageMarker,
  pageVersion,
  rawPageScan,
  rawPageBuild
) where

import Codec.Container.Ogg.ByteFields

import qualified Data.ByteString.Lazy as L
import Data.Int (Int64)
import Data.Word (Word8, Word32, Word64)

------------------------------------------------------------
-- The Ogg page format
-- from RFC3533: http://www.ietf.org/rfc/rfc3533.txt
{-

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1| Byte
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| capture_pattern: Magic number for page start "OggS"           | 0-3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| version       | header_type   | granule_position              | 4-7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               | 8-11
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               | bitstream_serial_number       | 12-15
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               | page_sequence_number          | 16-19
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               | CRC_checksum                  | 20-23
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               |page_segments  | segment_table | 24-27
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ...                                                           | 28-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

-}

------------------------------------------------------------
-- Data
--

data OggRawPage =
  OggRawPage {
    rawPageVersion :: !Word8,
    rawPageHType :: !Word8,
    rawPageGranulepos :: !Word64,
    rawPageSerialno :: !Word32,
    rawPageSeqno :: !Word32,
    rawPageCRC :: !Word32,
    rawPageNumseg :: !Int,
    rawPageSegtab :: !([Int]),
    rawPageBody :: !L.ByteString
  }

------------------------------------------------------------
-- OggRawPage constants
--

pageMarker :: L.ByteString
pageMarker = L.pack [0x4f, 0x67, 0x67, 0x53] -- "OggS"

-- | Ogg version supported by this library
pageVersion :: Word8
pageVersion = 0x00

------------------------------------------------------------
-- rawPageScan
--

rawPageScan :: L.ByteString -> [OggRawPage]
rawPageScan input
  | L.null input                  = []
  | L.isPrefixOf pageMarker input = newPage : rawPageScan rest
  | otherwise                     = rawPageScan (L.tail input)
  where (newPage, pageLen) = rawPageBuild input
        rest = L.drop pageLen input

rawPageBuild :: L.ByteString -> (OggRawPage, Int64)
rawPageBuild d = (newRawPage, pageLen) where
  newRawPage = OggRawPage v htype gp serialno seqno crc numseg segtab body
  v = u8At 4 d
  htype = u8At 5 d
  gp = le64At 6 d
  serialno = le32At 14 d
  seqno = le32At 18 d
  crc = le32At 22 d
  numseg64 = u8At 26 d
  numseg = fromIntegral numseg64
  st = L.unpack $ L.take numseg64 (L.drop 27 d)
  segtab = map fromIntegral st
  headerSize = 27 + numseg64
  bodySize = fromIntegral $ sum segtab
  body = L.take bodySize (L.drop headerSize d)
  pageLen = fromIntegral $ headerSize + bodySize

------------------------------------------------------------
-- Show
--

instance Show OggRawPage where
  show r =
    "Version: " ++ show (rawPageVersion r) ++ "\n" ++
    "HType: " ++ show (rawPageHType r) ++ "\n" ++
    "Granulepos: " ++ show (rawPageGranulepos r) ++ "\n" ++
    "Serialno: " ++ show (rawPageSerialno r) ++ "\n" ++
    "Seqno: " ++ show (rawPageSeqno r) ++ "\n" ++
    "CRC: " ++ show (rawPageCRC r) ++ "\n" ++
    "Numseg: " ++ show (rawPageNumseg r) ++ "\n" ++
    "Segtab: " ++ show (rawPageSegtab r) ++ "\n" ++ "\n"