///|
/// QUIC loss recovery and congestion control (RFC 9002): NewReno slow-start and
/// the halving on a congestion signal (§7); the RTT estimator's smoothed_rtt /
/// rttvar / PTO (§5.3, §6.2.1); and the composed `QuicRecovery` loop sampling an
/// RTT and freeing bytes on an ACK.
///
///     moon run src/examples/24-quic-recovery --target native
#coverage.skip
fn main {
  run() catch {
    e => println("recovery error: \{e}")
  }
}

///|
fn run() -> Unit raise {
  let cc = @recovery.NewReno::new(policy=@recovery.Policy::new(datagram=1200L))
  println("initial cwnd = \{cc.window().to_string()}")
  cc.on_ack(1200L)
  println("after ack cwnd = \{cc.window().to_string()} (slow start)")
  cc.on_congestion(at=0L, now=0L)
  println(
    "after loss cwnd = \{cc.window().to_string()} ssthresh=\{cc.ssthresh().to_string()}",
  )
  let rtt = @recovery.Rtt::new()
  rtt.sample(100000L, ack_delay=0L)
  rtt.sample(80000L, ack_delay=5000L)
  println(
    "rtt: smoothed=\{rtt.smoothed().to_string()} var=\{rtt.variation().to_string()} min=\{rtt.min().to_string()} pto=\{rtt.pto().to_string()}",
  )
  let rec = @recovery.State::new(
    policy=@recovery.Policy::new(
      datagram=1200L,
      ack_delay=25000L,
      granularity=1000L,
    ),
  )
  rec.on_sent(0L, at=0L, size=1200L)
  let ack : @frame.Frame = Ack(largest=0UL, delay=0UL, first_range=0UL, ranges=[])
  let lost = rec.on_ack(ack, 100L, ack_delay=0L)
  println(
    "after ack: in_flight=\{rec.in_flight().to_string()} smoothed_rtt=\{rec.rtt().smoothed().to_string()} window=\{rec.window().to_string()} lost=\{lost.length()}",
  )
  // The sent-packet tracker underneath: record two sends, acknowledge one.
  let tr = @recovery.Flight::new()
  tr.on_sent(0L, at=0L, size=1200L)
  tr.on_sent(1L, at=100L, size=1200L)
  let acked_bytes = tr.on_ack([(1L, 1L)])
  println(
    "tracker: acked_bytes=\{acked_bytes.to_string()} largest_acked=\{tr.largest_acked().to_string()} outstanding=\{tr.outstanding().length()}",
  )
}