// This file is based on the Go implementation found here:
// https://cs.opensource.google/go/go/+/refs/tags/go1.23.1:src/compress/flate/dict_decoder.go
// which has the copyright notice:
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

///|
/// DictDecoder implements the LZ77 sliding dictionary as used in decompression.
/// LZ77 decompresses data through sequences of two forms of commands:
///
///   - Literal insertions: Runs of one or more symbols are inserted into the data
///     stream as is. This is accomplished through the write_byte method for a
///     single symbol, or combinations of write_slice/write_mark for multiple symbols.
///     Any valid stream must start with a literal insertion if no preset dictionary
///     is used.
///
///   - Backward copies: Runs of one or more symbols are copied from previously
///     emitted data. Backward copies come as the tuple (dist, length) where dist
///     determines how far back in the stream to copy from and length determines how
///     many bytes to copy. Note that it is valid for the length to be greater than
///     the distance. Since LZ77 uses forward copies, that situation is used to
///     perform a form of run-length encoding on repeated runs of symbols.
///     The write_copy and try_write_copy are used to implement this command.
///
/// For performance reasons, this implementation performs little to no sanity
/// checks about the arguments. As such, the invariants documented for each
/// method call must be respected.
priv struct DictDecoder {
  hist : Slice[Byte] // []byte // Sliding window history

  // Invariant: 0 <= rd_pos <= wr_pos <= len(hist)
  mut wr_pos : Int // Current output position in buffer
  mut rd_pos : Int // Have emitted hist[:rd_pos] already
  mut full : Bool // Has a full window length been written yet?
}

///|
/// DictDecoder::new initializes DictDecoder to have a sliding window dictionary of the given
/// size. If a preset dict is provided, it will initialize the dictionary with
/// the contents of dict.
fn DictDecoder::new(size : Int, dict : Slice[Byte]) -> DictDecoder {
  let dd = {
    hist: Slice::new(Array::make(size, b'\x00')),
    wr_pos: 0,
    rd_pos: 0,
    full: false,
  }
  let mut dict = dict
  if dict.length() > size {
    dict = dict[dict.length() - size:]
  }
  dd.wr_pos = slice_copy(dd.hist, dict)
  if dd.wr_pos == size {
    dd.wr_pos = 0
    dd.full = true
  }
  dd.rd_pos = dd.wr_pos
  dd
}

///|
/// hist_size reports the total amount of historical data in the dictionary.
fn DictDecoder::hist_size(self : DictDecoder) -> Int {
  if self.full {
    return self.hist.length()
  }
  self.wr_pos
}

///|
/// avail_read reports the number of bytes that can be flushed by read_flush.
fn DictDecoder::avail_read(self : DictDecoder) -> Int {
  self.wr_pos - self.rd_pos
}

///|
/// avail_write reports the available amount of output buffer space.
fn DictDecoder::avail_write(self : DictDecoder) -> Int {
  self.hist.length() - self.wr_pos
}

///|
/// write_slice returns a slice of the available buffer to write data to.
///
/// This invariant will be kept: len(s) <= avail_write()
fn DictDecoder::write_slice(self : DictDecoder) -> Slice[Byte] {
  self.hist[self.wr_pos:]
}

///|
/// write_mark advances the writer pointer by cnt.
///
/// This invariant must be kept: 0 <= cnt <= avail_write()
fn DictDecoder::write_mark(self : DictDecoder, cnt : Int) -> Unit {
  self.wr_pos += cnt
}

///|
/// write_byte writes a single byte to the dictionary.
///
/// This invariant must be kept: 0 < avail_write()
fn DictDecoder::write_byte(self : DictDecoder, c : Byte) -> Unit {
  self.hist[self.wr_pos] = c
  self.wr_pos += 1
}

///|
/// write_copy copies a string at a given (dist, length) to the output.
/// This returns the number of bytes copied and may be less than the requested
/// length if the available space in the output buffer is too small.
///
/// This invariant must be kept: 0 < dist <= hist_size()
fn DictDecoder::write_copy(self : DictDecoder, dist : Int, length : Int) -> Int {
  let dst_base = self.wr_pos
  let mut dst_pos = dst_base
  let mut src_pos = dst_pos - dist
  let mut end_pos = dst_pos + length
  if end_pos > self.hist.length() {
    end_pos = self.hist.length()
  }

  // Copy non-overlapping section after destination position.
  //
  // This section is non-overlapping in that the copy length for this section
  // is always less than or equal to the backwards distance. This can occur
  // if a distance refers to data that wraps-around in the buffer.
  // Thus, a backwards copy is performed here; that is, the exact bytes in
  // the source prior to the copy is placed in the destination.
  if src_pos < 0 {
    src_pos += self.hist.length()
    dst_pos += slice_copy(self.hist[dst_pos:end_pos], self.hist[src_pos:])
    src_pos = 0
  }

  // Copy possibly overlapping section before destination position.
  //
  // This section can overlap if the copy length for this section is larger
  // than the backwards distance. This is allowed by LZ77 so that repeated
  // strings can be succinctly represented using (dist, length) pairs.
  // Thus, a forwards copy is performed here; that is, the bytes copied is
  // possibly dependent on the resulting bytes in the destination as the copy
  // progresses along.
  while dst_pos < end_pos {
    dst_pos += slice_copy(
      self.hist[dst_pos:end_pos],
      self.hist[src_pos:dst_pos],
    )
  }

  //
  self.wr_pos = dst_pos
  return dst_pos - dst_base
}

///|
/// try_write_copy tries to copy a string at a given (distance, length) to the
/// output. This specialized version is optimized for short distances.
///
/// This method is designed to be inlined for performance reasons.
///
/// This invariant must be kept: 0 < dist <= hist_size()
fn DictDecoder::try_write_copy(
  self : DictDecoder,
  dist : Int,
  length : Int,
) -> Int {
  let mut dst_pos = self.wr_pos
  let end_pos = dst_pos + length
  if dst_pos < dist || end_pos > self.hist.length() {
    return 0
  }
  let dst_base = dst_pos
  let src_pos = dst_pos - dist

  // Copy possibly overlapping section before destination position.
  while dst_pos < end_pos {
    dst_pos += slice_copy(
      self.hist[dst_pos:end_pos],
      self.hist[src_pos:dst_pos],
    )
  }
  self.wr_pos = dst_pos
  return dst_pos - dst_base
}

///|
fn slice_copy(dst : Slice[Byte], src : Slice[Byte]) -> Int {
  let n = @cmp.minimum(dst.length(), src.length())
  for i = 0; i < n; i = i + 1 {
    dst[i] = src[i]
  }
  n
}

///|
/// read_flush returns a slice of the historical buffer that is ready to be
/// emitted to the user. The data returned by read_flush must be fully consumed
/// before calling any other DictDecoder methods.
fn DictDecoder::read_flush(self : DictDecoder) -> Slice[Byte] {
  let to_read = self.hist[self.rd_pos:self.wr_pos]
  self.rd_pos = self.wr_pos
  if self.wr_pos == self.hist.length() {
    self.wr_pos = 0
    self.rd_pos = 0
    self.full = true
  }
  return to_read
}