// Per-message compression (RFC 1952 gzip over RFC 1951 DEFLATE, and the raw DEFLATE stream
// gRPC calls `deflate`): `gunzip` inflates a gzip member, the client engine transparently
// inflates a `grpc-encoding: gzip` reply whose message has the compression flag set, and
// the negotiation decides which encoding a reply may use. The vectors are real gzip
// members (fixed-Huffman, stored, and one with an FNAME field); the last is delivered as a
// compressed reply the caller receives already decompressed.
//
//     moon run src/examples/16-gzip

///|
fn ascii(bs : Bytes) -> String {
  let buf = StringBuilder()
  for b in bs {
    buf.write_char((b.to_int() & 0xFF).unsafe_to_char())
  }
  buf.to_string()
}

///|
fn run() -> Unit raise {
  // Which encoding a reply may go out under: the server's choice, kept only when the
  // caller listed it, and identity when it did not.
  let accepts = b"identity,gzip,deflate"
  println(
    "negotiated with \"\{ascii(accepts)}\": gzip -> \{@moonrpc.negotiated(Gzip, accepts[:]).code()}",
  )
  println(
    "negotiated with \"identity\":   gzip -> \{@moonrpc.negotiated(Gzip, b"identity"[:]).code()}",
  )
  // A body survives both encodings, and both shrink a repetitive one.
  let body = b"the quick brown fox jumps over the lazy dog. ".repeat(16)
  let all : Array[@moonrpc.Encoding] = [Identity, Gzip, Deflate]
  for encoding in all {
    let encoded = encoding.encode(body[:])
    let back = encoding.decode(encoded[:]).unwrap()
    println(
      "\{encoding.code()}: \{body.length()} -> \{encoded.length()} octets, round trip \{back == body}",
    )
  }

  // Three real gzip members, one per DEFLATE block type / optional field.
  let fixed = @base16.decode_lossy(
    "1f8b0800cb9b6a6a02ffcb48cdc9c9d75148afca2c5028cf2fca490100eb83468211000000",
  )
  let stored = @base16.decode_lossy(
    "1f8b0800fe9d6a6a04ff011d00e2ff73746f72656420626c6f636b2c206e6f20687566666d616e20686572652b46bf561d000000",
  )
  let named = @base16.decode_lossy(
    "1f8b08080000000002ff68692e74787400cb4bcc4d4d51c84dcd4d4a2d522848acccc94f4c01004e5eca2a14000000",
  )
  println("gunzip fixed-Huffman: \{ascii(@gzip.gunzip(fixed[:]))}")
  println("gunzip stored block:  \{ascii(@gzip.gunzip(stored[:]))}")
  println("gunzip FNAME member:  \{ascii(@gzip.gunzip(named[:]))}")

  // The client-side path: a peer replies `grpc-encoding: gzip` with a compression-flagged
  // message; the caller gets the plaintext, never the gzip bytes.
  let client = @moonrpc.H2Client::new()
  let (id, _req) = client.unary("/echo.Echo/Say", b"x")
  let _ = client.feed(Settings(params=[], ack=false))
  let enc = @hpack.Encoder::new()
  let hblock = enc.encode([
    { name: b":status", value: b"200", },
    { name: b"content-type", value: b"application/grpc", },
    { name: b"grpc-encoding", value: b"gzip", },
  ])
  let _ = client.feed(
    Headers(
      stream_id=id,
      fragment=hblock,
      end_stream=false,
      end_headers=true,
      priority=None,
      padding=0,
    ),
  )
  let _ = client.feed(
    Data(
      stream_id=id,
      data=@moonrpc.encode_message(fixed, compressed=true),
      end_stream=false,
      padding=0,
    ),
  )
  let tblock = enc.encode([{ name: b"grpc-status", value: b"0", }])
  let _ = client.feed(
    Headers(
      stream_id=id,
      fragment=tblock,
      end_stream=true,
      end_headers=true,
      priority=None,
      padding=0,
    ),
  )
  let reply = client.reply(id)
  println(
    "gzip reply inflated by the client: \{ascii(reply.messages[0])} (grpc-status \{reply.grpc_status})",
  )
}

///|
#coverage.skip
fn main {
  run() catch {
    e => println("error: \{e}")
  }
}