///|
/// A QUIC packet built from the connection ID up, then taken back apart.
///
///     moon run src/examples/tour
#coverage.skip
fn main {
  // A varint says how long it is in its own first two bits (RFC 9000 §16).
  for v in [0UL, 63UL, 16383UL, 1073741823UL] {
    println("varint \{v} -> \{hex(@quic.encode(v))}")
  }

  // Frames are the payload's contents (§19). A CRYPTO frame carries the handshake.
  let frames : Array[@frame.Frame] = [
    Crypto(offset=0UL, data=b"the ClientHello would go here"),
    Ping,
    Padding(3),
  ]
  let payload = @packet.payload(frames)
  println(
    "payload            \{payload.length()} bytes, \{frames.length()} frames",
  )
  println("elicits an ACK     \{@packet.payload_elicits_ack(frames)}")

  // Both endpoints can compute the Initial keys from the client's connection ID
  // alone — no TLS exchange needed, which is what makes an Initial packet readable
  // by anyone on the path and protected against everyone off it (RFC 9001 §5.2).
  let dcid = b"\x83\x94\xc8\xf0\x3e\x51\x57\x08"
  let (client, server) = @crypto.initial(dcid[:])
  println("client key         \{hex(client.key)}")
  println("server key         \{hex(server.key)}")

  // The header a packet is sealed under: the invariant part, the Initial token, the
  // Length covering the packet number and the sealed payload (§17.2).
  let long : @packet.Long = {
    packet_type: Initial,
    type_specific: 0,
    version: 1,
    dcid,
    scid: b"\xc0\xff\xee",
  }
  let number = @packet.number(2L, None)
  let header = long.header(number[:], payload=payload.length() + 16)
  println("header             \{hex(header)}")

  // Seal the payload under that header, then hide the packet number (§5.3–§5.4).
  let wire = client.seal(header[:], payload[:], number=2L)
  println(
    "packet             \{wire.length()} bytes, first byte \{hex(wire[0:1].to_owned())}",
  )

  // The receiver reads the Length field — which protection leaves alone — to find
  // the packet number, recovers it against what it has already seen, and opens.
  guard client.open(wire[:], largest=1L) is Some((plain, pn)) else {
    println("the packet did not open")
    return
  }
  println("opened             packet number \{pn}, \{plain.length()} bytes")
  let back = @packet.read_payload(plain) catch {
    e => {
      println("the payload did not parse: \{e}")
      return
    }
  }
  println(
    "frames back        \{back.length()}, same as sent: \{back == frames}",
  )

  // An ACK reports the packet numbers seen as a largest and a run of gaps (§19.3.1).
  let seen = @frame.Seen::new()
  for pn in [0UL, 1UL, 2UL, 5UL, 6UL, 9UL] {
    seen.add(pn)
  }
  guard seen.fields() is Some((largest, first, pairs)) else { return }
  let ack : @frame.Frame = Ack(
    largest~,
    delay=0UL,
    first_range=first,
    ranges=pairs,
  )
  println("ack                \{hex(@frame.encode(ack))}")
  let acked = ack.acked() catch {
    e => {
      println("the ACK did not read back: \{e}")
      return
    }
  }
  for run in acked {
    println("  acknowledges     \{run.0}..\{run.1}")
  }
  // An endpoint declares its limits in a block the TLS handshake carries (§18).
  let mine = @conn.Limits::new(
    idle_timeout=30_000,
    data=1048576,
    stream_data_bidi_remote=65536,
    streams_bidi=100,
  )
  let block = @conn.encode(mine.params()[:])
  println(
    "params             \{block.length()} bytes, \{mine.params().length()} of them",
  )
  let back = @conn.decode(block[:]) catch { _ => return }
  println("read back          \{@conn.Limits::read(back[:]) == mine}")
  // Everything the block leaves out keeps RFC 9000 §18.2's default.
  println(
    "ack delay exponent \{@conn.Limits::read(back[:]).ack_delay_exponent}",
  )

  // A server holds one connection per connection id, and answers a datagram by it.
  let server : @conn.Endpoint[String] = @conn.Endpoint::new(
    @conn.Config::default(),
  )
  println("connections        \{server.count()}")
}

///|
fn hex(b : Bytes) -> String {
  let digits = "0123456789abcdef"
  let sb = StringBuilder()
  for i = 0; i < b.length(); i = i + 1 {
    let x = b[i].to_int()
    sb.write_char(digits[x >> 4].unsafe_to_char())
    sb.write_char(digits[x & 0xf].unsafe_to_char())
  }
  sb.to_string()
}