///|
/// Top-of-stream parsing: the `fLaC` marker and the metadata-block chain that
/// precedes the audio frames. Besides `STREAMINFO`, the non-audio blocks —
/// padding, application data, seek tables, Vorbis comments and pictures — are
/// decoded into typed values rather than skipped.

/// The result of parsing a stream's metadata: the `STREAMINFO` block plus the
/// byte offset where the first audio frame begins.
pub struct FlacHeader {
  info : StreamInfo
  frame_offset : Int
}

/// One entry of a `SEEKTABLE` block: a sample number, the byte offset of the
/// frame that starts there, and that frame's sample count.
pub struct SeekPoint {
  sample_number : Int64
  offset : Int64
  frame_samples : Int
}

/// A `VORBIS_COMMENT` block: a vendor string plus the list of
/// `KEY=value` comment strings.
pub struct VorbisComment {
  vendor : String
  comments : Array[String]
}

/// A `PICTURE` block: the embedded cover art and its dimensions.
pub struct Picture {
  picture_type : Int
  mime_type : String
  description : String
  width : Int
  height : Int
  depth : Int
  colors : Int
  data : Bytes
}

/// One decoded metadata block, tagged by type.
pub enum MetadataBlock {
  StreamInfo(StreamInfo)
  Padding(Int)
  Application(Int, Bytes)
  SeekTable(Array[SeekPoint])
  VorbisComment(VorbisComment)
  Picture(Picture)
  Other(Int, Bytes)
}

/// Read `n` whole bytes from the (byte-aligned) reader into a fresh buffer.
fn read_bytes(r : BitReader, n : Int) -> (Bytes, BitReader) raise DecodeError {
  if n < 0 {
    raise InvalidMetadata
  }
  let start = r.pos() / 8
  let data = r.data()
  if start + n > data.length() {
    raise Truncated
  }
  let out = Array::make(n, b'\x00')
  for i in 0.. (String, BitReader) raise DecodeError {
  let (bytes, r1) = read_bytes(r, n)
  (@utf8.decode_lossy(bytes), r1)
}

/// Read a big-endian unsigned 64-bit integer (two 32-bit halves, reassembled
/// without sign loss).
fn read_u64_be(r : BitReader) -> (Int64, BitReader) raise DecodeError {
  let (hi, r1) = r.read_bits(32)
  let (lo, r2) = r1.read_bits(32)
  let hi64 = Int64::from_int(hi) & 4294967295L
  let lo64 = Int64::from_int(lo) & 4294967295L
  ((hi64 << 32) | lo64, r2)
}

/// Read a little-endian unsigned 32-bit integer (Vorbis comments store their
/// lengths little-endian, unlike the rest of FLAC).
fn read_u32_le(r : BitReader) -> (Int, BitReader) raise DecodeError {
  let (b0, r1) = r.read_byte()
  let (b1, r2) = r1.read_byte()
  let (b2, r3) = r2.read_byte()
  let (b3, r4) = r3.read_byte()
  let value = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
  if value < 0 {
    raise InvalidMetadata
  }
  (value, r4)
}

/// Parse the content of one metadata block (already extracted as `content`).
fn parse_block_content(block_type : Int, content : Bytes) -> MetadataBlock raise DecodeError {
  let r = BitReader::new(content)
  if block_type == 0 {
    let (si, _) = r.read_streaminfo()
    StreamInfo(si)
  } else if block_type == 1 {
    Padding(content.length())
  } else if block_type == 2 {
    if content.length() < 4 {
      raise InvalidMetadata
    }
    let (id, r1) = r.read_bits(32)
    let (data, _) = read_bytes(r1, content.length() - 4)
    Application(id, data)
  } else if block_type == 3 {
    if content.length() % 18 != 0 {
      raise InvalidMetadata
    }
    let count = content.length() / 18
    let points = Array::make(0, { sample_number: 0L, offset: 0L, frame_samples: 0 })
    let mut rr = r
    for _ in 0.. (MetadataBlock, BitReader) raise DecodeError {
  let (content, r1) = read_bytes(r, len)
  let block = try {
    parse_block_content(block_type, content)
  } catch {
    _ => raise InvalidMetadata
  }
  (block, r1)
}

/// Parse the `fLaC` marker and the whole metadata-block chain, returning the
/// decoded blocks and the byte offset of the first audio frame.
fn parse_chain(data : Bytes) -> (Array[MetadataBlock], Int) raise DecodeError {
  if data.length() < 4 {
    raise Truncated
  }
  if data[0] != b'f' || data[1] != b'L' || data[2] != b'a' || data[3] != b'C' {
    raise NotFlac
  }
  let blocks = Array::make(0, Other(0, b""))
  let mut r = BitReader::new(data).skip(32)
  let mut done = false
  while !done {
    let (header, r1) = r.read_byte()
    let is_last = (header & 0x80) != 0
    let block_type = header & 0x7F
    let (len, r2) = r1.read_u24()
    let (block, r3) = read_block(r2, block_type, len)
    blocks.push(block)
    r = r3
    if is_last {
      done = true
    }
  }
  (blocks, r.pos() / 8)
}

/// Decode every metadata block in the stream.
pub fn parse_metadata_blocks(data : Bytes) -> Array[MetadataBlock] raise DecodeError {
  parse_chain(data).0
}

/// Parse the `fLaC` marker and metadata blocks, returning the decoded
/// `STREAMINFO` and the offset of the first audio frame.
///
/// The first block must be a `STREAMINFO`; other blocks are decoded and, if
/// they are not a `STREAMINFO`, ignored here (use `parse_metadata_blocks` to
/// keep them).
pub fn parse_metadata(data : Bytes) -> FlacHeader raise DecodeError {
  let (blocks, frame_offset) = parse_chain(data)
  let mut info : StreamInfo? = None
  for b in blocks {
    match b {
      StreamInfo(si) => info = Some(si)
      _ => ()
    }
  }
  match info {
    Some(si) => { info: si, frame_offset: frame_offset }
    None => raise InvalidMetadata
  }
}