// The QUIC server event loop over a real UDP socket (RFC 9000 §5). `QuicServer` is the whole
// state machine but holds neither socket nor clock; this file gives it both. One task pulls
// datagrams off the endpoint and demultiplexes them onto connections, another fires the timers
// on a fixed tick, and both flush whatever the connection table has to send afterwards — which
// is where `QuicSender::poll_send` finally meets a socket. Native-only, because the socket is.

///|
/// The event loop's clock, in the microseconds the recovery and idle timers are measured in.
/// `@async.now()` counts milliseconds since the epoch.
pub fn quic_now() -> Int64 {
  @async.now() * 1000L
}

///|
/// Push every datagram the server has ready onto `ep`, until its queues are empty or the
/// anti-amplification limit stops it.
async fn quic_pump(
  ep : QuicUdpEndpoint,
  server : QuicServer[@socket.Addr],
) -> Unit {
  for ;; {
    match server.poll_out(quic_now()) {
      Some((datagram, to)) => ep.send_datagram(datagram, to)
      None => break
    }
  }
}

///|
/// Serve QUIC on `ep` until the enclosing task group is cancelled: one task reads datagrams
/// and demultiplexes them onto connections, the other runs the idle, probe and closing-period
/// timers every `tick_ms` milliseconds, and both push out whatever that left ready to send.
/// A datagram that names no connection or fails to authenticate is dropped rather than
/// killing the loop — an endpoint cannot tell a stray packet from an attack.
pub async fn quic_serve_endpoint(
  ep : QuicUdpEndpoint,
  server : QuicServer[@socket.Addr],
  tick_ms? : Int = 20,
) -> Unit {
  @async.with_task_group(g => {
    let _ : @async.Task[Unit] = g.spawn(() => {
      for ;; {
        let (datagram, from) = ep.recv_datagram()
        server.recv(datagram, from, quic_now()) catch {
          _ => ()
        }
        quic_pump(ep, server) catch {
          _ => ()
        }
      }
    })
    for ;; {
      @async.sleep(tick_ms)
      server.tick(quic_now())
      quic_pump(ep, server) catch {
        _ => ()
      }
    }
  })
}

///|
/// Bind a UDP socket at `addr` (e.g. `"0.0.0.0:443"`) and serve `server` on it. The socket
/// outlives the loop deliberately: closing one a task is parked in `recvfrom` on wedges it
/// rather than waking it, so shutdown is cancellation of the task group, not a close.
pub async fn quic_serve(
  addr : String,
  server : QuicServer[@socket.Addr],
  tick_ms? : Int = 20,
) -> Unit {
  quic_serve_endpoint(QuicUdpEndpoint::bind(addr), server, tick_ms~)
}