// moonflac-encoder — pure MoonBit FLAC encoder.
// Copyright 2026 Sakuraqwq1
// Licensed under the Apache License, Version 2.0.

///|
/// Incremental encoder: feed PCM as it arrives, get FLAC frames back.
///
/// The one-shot [`encode`](encode.mbt) needs the whole signal because STREAMINFO
/// carries the total sample count and the MD5 of the unencoded PCM. A stream
/// does not have either up front, so this encoder writes both fields as zero,
/// which RFC 9639 defines as "unknown". The reference `flac` tool does the same
/// when its output is not seekable. A decoder still gets a valid stream, it just
/// cannot use the digest to verify the decode.
pub struct StreamEncoder {
  sample_rate : Int32
  channels : Int32
  bits : Int32
  mut frame_number : Int32
  buffer : Array[Int64]
  mut filled : Int32
}

///|
/// Start a stream with the given parameters. Nothing is emitted until
/// [`StreamEncoder::header`] or [`StreamEncoder::write`] is called.
pub fn StreamEncoder::new(
  sample_rate : Int32,
  channels : Int32,
  bits_per_sample : Int32,
) -> StreamEncoder raise EncodeError {
  if channels < 1 || channels > 8 {
    raise BadChannels(channels)
  }
  if bits_per_sample < 4 || bits_per_sample > 32 {
    raise BadBits(bits_per_sample)
  }
  if sample_rate <= 0 {
    raise BadSampleRate(sample_rate)
  }
  StreamEncoder::{
    sample_rate,
    channels,
    bits: bits_per_sample,
    frame_number: 0,
    buffer: Array::make(BLOCK_SIZE * channels, 0L),
    filled: 0,
  }
}

///|
/// The `fLaC` marker and the STREAMINFO block, with the sample count and PCM
/// digest left unknown. Emit this once, before the first frame.
pub fn StreamEncoder::header(self : StreamEncoder) -> Bytes {
  let out = @buffer.Buffer::Buffer(size_hint=64)
  out.write_string_utf8("fLaC")
  out.write_byte(0x80)
  out.write_byte(0x00)
  out.write_byte(0x00)
  out.write_byte(34)
  let info = @primitives.BitWriter::new()
  info.write_bits(BLOCK_SIZE.to_int64(), 16)
  info.write_bits(BLOCK_SIZE.to_int64(), 16)
  info.write_bits(0L, 24)
  info.write_bits(0L, 24)
  info.write_bits(self.sample_rate.to_int64(), 20)
  info.write_bits((self.channels - 1).to_int64(), 3)
  info.write_bits((self.bits - 1).to_int64(), 5)
  info.write_bits(0L, 36)
  let body = info.finish()
  for index = 0; index < body.length(); index = index + 1 {
    out.write_byte(body.at(index))
  }
  // Unknown digest: sixteen zero bytes.
  for _index = 0; _index < 16; _index = _index + 1 {
    out.write_byte(0x00)
  }
  out.to_bytes()
}

///|
/// Push interleaved PCM and receive whatever complete frames that produced.
///
/// The result is usually empty for small inputs: samples are buffered until a
/// full block is available, because frame size is what the format trades
/// compression against latency for.
pub fn StreamEncoder::write(
  self : StreamEncoder,
  samples : Array[Int64],
) -> Bytes raise EncodeError {
  if samples.length() % self.channels != 0 {
    raise NotWholeFrames(samples.length(), self.channels)
  }
  let out = @buffer.Buffer::Buffer(size_hint=1024)
  let frames = samples.length() / self.channels
  let mut index = 0
  while index < frames {
    let room = BLOCK_SIZE - self.filled
    let remaining = frames - index
    let take = if remaining < room { remaining } else { room }
    for offset = 0; offset < take; offset = offset + 1 {
      for channel = 0; channel < self.channels; channel = channel + 1 {
        self.buffer[(self.filled + offset) * self.channels + channel] = samples[(
            index + offset
          ) *
          self.channels +
          channel]
      }
    }
    self.filled = self.filled + take
    index = index + take
    if self.filled == BLOCK_SIZE {
      let bytes = build_frame(
        self.buffer,
        self.channels - 1,
        self.channels,
        0,
        BLOCK_SIZE,
        self.frame_number,
        self.sample_rate,
        self.bits,
      )
      for byte = 0; byte < bytes.length(); byte = byte + 1 {
        out.write_byte(bytes.at(byte))
      }
      self.frame_number = self.frame_number + 1
      self.filled = 0
    }
  }
  out.to_bytes()
}

///|
/// Flush the final partial block. Call once, after the last
/// [`StreamEncoder::write`]; further calls return nothing.
pub fn StreamEncoder::finish(self : StreamEncoder) -> Bytes {
  let out = @buffer.Buffer::Buffer(size_hint=1024)
  if self.filled > 0 {
    let bytes = build_frame(
      self.buffer,
      self.channels - 1,
      self.channels,
      0,
      self.filled,
      self.frame_number,
      self.sample_rate,
      self.bits,
    )
    for byte = 0; byte < bytes.length(); byte = byte + 1 {
      out.write_byte(bytes.at(byte))
    }
    self.frame_number = self.frame_number + 1
    self.filled = 0
  }
  out.to_bytes()
}