///|
/// The QPACK dynamic table (RFC 9204 §3.2): encoder-stream instructions (set
/// capacity, insert with a static/dynamic name reference, insert literal,
/// duplicate) build the table, absolute and relative indexing read it, an
/// instruction round-trips through the encoder-stream codec, and a dynamic
/// encode splits the encoder-stream inserts from the field section.
///
///     moon run src/examples/29-qpack-dynamic --target native
#coverage.skip
fn main {
  run() catch {
    e => println("qpack dyn error: \{e}")
  }
}

///|
fn run() -> Unit raise {
  let t = @qpack.Table::new(4096)
  let _ = t.apply(SetCapacity(220))
  let _ = t.apply(Insert(is_static=true, index=0, value=b"www.example.com"))
  let _ = t.apply(InsertLiteral(name=b"custom-key", value=b"custom-value"))
  let _ = t.apply(Duplicate(index=0))
  println(
    "table: entries=\{t.entries.length()} insert_count=\{t.insert_count} used=\{t.used}",
  )
  println("get(0)=\{pairb(t.get(0))} get_relative(0)=\{pairb(t.relative(0))}")
  let inst = @qpack.Change::encode(
    InsertLiteral(name=b"k", value=b"v"),
    huffman=false,
  )
  match @qpack.Change::decode(inst[:]) {
    Some((_, n)) =>
      println("encoder inst round-trips, consumed \{n} of \{inst.length()}")
    None => println("bad instruction")
  }
  // Encode headers with dynamic indexing (max_entries = negotiated capacity),
  // then apply the encoder-stream to a peer table and decode the section back.
  let headers = [
    @header.Header::of(":authority", "www.example.com"),
    @header.Header::of("custom-key", "custom-value"),
  ]
  let dt = @qpack.Table::new(4096)
  let (insts, section) = @qpack.encode_with(headers, dt, 128, huffman=false)
  println(
    "dynamic encode: \{insts.length()} encoder-stream bytes, \{section.length()} field-section bytes, inserts=\{dt.insert_count}",
  )
  let peer = @qpack.Table::new(4096)
  let view = insts[:]
  let mut off = 0
  while off < insts.length() {
    match @qpack.Change::decode(view[off:]) {
      Some((got, consumed)) => {
        let _ = peer.apply(got)
        off = off + consumed
      }
      None => break
    }
  }
  println(
    "dynamic round-trip = \{@qpack.decode_with(section, peer, 128) == headers}",
  )
  // The decoder-stream acknowledgement direction (RFC 9204 §4.4).
  let dinst = @qpack.Ack::encode(Section(stream_id=4))
  match @qpack.Ack::decode(dinst[:]) {
    Some((got, _)) =>
      println(
        "decoder inst (Section) round-trip = \{got == Section(stream_id=4)}",
      )
    None => println("bad decoder inst")
  }
}

///|
fn pairb(o : (Bytes, Bytes)?) -> String {
  match o {
    Some((n, v)) => ascii(n) + " " + ascii(v)
    None => "(none)"
  }
}

///|
fn ascii(b : Bytes) -> String {
  let sb = StringBuilder()
  for i = 0; i < b.length(); i = i + 1 {
    sb.write_char(b[i].to_int().unsafe_to_char())
  }
  sb.to_string()
}