///|
/// The assembled QUIC data path: a receive-side `StreamManager` routes STREAM
/// frames to per-stream reassembly; a send-side `QuicStreamScheduler`
/// round-robins streams capped by the frame size; the `QuicSender` packetizes
/// queued data and an ACK clears it; and a `QuicConnection` numbers its three
/// packet-number spaces and reassembles CRYPTO per level.
///
///     moon run src/examples/26-quic-sender --target native
#coverage.skip
fn main {
  run() catch {
    e => println("sender error: \{e}")
  }
}

///|
fn run() -> Unit raise {
  let m = @stream.Streams::new(100UL, 100UL, server=true)
  let _ = m.on_stream(0UL, 3UL, b"def", fin=false)
  let delivered = m.on_stream(0UL, 0UL, b"abc", fin=false)
  println("stream mgr delivered = \"\{ascii(delivered)}\" streams=\{m.count()}")
  let sch = @stream.Scheduler::new(
    @stream.Budget::new(1000UL, 1000UL),
    frame=300UL,
  )
  sch.queue(0UL, 500UL)
  sch.queue(4UL, 500UL)
  let d0 = next(sch)
  let d1 = next(sch)
  println(
    "scheduler: d0 stream=\{d0.stream.to_string()} len=\{d0.length.to_string()}, d1 stream=\{d1.stream.to_string()} len=\{d1.length.to_string()}",
  )
  let s = @conn.Sender::new(100000UL, 100000UL, 1200L, 25000L, 1000L, 8UL)
  s.queue_stream(0UL, b"abcdefghij")
  s.queue_fin(0UL)
  let (pn0, f0) = poll(s, 0L)
  let (pn1, _) = poll(s, 1L)
  println(
    "sender pn0=\{pn0.to_string()} pn1=\{pn1.to_string()} first packet carries \{f0.length()} frame(s)",
  )
  let ack : @frame.Frame = Ack(largest=1UL, delay=0UL, first_range=1UL, ranges=[])
  s.on_ack(ack, 10L)
  println("outstanding after ack = \{s.outstanding().length()}")
  let c = @conn.Conn::new(false)
  let _ = c.next_packet_number(@conn.Level::Initial)
  println(
    "next initial pn=\{c.next_packet_number(@conn.Level::Initial).to_string()} next handshake pn=\{c.next_packet_number(@conn.Level::Handshake).to_string()}",
  )
  let _ = c.on_crypto_frame(@conn.Level::Initial, 3UL, b"def")
  println(
    "crypto reassembled = \"\{ascii(c.on_crypto_frame(@conn.Level::Initial, 0UL, b"abc"))}\"",
  )
}

///|
fn next(sch : @stream.Scheduler) -> @stream.Chunk raise {
  match sch.next() {
    Some(d) => d
    None => abort("expected a scheduling decision")
  }
}

///|
fn poll(s : @conn.Sender, now : Int64) -> (Int64, Array[@frame.Frame]) raise {
  match s.poll_send(now) {
    Some(p) => p
    None => abort("expected a packet")
  }
}

///|
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()
}