// The streaming encoder.
//
// Input is copied into `data`, a buffer that keeps the window of past input
// (for backward references) and the input not encoded yet, at most one
// meta-block of it. A full meta-block's worth is encoded as soon as it is
// there; `Flush` and `Finish` encode whatever is buffered. Compressed bytes
// wait in the bit writer until the caller's buffer takes them, and no more
// input is taken while they wait: memory stays bounded whatever the caller
// passes. The step model follows moonbit-community/flate's `Deflater`.

///|
/// What `Encoder::step` should do once it has taken all of its input.
pub(all) enum EncodeAction {
  /// Only take input.
  Continue
  /// Compress everything buffered and pad to a byte boundary, so that the
  /// output so far decodes to the input so far; the stream continues.
  Flush
  /// Compress everything buffered and end the stream.
  Finish
} derive(Eq, Debug)

///|
pub extend EncodeAction with Eq::{equal, not_equal}

///|
pub extend EncodeAction with @debug.Debug::{to_repr}

///|
/// The first allocation of the input buffer; it doubles as input grows.
const INITIAL_BUFFER : Int32 = 1024

///|
/// A streaming Brotli encoder (RFC 7932): greedy LZ77 with one prefix code
/// per category, about the ratio of gzip. Feed it input in chunks of any
/// size with `step` and collect output in slices of any size. It keeps the
/// window of past input (`1 << window_bits` bytes, and no more than the
/// input so far) plus at most a meta-block (64 KiB) of input and its
/// output, and fixed-size tables.
pub struct Encoder {
  priv window_bits : Int32
  // The longest distance: the window less 16 bytes (section 9.1).
  priv max_distance : Int32
  // The input buffer: `data[0..data_len)` is past input (the window) up to
  // `block_start`, then input not encoded yet. It grows up to `data_max`,
  // then slides down.
  priv mut data : FixedArray[Byte]
  priv mut data_len : Int32
  priv mut block_start : Int32
  priv data_max : Int32
  priv matcher : @lz77.Matcher
  // The distance of the latest `longest_match`.
  priv mut found_dist : Int32
  priv meta_block : MetaBlock
  // The last four distances, as the decoder keeps them.
  priv dist_ring : FixedArray[Int32]
  priv mut dist_ring_index : Int32
  // Output: complete bytes of `writer` from `drained` wait for the caller.
  priv writer : BitPacker
  priv mut drained : Int32
  priv mut header_written : Bool
  priv mut flush_requested : Bool
  priv mut finish_requested : Bool
  priv mut emitted_final : Bool
  priv mut done : Bool
  priv mut step_consumed : Int32
  priv mut step_produced : Int32
}

///|
/// An encoder at the start of a stream, with a window of
/// `1 << window_bits` bytes (clamped to 10..24). The default 22 is the
/// reference encoder's; a decoder needs up to that much memory.
pub fn Encoder::Encoder(window_bits~ : Int32 = 22) -> Encoder {
  let window_bits = window_bits.clamp(min=10, max=24)
  let window = 1 << window_bits
  // Room to slide by at least a quarter window (or a meta-block) at a time.
  let slack = (window >> 2).max(BLOCK_SIZE)
  {
    window_bits,
    max_distance: window - 16,
    data: FixedArray::make(0, b'\x00'),
    data_len: 0,
    block_start: 0,
    data_max: window - 16 + slack + BLOCK_SIZE,
    matcher: @lz77.Matcher(hash_bits=window_bits.min(15)),
    found_dist: 0,
    meta_block: MetaBlock::new(),
    dist_ring: [16, 15, 11, 4],
    dist_ring_index: 0,
    writer: BitPacker::new(),
    drained: 0,
    header_written: false,
    flush_requested: false,
    finish_requested: false,
    emitted_final: false,
    done: false,
    step_consumed: 0,
    step_produced: 0,
  }
}

///|
/// Starts a new stream, keeping the tables' memory. The input buffer is
/// released.
pub fn Encoder::reset(self : Encoder) -> Unit {
  self.data = FixedArray::make(0, b'\x00')
  self.data_len = 0
  self.block_start = 0
  self.matcher.reset()
  self.dist_ring[0] = 16
  self.dist_ring[1] = 15
  self.dist_ring[2] = 11
  self.dist_ring[3] = 4
  self.dist_ring_index = 0
  self.writer.clear()
  self.writer.acc = 0
  self.writer.nbits = 0
  self.drained = 0
  self.header_written = false
  self.flush_requested = false
  self.finish_requested = false
  self.emitted_final = false
  self.done = false
  self.step_consumed = 0
  self.step_produced = 0
}

///|
/// Whether the stream has ended and all its output has been handed out.
pub fn Encoder::is_finished(self : Encoder) -> Bool {
  self.done
}

///|
/// Input bytes the latest `step` took.
pub fn Encoder::last_consumed(self : Encoder) -> Int32 {
  self.step_consumed
}

///|
/// Output bytes the latest `step` wrote.
pub fn Encoder::last_produced(self : Encoder) -> Int32 {
  self.step_produced
}

///|
/// Compresses from `input` into `output`; see `last_consumed` and
/// `last_produced` for how much of each it used. Input that was not taken
/// (when the output is full) must be passed again, with the same action.
///
/// `action` applies once all of `input` is taken: `Flush` makes the output
/// so far decode to the input so far, `Finish` ends the stream. Both stay
/// in effect while the step answers `NeedMoreOutput`. Status: `NeedMoreInput`
/// once everything is taken and its output handed out (a `Flush` is then
/// complete), `NeedMoreOutput` while output waits, `Done` once the stream
/// has ended and been handed out; after that, steps take no input and
/// answer `Done`.
pub fn Encoder::step(
  self : Encoder,
  input : BytesView,
  output : MutArrayView[Byte],
  action~ : EncodeAction = Continue,
) -> Status {
  self.step_with(input, output, None, action)
}

///|
/// `step` into a whole `FixedArray`, with bulk copies.
pub fn Encoder::step_into(
  self : Encoder,
  input : BytesView,
  output : FixedArray[Byte],
  action~ : EncodeAction = Continue,
) -> Status {
  self.step_with(input, output.mut_view(), Some(output), action)
}

///|
fn Encoder::step_with(
  self : Encoder,
  input : BytesView,
  output : MutArrayView[Byte],
  fixed_output : FixedArray[Byte]?,
  action : EncodeAction,
) -> Status {
  self.step_consumed = 0
  self.step_produced = 0
  if self.done {
    return Done
  }
  if input.length() == 0 {
    self.latch(action)
  }
  let mut consumed = 0
  let mut produced = 0
  for ;; {
    produced = self.drain(output, fixed_output, produced)
    if self.drained < self.writer.len {
      return self.finish_step(NeedMoreOutput, consumed, produced)
    }
    self.writer.clear()
    self.drained = 0
    if self.emitted_final {
      self.done = true
      return self.finish_step(Done, consumed, produced)
    }
    if self.finish_requested {
      self.emit_final()
      continue
    }
    if self.flush_requested {
      self.emit_flush()
      continue
    }
    let buffered = self.data_len - self.block_start
    if buffered == BLOCK_SIZE {
      self.write_header()
      self.encode_block(self.block_start, self.data_len, false)
      self.block_start = self.data_len
      continue
    }
    if consumed == input.length() {
      return self.finish_step(NeedMoreInput, consumed, produced)
    }
    let take = (input.length() - consumed).min(BLOCK_SIZE - buffered)
    self.append(input[consumed:consumed + take])
    consumed += take
    if consumed == input.length() {
      self.latch(action)
    }
  }
}

///|
fn Encoder::finish_step(
  self : Encoder,
  status : Status,
  consumed : Int32,
  produced : Int32,
) -> Status {
  self.step_consumed = consumed
  self.step_produced = produced
  status
}

///|
/// Takes `action` once its input is all taken; a finished stream takes no
/// more requests.
fn Encoder::latch(self : Encoder, action : EncodeAction) -> Unit {
  guard !self.finish_requested else { return }
  match action {
    Continue => ()
    Flush => self.flush_requested = true
    Finish => self.finish_requested = true
  }
}

///|
/// Hands out waiting output into `output` from `pos`; the new position.
fn Encoder::drain(
  self : Encoder,
  output : MutArrayView[Byte],
  fixed_output : FixedArray[Byte]?,
  pos : Int32,
) -> Int32 {
  let n = (self.writer.len - self.drained).min(output.length() - pos)
  guard n > 0 else { return pos }
  let buf = self.writer.buf
  match fixed_output {
    Some(out) => FixedArray::unsafe_blit(out, pos, buf, self.drained, n)
    None =>
      for i in 0.. Unit {
  let n = bytes.length()
  if self.data_len + n > self.data.length() &&
    self.data.length() < self.data_max {
    let mut size = self.data.length().max(INITIAL_BUFFER)
    while size < self.data_len + n {
      size = size * 2
    }
    let grown = FixedArray::make(size.min(self.data_max), b'\x00')
    FixedArray::unsafe_blit(grown, 0, self.data, 0, self.data_len)
    self.data = grown
  }
  if self.data_len + n > self.data.length() {
    // Keep the window before the input not encoded yet. `data_max` leaves
    // room for at least a meta-block, so the shift is at least that.
    let shift = self.block_start - self.max_distance
    FixedArray::unsafe_blit(
      self.data,
      0,
      self.data,
      shift,
      self.data_len - shift,
    )
    self.data_len -= shift
    self.block_start -= shift
    self.matcher.slide(shift)
  }
  self.data.blit_from_bytesview(self.data_len, bytes)
  self.data_len += n
}

///|
/// The stream header: WBITS (section 9.1), before the first meta-block.
fn Encoder::write_header(self : Encoder) -> Unit {
  guard !self.header_written else { return }
  self.header_written = true
  let w = self.writer
  let wbits = self.window_bits
  if wbits == 16 {
    w.write(1, 0)
  } else if wbits == 17 {
    w.write(7, 1)
  } else if wbits > 17 {
    w.write(4, 1 | ((wbits - 17) << 1))
  } else {
    w.write(7, 1 | ((wbits - 8) << 4))
  }
}

///|
/// Encodes the buffered input and pads to a byte boundary with an empty
/// metadata meta-block, as the reference encoder's flush does.
fn Encoder::emit_flush(self : Encoder) -> Unit {
  self.flush_requested = false
  self.write_header()
  if self.block_start < self.data_len {
    self.encode_block(self.block_start, self.data_len, false)
    self.block_start = self.data_len
  }
  if self.writer.nbits > 0 {
    // ISLAST 0, MNIBBLES 0 (a metadata block), reserved 0, MSKIPBYTES 0.
    self.writer.write(6, 0b000110)
    self.writer.align()
  }
}

///|
/// Encodes the buffered input as the last meta-block and pads the stream.
fn Encoder::emit_final(self : Encoder) -> Unit {
  self.write_header()
  if self.block_start < self.data_len {
    self.encode_block(self.block_start, self.data_len, true)
    self.block_start = self.data_len
  } else {
    // ISLAST and ISLASTEMPTY.
    self.writer.write(2, 3)
  }
  self.writer.align()
  self.emitted_final = true
}