///|
/// Incremental Ogg FLAC demultiplexer. Output owns its bytes. Chained streams
/// emit a fresh fLaC signature, allowing callers to finish/start decoders.
/// After an error, discard this demultiplexer rather than resuming it.
struct OggDemux {
  page : Array[Byte]
  packet : Array[Byte]
  mut serial : UInt32?
  mut sequence : UInt32
  mut offset : Int64
  mut first : Bool
  mut headers : Bool
  mut header_count : Int32
  mut expected_headers : Int32
  mut ended : Bool
  mut saw_stream : Bool
  mut peak : Int32
  mut page_size : Int32
  mut granule : UInt64?
  mut info : StreamInfo?
  max_packet_bytes : Int32
}

///|
pub fn OggDemux::new(max_packet_bytes~ : Int32 = 16777219) -> OggDemux {
  {
    page: [],
    packet: [],
    serial: None,
    sequence: 0,
    offset: 0,
    first: true,
    headers: true,
    header_count: 0,
    expected_headers: 0,
    ended: false,
    saw_stream: false,
    peak: 0,
    page_size: 27,
    granule: None,
    info: None,
    max_packet_bytes,
  }
}

///|
pub fn OggDemux::buffered_bytes(self : OggDemux) -> Int32 {
  self.page.length() + self.packet.length()
}

///|
pub fn OggDemux::peak_buffered_bytes(self : OggDemux) -> Int32 {
  self.peak
}

///|
fn ogg_le(page : Array[Byte], start : Int32, n : Int32) -> UInt64 {
  let mut value = 0UL
  for i = 0; i < n; i = i + 1 {
    value = value | (page[start + i].to_uint64() << (i * 8))
  }
  value
}

///|
fn OggDemux::bad(self : OggDemux, message : String) -> Unit raise FlacError {
  raise Invalid(self.offset, message)
}

///|
fn ogg_crc(page : Array[Byte]) -> UInt32 {
  let mut crc = 0U
  for i = 0; i < page.length(); i = i + 1 {
    let b = if i >= 22 && i < 26 { 0U } else { page[i].to_uint() }
    crc = crc ^ (b << 24)
    for _j = 0; _j < 8; _j = _j + 1 {
      crc = if (crc & 0x80000000U) != 0U {
        (crc << 1) ^ 0x04c11db7U
      } else {
        crc << 1
      }
    }
  }
  crc
}

///|
fn OggDemux::process(
  self : OggDemux,
  output : Array[Bytes],
) -> Unit raise FlacError {
  let p = self.page
  if ogg_crc(p) != ogg_le(p, 22, 4).to_uint() {
    self.bad("Ogg CRC mismatch")
  }
  let flags = p[5].to_int()
  let serial = ogg_le(p, 14, 4).to_uint()
  let seq = ogg_le(p, 18, 4).to_uint()
  let segments = p[26].to_int()
  if self.serial is None {
    // Select a FLAC BOS; unrelated logical streams may be multiplexed.
    if (flags & 2) == 0 ||
      segments == 0 ||
      p.length() < 33 ||
      p[27 + segments] != 127 {
      return
    }
    let b = 27 + segments
    if p.length() < b + 5 ||
      p[b + 1] != 70 ||
      p[b + 2] != 76 ||
      p[b + 3] != 65 ||
      p[b + 4] != 67 {
      return
    }
    self.serial = Some(serial)
    self.sequence = seq
    self.first = true
    self.headers = true
    self.header_count = 0
    self.ended = false
    self.granule = None
    self.saw_stream = true
  }
  if self.serial != Some(serial) {
    return
  }
  if seq != self.sequence {
    self.bad("Ogg page sequence discontinuity")
  }
  self.sequence += 1U
  if ((flags & 1) != 0) != !self.packet.is_empty() {
    self.bad("Ogg continuation flag mismatch")
  }
  if self.first &&
    (
      seq != 0U ||
      flags != 2 ||
      segments != 1 ||
      p[27] != 51 ||
      p.length() != 79
    ) {
    self.bad("invalid Ogg FLAC BOS page")
  }
  if !self.first && (flags & 2) != 0 {
    self.bad("unexpected Ogg BOS")
  }
  let granule = ogg_le(p, 6, 8)
  let was_headers = self.headers
  if self.headers && granule != 0UL {
    self.bad("nonzero Ogg header granule")
  }
  let mut cursor = 27 + segments
  let mut completed = false
  let mut page_samples = 0UL
  for i = 0; i < segments; i = i + 1 {
    let size = p[27 + i].to_int()
    if size > self.max_packet_bytes - self.packet.length() {
      raise Limit("Ogg packet byte limit")
    }
    for j = 0; j < size; j = j + 1 {
      self.packet.push(p[cursor + j])
    }
    cursor += size
    if self.buffered_bytes() > self.peak {
      self.peak = self.buffered_bytes()
    }
    if size < 255 {
      completed = true
      let packet = Bytes::from_array(self.packet)
      self.packet.clear()
      if self.first {
        if packet.length() != 51 ||
          packet[5] != 1 ||
          packet[6] != 0 ||
          packet[9] != 102 ||
          packet[10] != 76 ||
          packet[11] != 97 ||
          packet[12] != 67 ||
          packet[13] != 0 ||
          packet[14] != 0 ||
          packet[15] != 0 ||
          packet[16] != 34 {
          self.bad("invalid Ogg FLAC mapping header")
        }
        self.expected_headers = packet[7].to_int() * 256 + packet[8].to_int()
        let r = MetaReader::{ data: packet, base: 0, pos: 9, }
        let native = r.take(42)
        let standalone = native.to_array()
        standalone[4] = 128
        let (metadata, _) = parse_metadata(Bytes::from_array(standalone))
        self.info = Some(metadata.info)
        output.push(native)
        self.first = false
      } else if self.headers {
        if packet.length() < 4 {
          self.bad("short Ogg metadata packet")
        }
        let n = packet[1].to_int() * 65536 +
          packet[2].to_int() * 256 +
          packet[3].to_int()
        if packet.length() != n + 4 || (packet[0].to_int() & 127) == 0 {
          self.bad("invalid Ogg metadata packet")
        }
        self.header_count += 1
        output.push(packet)
        if (packet[0].to_int() & 128) != 0 {
          if i != segments - 1 {
            self.bad("Ogg audio shares final metadata page")
          }
          if self.expected_headers != 0 &&
            self.expected_headers != self.header_count {
            self.bad("Ogg header packet count mismatch")
          }
          self.headers = false
        }
      } else {
        if packet.length() < 2 ||
          packet[0] != 255 ||
          (packet[1].to_int() & 254) != 248 {
          self.bad("Ogg audio packet lacks FLAC frame")
        }
        let info = self.info.unwrap()
        let (frame, consumed) = parse_frame(packet, 0, info, self.offset) catch {
          NeedMore =>
            raise Invalid(self.offset, "truncated FLAC frame in Ogg packet")
          error => raise error
        }
        if consumed != packet.length() {
          self.bad("Ogg audio packet contains more than one FLAC frame")
        }
        page_samples += frame.block_size.to_uint64()
        output.push(packet)
      }
    }
  }
  if !self.headers && !completed && granule != 0xffffffffffffffffUL {
    self.bad("Ogg unfinished page granule")
  }
  if !was_headers && completed {
    if granule == 0xffffffffffffffffUL {
      self.bad("missing Ogg completed-page granule")
    }
    if self.granule is Some(previous) {
      if granule < previous || granule - previous != page_samples {
        self.bad("Ogg granule does not match decoded sample count")
      }
    } else if granule < page_samples {
      self.bad("Ogg first granule precedes decoded samples")
    }
    self.granule = Some(granule)
  }
  if (flags & 4) != 0 {
    if self.headers || !self.packet.is_empty() {
      self.bad("Ogg EOS before packet completion")
    }
    self.ended = true
    self.serial = None
  }
}

///|
/// Consumes arbitrarily large caller input a byte at a time without retaining it.
pub fn OggDemux::push(
  self : OggDemux,
  data : Bytes,
) -> Array[Bytes] raise FlacError {
  let output = []
  for byte in data {
    self.page.push(byte)
    let p = self.page
    if p.length() == 27 {
      if p[0] != 79 ||
        p[1] != 103 ||
        p[2] != 103 ||
        p[3] != 83 ||
        p[4] != 0 ||
        (p[5].to_int() & 248) != 0 {
        self.bad("invalid Ogg page header")
      }
    }
    if p.length() >= 27 && p.length() == 27 + p[26].to_int() {
      let mut total = 27 + p[26].to_int()
      for i = 0; i < p[26].to_int(); i = i + 1 {
        total += p[27 + i].to_int()
      }
      self.page_size = total
    } else if p.length() == 27 {
      self.page_size = 27 + p[26].to_int()
    }
    if p.length() >= 27 && p.length() == self.page_size {
      self.process(output)
      self.offset += self.page_size.to_int64()
      self.page.clear()
      self.page_size = 27
    }
    if self.buffered_bytes() > self.peak {
      self.peak = self.buffered_bytes()
    }
  }
  output
}

///|
pub fn OggDemux::finish(self : OggDemux) -> Unit raise FlacError {
  if !self.page.is_empty() ||
    !self.packet.is_empty() ||
    !self.saw_stream ||
    !self.ended {
    self.bad("truncated Ogg FLAC stream")
  }
}