// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

// Pacing (RFC 9002 ยง7.7): an endpoint spreads a window over the round trip instead of putting
// all of it on the wire at once. A burst fills a bottleneck queue and then loses a whole window
// of packets, which costs more than the slower start did. The pacer is a token bucket: bytes
// accrue at the congestion controller's rate, and a packet may go when its share has accrued.
// It holds no clock, so a caller drives it with the same `now` every other timer uses.

///|
/// How many datagrams may leave back to back before the rate applies: enough for the ack clock
/// to keep the pipe full, small enough not to make a queue.
let burst : Int64 = 10

///|
/// A token bucket over bytes, at a rate in bytes per microsecond. A rate of zero means the
/// connection is not paced and everything is allowed.
pub struct Pacer {
  priv mut tokens : Double
  /// When the bucket was last brought up to date.
  priv mut at : Int64
  priv mut rate : Double
  priv burst : Double
  priv mut started : Bool
}

///|
/// A pacer that lets everything through until a rate is set.
pub fn Pacer::new(burst_bytes~ : Int64 = burst * 1200L) -> Pacer {
  {
    tokens: 0.0,
    at: 0L,
    rate: 0.0,
    burst: burst_bytes.to_double(),
    started: false,
  }
}

///|
/// The rate in bytes per microsecond; zero when unpaced.
pub fn Pacer::rate(self : Pacer) -> Double {
  self.rate
}

///|
/// Set the rate, in bytes per microsecond. Zero turns pacing off. The bucket starts full, so
/// the first burst after a quiet period is not delayed.
pub fn Pacer::set_rate(self : Pacer, rate : Double) -> Unit {
  if rate < 0.0 {
    self.rate = 0.0
  } else {
    self.rate = rate
  }
  if !self.started {
    self.tokens = self.burst
    self.started = true
  }
}

///|
/// Bring the bucket up to `now`.
fn Pacer::refill(self : Pacer, now : Int64) -> Unit {
  if !self.started {
    self.at = now
    self.tokens = self.burst
    self.started = true
    return
  }
  if now > self.at && self.rate > 0.0 {
    self.tokens = self.tokens + (now - self.at).to_double() * self.rate
    if self.tokens > self.burst {
      self.tokens = self.burst
    }
  }
  self.at = now
}

///|
/// Whether `bytes` may go at `now`. Unpaced, always.
pub fn Pacer::allow(self : Pacer, now : Int64, bytes : Int64) -> Bool {
  if self.rate <= 0.0 {
    return true
  }
  self.refill(now)
  self.tokens >= bytes.to_double()
}

///|
/// Take `bytes` out of the bucket.
pub fn Pacer::spend(self : Pacer, now : Int64, bytes : Int64) -> Unit {
  if self.rate <= 0.0 {
    return
  }
  self.refill(now)
  self.tokens = self.tokens - bytes.to_double()
  if self.tokens < 0.0 {
    self.tokens = 0.0
  }
}

///|
/// When `bytes` will have accrued, or `None` when it already has or nothing is paced. A driver
/// arms a timer on this and comes back for the packet.
pub fn Pacer::next_at(self : Pacer, now : Int64, bytes : Int64) -> Int64? {
  if self.rate <= 0.0 {
    return None
  }
  self.refill(now)
  if self.tokens >= bytes.to_double() {
    return None
  }
  let missing = bytes.to_double() - self.tokens
  // Rounded up: a truncated microsecond would come back a hair short and wait again.
  let wait = (missing / self.rate).ceil().to_int64()
  Some(now + (if wait < 1L { 1L } else { wait }))
}