///|
/// Incremental integer PCM input events. Samples are interleaved, unshifted,
/// owned, and always contain complete channel groups.
pub(all) enum PcmInputEvent {
  PcmFormatReady(PcmFormat, UInt32?)
  PcmSamples(Array[Int32])
  PcmNeedInput
  PcmInputDone
}

///|
/// A bounded RIFF/RF64 or signed raw PCM reader. Unknown chunks are skipped
/// incrementally. RF64 size tables have an explicit 65536-entry resource limit.
pub struct PcmInput {
  mut pending : BytesView
  mut offset : Int64
  mut ended : Bool
  mut failed : FlacError?
  mut stage : Int32
  mut format : PcmFormat?
  mut announced : Bool
  mut mask : UInt32?
  mut width : Int32
  mut shift : Int32
  raw : Bool
  mut rf64 : Bool
  mut riff_end : Int64?
  mut chunk_left : Int64
  mut pad : Bool
  mut saw_data : Bool
  mut saw_ds64 : Bool
  mut data_size : Int64
  mut data_size_used : Bool
  mut expected_samples : Int64
  mut samples : Int64
  mut table_left : Int32
  sizes : Map[UInt32, Array[Int64]]
  size_positions : Map[UInt32, Int32]
}

///|
fn PcmInput::create(raw : Bool, format : PcmFormat?) -> PcmInput {
  {
    pending: b""[:],
    offset: 0L,
    ended: false,
    failed: None,
    stage: if raw {
      9
    } else {
      0
    },
    format,
    announced: false,
    mask: None,
    width: 0,
    shift: 0,
    raw,
    rf64: false,
    riff_end: None,
    chunk_left: 0L,
    pad: false,
    saw_data: false,
    saw_ds64: false,
    data_size: 0L,
    data_size_used: false,
    expected_samples: 0L,
    samples: 0L,
    table_left: 0,
    sizes: {},
    size_positions: {},
  }
}

///|
/// Read integer RIFF WAVE or RF64; floating-point PCM is explicitly rejected.
pub fn PcmInput::wav() -> PcmInput {
  PcmInput::create(false, None)
}

///|
/// Raw PCM is signed little-endian, sign-extended and unshifted in ceil(bits/8)
/// bytes. Zero sample rate is representable; the encoder requires non_audio.
pub fn PcmInput::raw(format : PcmFormat) -> PcmInput raise FlacError {
  pcm_input_validate(format, 0L)
  let reader = PcmInput::create(true, Some(format))
  reader.width = (format.bits_per_sample + 7) / 8
  reader
}

///|
fn pcm_input_validate(
  format : PcmFormat,
  offset : Int64,
) -> Unit raise FlacError {
  if format.channels < 1 ||
    format.channels > 8 ||
    format.bits_per_sample < 4 ||
    format.bits_per_sample > 32 ||
    format.sample_rate < 0 ||
    format.sample_rate > 1048575 {
    raise Invalid(offset, "PCM format outside FLAC dimensions")
  }
}

///|
/// Copies at most the remaining 64 KiB staging capacity; drain next and retry
/// any unaccepted suffix. Calling after finish is an error.
pub fn PcmInput::push(self : PcmInput, input : Bytes) -> Int32 raise FlacError {
  if self.failed is Some(error) {
    raise error
  }
  if self.ended {
    raise Invalid(self.offset, "PCM input after finish")
  }
  let room = 65536 - self.pending.length()
  if room == 0 {
    raise Limit("PCM input staging full; drain next")
  }
  let count = if input.length() < room { input.length() } else { room }
  if count == 0 {
    return 0
  }
  let old = self.pending
  let storage = FixedArray::make(old.length() + count, (0 : Byte))
  storage.blit_from_bytesview(0, old)
  storage.blit_from_bytes(old.length(), input, 0, count)
  // This allocation is exclusively owned and never mutated after freezing.
  // Both copies are bounded by 64 KiB; no view can retain the caller's input.
  self.pending = storage.unsafe_reinterpret_as_bytes()[:]
  count
}

///|
pub fn PcmInput::finish(self : PcmInput) -> Unit raise FlacError {
  if self.failed is Some(error) {
    raise error
  }
  self.ended = true
}

///|
pub fn PcmInput::buffered_bytes(self : PcmInput) -> Int32 {
  self.pending.length()
}

///|
fn PcmInput::consume(self : PcmInput, count : Int32) -> Unit {
  self.pending = if count == self.pending.length() {
    b""[:]
  } else {
    self.pending[count:]
  }
  self.offset += count.to_int64()
}

///|
fn pcm_le(data : BytesView, offset : Int32, width : Int32) -> Int64 {
  let mut result = 0L
  for i in 0.. Int64 raise FlacError {
  if data[offset + 7].to_int() >= 128 {
    raise Limit("WAV size exceeds signed 64-bit range")
  }
  let value = pcm_le(data, offset, 8)
  if value > 9223372036854775799L {
    raise Invalid(position, "WAV size cannot include RIFF header")
  }
  value
}

///|
fn PcmInput::incomplete(self : PcmInput) -> PcmInputEvent raise FlacError {
  if self.ended {
    raise Invalid(self.offset, "truncated PCM/WAV input")
  }
  PcmNeedInput
}

///|
fn PcmInput::format_body(self : PcmInput) -> Unit raise FlacError {
  let p = self.pending
  let tag = pcm_le(p, 0, 2).to_int()
  let channels = pcm_le(p, 2, 2).to_int()
  let rate = pcm_le(p, 4, 4)
  let byte_rate = pcm_le(p, 8, 4)
  let align = pcm_le(p, 12, 2).to_int()
  let container_bits = pcm_le(p, 14, 2).to_int()
  if tag != 1 && tag != 65534 {
    raise Invalid(
      self.offset,
      "only integer PCM WAV is supported (not float or compressed audio)",
    )
  }
  if container_bits != 8 &&
    container_bits != 16 &&
    container_bits != 24 &&
    container_bits != 32 {
    raise Invalid(self.offset, "WAV PCM container must be 8, 16, 24 or 32 bits")
  }
  if self.chunk_left > 16L &&
    (self.chunk_left < 18L || pcm_le(p, 16, 2) > self.chunk_left - 18L) {
    raise Invalid(self.offset, "WAV format extension exceeds its chunk")
  }
  let bits = if tag == 65534 {
    if self.chunk_left < 40L || pcm_le(p, 16, 2) < 22L {
      raise Invalid(self.offset, "short extensible WAV format")
    }
    if p[24:40].to_owned() !=
      b"\x01\x00\x00\x00\x00\x00\x10\x00\x80\x00\x00\xaa\x00\x38\x9b\x71" {
      raise Invalid(self.offset, "WAV extensible subformat is not integer PCM")
    }
    self.mask = Some(pcm_le(p, 20, 4).to_int().reinterpret_as_uint())
    pcm_le(p, 18, 2).to_int()
  } else {
    container_bits
  }
  if rate > 1048575L ||
    bits > container_bits ||
    align != channels * (container_bits / 8) ||
    byte_rate != rate * align.to_int64() {
    raise Invalid(
      self.offset,
      "inconsistent WAV format dimensions or byte rate",
    )
  }
  let format : PcmFormat = {
    sample_rate: rate.to_int(),
    channels,
    bits_per_sample: bits,
  }
  pcm_input_validate(format, self.offset)
  self.format = Some(format)
  self.width = container_bits / 8
  self.shift = container_bits - bits
}

///|
fn PcmInput::read_samples(
  self : PcmInput,
  format : PcmFormat,
  count : Int32,
) -> Array[Int32] raise FlacError {
  if self.shift == 0 && format.bits_per_sample == self.width * 8 {
    // step() supplies count <= pending.length(), divisible by width*channels,
    // with at most 4096 scalar samples. Validated raw/WAV widths are 1..4.
    // For i
        if self.raw {
          Array::makei(length, i => input.unsafe_get(i).to_int() << 24 >> 24)
        } else {
          Array::makei(length, i => input.unsafe_get(i).to_int() - 128)
        }
      2 =>
        Array::makei(length, i => {
          input.unsafe_read_uint16_le(i * 2).to_int() << 16 >> 16
        })
      3 =>
        Array::makei(length, i => {
          let at = i * 3
          let word = input.unsafe_read_uint16_le(at).to_int() |
            (input.unsafe_get(at + 2).to_int() << 16)
          word << 8 >> 8
        })
      4 =>
        Array::makei(length, i => {
          input.unsafe_read_uint32_le(i * 4).reinterpret_as_int()
        })
      _ => abort("validated PCM container width")
    }
    // Commit only after the complete owned output has been constructed.
    self.samples += (count / (self.width * format.channels)).to_int64()
    self.consume(count)
    return out
  }
  let out : Array[Int32] = []
  let minimum = -(1L << (format.bits_per_sample - 1))
  let maximum = (1L << (format.bits_per_sample - 1)) - 1L
  for i in 0..<(count / self.width) {
    let unsigned = pcm_le(self.pending, i * self.width, self.width)
    let value = if !self.raw && self.width == 1 {
      unsigned - 128L
    } else if (unsigned & (1L << (self.width * 8 - 1))) != 0L {
      unsigned - (1L << (self.width * 8))
    } else {
      unsigned
    }
    if !self.raw && (value & ((1L << self.shift) - 1L)) != 0L {
      raise Invalid(
        self.offset + (i * self.width).to_int64(),
        "nonzero WAV unused low bits",
      )
    }
    let sample = value >> self.shift
    if sample < minimum || sample > maximum {
      raise Invalid(
        self.offset + (i * self.width).to_int64(),
        "raw PCM sample is not sign-extended to its declared bit depth",
      )
    }
    out.push(sample.to_int())
  }
  self.samples += (count / (self.width * format.channels)).to_int64()
  self.consume(count)
  out
}

///|
fn PcmInput::step(self : PcmInput) -> PcmInputEvent raise FlacError {
  if !self.announced && self.format is Some(format) {
    self.announced = true
    return PcmFormatReady(format, self.mask)
  }
  while true {
    match self.stage {
      0 => {
        if self.pending.length() < 12 {
          return self.incomplete()
        }
        let signature = self.pending[:4].to_owned()
        if (signature != b"RIFF" && signature != b"RF64") ||
          self.pending[8:12].to_owned() != b"WAVE" {
          raise Invalid(0L, "input is not RIFF/RF64 WAVE")
        }
        let size = pcm_le(self.pending, 4, 4)
        self.rf64 = signature == b"RF64"
        if self.rf64 {
          if size != 4294967295L {
            raise Invalid(4L, "RF64 RIFF size must be 0xffffffff")
          }
        } else {
          if size < 4L {
            raise Invalid(4L, "short RIFF size")
          }
          self.riff_end = Some(size + 8L)
        }
        self.consume(12)
        self.stage = 1
      }
      1 => {
        if self.riff_end is Some(end) {
          if self.offset == end {
            self.stage = 8
            continue
          }
          if end - self.offset < 8L {
            raise Invalid(
              self.offset,
              "partial WAV chunk header within RIFF size",
            )
          }
        }
        if self.pending.length() < 8 {
          return self.incomplete()
        }
        let tag = self.pending[:4].to_owned()
        let id = pcm_le(self.pending, 0, 4).to_int().reinterpret_as_uint()
        let mut size = pcm_le(self.pending, 4, 4)
        if self.rf64 && !self.saw_ds64 && tag != b"ds64" {
          raise Invalid(self.offset, "RF64 requires ds64 as its first chunk")
        }
        if self.rf64 {
          if tag == b"data" && !self.data_size_used {
            if size != 4294967295L && size != self.data_size {
              raise Invalid(self.offset, "RF64 data size disagrees with ds64")
            }
            size = self.data_size
            self.data_size_used = true
          } else if size == 4294967295L {
            let index = self.size_positions.get(id).unwrap_or(0)
            match self.sizes.get(id) {
              Some(sizes) if index < sizes.length() => {
                size = sizes[index]
                self.size_positions[id] = index + 1
              }
              _ =>
                raise Invalid(
                  self.offset,
                  "missing RF64 chunk size table entry",
                )
            }
          }
        }
        self.consume(8)
        self.chunk_left = size
        self.pad = (size & 1L) == 1L
        if self.riff_end is Some(end) &&
          (size > end - self.offset || (self.pad && size == end - self.offset)) {
          raise Invalid(self.offset, "WAV chunk exceeds RIFF boundary")
        }
        if tag == b"fmt " {
          if self.format is Some(_) || size < 16L {
            raise Invalid(self.offset, "duplicate or short WAV format")
          }
          self.stage = 2
        } else if tag == b"ds64" {
          if !self.rf64 || self.saw_ds64 || size < 28L {
            raise Invalid(self.offset, "unexpected or short RF64 ds64 chunk")
          }
          self.stage = 3
        } else if tag == b"data" {
          let format = match self.format {
            Some(value) => value
            None => raise Invalid(self.offset, "WAV data precedes format")
          }
          if size % (self.width * format.channels).to_int64() != 0L {
            raise Invalid(
              self.offset,
              "WAV data size contains a partial sample frame",
            )
          }
          self.saw_data = true
          self.stage = 6
        } else {
          self.stage = 5
        }
      }
      2 => {
        if self.pending.length() < 16 {
          return self.incomplete()
        }
        let needed = if pcm_le(self.pending, 0, 2) == 65534L {
          40
        } else if self.chunk_left > 16L {
          18
        } else {
          16
        }
        if self.chunk_left < needed.to_int64() {
          raise Invalid(self.offset, "short WAV format chunk")
        }
        if self.pending.length() < needed {
          return self.incomplete()
        }
        self.format_body()
        self.consume(needed)
        self.chunk_left -= needed.to_int64()
        self.stage = 5
        self.announced = true
        return PcmFormatReady(self.format.unwrap(), self.mask)
      }
      3 => {
        if self.pending.length() < 28 {
          return self.incomplete()
        }
        let riff_size = pcm_u64_size(self.pending, 0, self.offset)
        let data_size = pcm_u64_size(self.pending, 8, self.offset + 8L)
        let expected = pcm_u64_size(self.pending, 16, self.offset + 16L)
        let entries = pcm_le(self.pending, 24, 4)
        if entries > 65536L {
          raise Limit("RF64 size table exceeds 65536 entries")
        }
        if 28L + entries * 12L > self.chunk_left ||
          riff_size + 8L <
          self.offset + self.chunk_left + (if self.pad { 1L } else { 0L }) {
          raise Invalid(self.offset, "invalid RF64 ds64 sizes")
        }
        self.riff_end = Some(riff_size + 8L)
        self.data_size = data_size
        self.expected_samples = expected
        self.table_left = entries.to_int()
        self.saw_ds64 = true
        self.consume(28)
        self.chunk_left -= 28L
        self.stage = 4
      }
      4 => {
        if self.table_left == 0 {
          self.stage = 5
          continue
        }
        if self.pending.length() < 12 {
          return self.incomplete()
        }
        let id = pcm_le(self.pending, 0, 4).to_int().reinterpret_as_uint()
        let size = pcm_u64_size(self.pending, 4, self.offset + 4L)
        match self.sizes.get(id) {
          Some(values) => values.push(size)
          None => self.sizes[id] = [size]
        }
        self.consume(12)
        self.chunk_left -= 12L
        self.table_left -= 1
      }
      5 => {
        if self.chunk_left == 0L {
          self.stage = 7
          continue
        }
        if self.pending.is_empty() {
          return self.incomplete()
        }
        let count = if self.chunk_left < self.pending.length().to_int64() {
          self.chunk_left.to_int()
        } else {
          self.pending.length()
        }
        self.consume(count)
        self.chunk_left -= count.to_int64()
      }
      6 | 9 => {
        if self.stage == 6 && self.chunk_left == 0L {
          self.stage = 7
          continue
        }
        let format = self.format.unwrap()
        let align = self.width * format.channels
        let available = if self.stage == 6 &&
          self.chunk_left < self.pending.length().to_int64() {
          self.chunk_left.to_int()
        } else {
          self.pending.length()
        }
        let frames = available / align
        let frame_limit = 4096 / format.channels
        let count = (if frames < frame_limit { frames } else { frame_limit }) *
          align
        if count == 0 {
          if self.stage == 9 && self.ended && self.pending.is_empty() {
            self.stage = 8
            continue
          }
          return self.incomplete()
        }
        let samples = self.read_samples(format, count)
        if self.stage == 6 {
          self.chunk_left -= count.to_int64()
        }
        return PcmSamples(samples)
      }
      7 => {
        if self.pad {
          if self.pending.is_empty() {
            return self.incomplete()
          }
          self.consume(1)
        }
        self.stage = 1
      }
      8 => {
        if !self.pending.is_empty() {
          raise Invalid(self.offset, "trailing bytes beyond WAV container")
        }
        if !self.ended {
          return PcmNeedInput
        }
        if !self.raw && !self.saw_data {
          raise Invalid(self.offset, "WAV contains no data chunk")
        }
        if self.expected_samples != 0L && self.samples != self.expected_samples {
          raise Invalid(
            self.offset,
            "RF64 sample count does not match PCM data",
          )
        }
        return PcmInputDone
      }
      _ => abort("unreachable PCM reader stage")
    }
  } nobreak {
    abort("unreachable PCM input state")
  }
}

///|
/// Pull a format, complete interleaved PCM group, input request or completion.
/// Errors are sticky, and truncated input is diagnosed after finish.
pub fn PcmInput::next(self : PcmInput) -> PcmInputEvent raise FlacError {
  if self.failed is Some(error) {
    raise error
  }
  self.step() catch {
    error => {
      self.failed = Some(error)
      raise error
    }
  }
}