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

///|
/// A datagram socket. One task may receive while others send; two tasks receiving at once
/// would each take half the datagrams, so a connection that needs one reader keeps one.
pub struct Socket {
  sock : @socket.UdpServer
}

///|
/// Bind `host:port`. Port 0 asks the system for a free one, which `addr` reports.
pub async fn bind(
  host~ : String = "127.0.0.1",
  port~ : Int32 = 12000,
) -> Socket {
  { sock: @socket.UdpServer::UdpServer(@socket.Addr::parse(join(host, port))), }
}

///|
/// The address bound; with port 0 asked for, the port the system chose.
pub fn Socket::addr(self : Socket) -> @socket.Addr {
  self.sock.addr
}

///|
/// Send one datagram.
pub async fn Socket::send(
  self : Socket,
  to : @socket.Addr,
  bytes : Bytes,
) -> Unit {
  self.sock.sendto(bytes, to)
}

///|
/// The next datagram, or `None` when `timeout` milliseconds pass first. Without a timeout
/// it waits for one, which is what a server does and a client polling a handshake does not.
pub async fn Socket::recv(
  self : Socket,
  max~ : Int32 = 65535,
  timeout? : Int32,
) -> (Bytes, @socket.Addr)? {
  let buffer = FixedArray::make(max, b'\x00')
  let take = async fn() {
    let (n, from) = self.sock.recvfrom(buffer)
    (Bytes::makei(n, i => buffer[i]), from)
  }
  match timeout {
    Some(ms) => @async.with_timeout_opt(ms, take)
    None => Some(take())
  }
}

///|
/// Stop using the socket.
pub fn Socket::close(self : Socket) -> Unit {
  self.sock.close()
}

///|
/// `host:port`, with an IPv6 literal in brackets (RFC 3986 ยง3.2.2).
fn join(host : String, port : Int32) -> String {
  if host.contains(":") && !host.has_prefix("[") {
    "[\{host}]:\{port}"
  } else {
    "\{host}:\{port}"
  }
}