///|
type SockaddrIn Bytes

///|
extern "c" fn uv_sockaddr_in_make() -> SockaddrIn = "moonbit_uv_sockaddr_in_make"

///|
type Tcp

///|
pub impl ToHandle for Tcp with to_handle(self : Tcp) -> Handle = "%identity"

///|
pub impl ToHandle for Tcp with of_handle(self : Handle) -> Tcp = "%identity"

///|
pub impl ToStream for Tcp with to_stream(self : Tcp) -> Stream = "%identity"

///|
pub impl ToStream for Tcp with of_stream(self : Stream) -> Tcp = "%identity"

///|
extern "c" fn uv_tcp_make() -> Tcp = "moonbit_uv_tcp_make"

///|
extern "c" fn uv_tcp_init(uv : Loop, tcp : Tcp) -> Int = "moonbit_uv_tcp_init"

///|
pub fn Tcp::new(uv : Loop) -> Tcp raise Errno {
  let tcp = uv_tcp_make()
  let result = uv_tcp_init(uv, tcp)
  if result < 0 {
    raise Errno::of_int(result)
  }
  tcp
}

///|
extern "c" fn uv_ip4_addr(ip : Bytes, port : Int, addr : SockaddrIn) = "moonbit_uv_ip4_addr"

///|
pub fn[Ntbs : ToNtbs] ip4_addr(ip : Ntbs, port : Int) -> SockaddrIn {
  let ip = ip.to_ntbs()
  let addr = uv_sockaddr_in_make()
  uv_ip4_addr(ip, port, addr)
  addr
}

///|
type Sockaddr Bytes

///|
pub impl ToSockaddr for Sockaddr with to_sockaddr(self : Sockaddr) -> Sockaddr = "%identity"

///|
pub impl ToSockaddr for SockaddrIn with to_sockaddr(addr : SockaddrIn) -> Sockaddr = "%identity"

///|
extern "c" fn uv_tcp_bind(tcp : Tcp, addr : Sockaddr, flags : UInt) -> Int = "moonbit_uv_tcp_bind"

///|
type TcpBindFlags UInt

///|
const TCP_IPV6ONLY : UInt = 1

///|
const TCP_REUSEPORT : UInt = 2

///|
pub fn TcpBindFlags::new(
  ipv6_only~ : Bool = false,
  reuse_port~ : Bool = false
) -> TcpBindFlags {
  let mut flags = 0U
  if ipv6_only {
    flags = flags | TCP_IPV6ONLY
  }
  if reuse_port {
    flags = flags | TCP_REUSEPORT
  }
  return flags
}

///|
pub fn[Sockaddr : ToSockaddr] Tcp::bind(
  self : Tcp,
  addr : Sockaddr,
  flags : TcpBindFlags
) -> Unit raise Errno {
  let status = uv_tcp_bind(self, addr.to_sockaddr(), flags._)
  if status < 0 {
    raise Errno::of_int(status)
  }
}

///|
extern "c" fn uv_listen(
  stream : Stream,
  backlog : Int,
  cb : (Stream, Int) -> Unit
) -> Int = "moonbit_uv_listen"

///|
pub fn[Stream : ToStream] listen(
  stream : Stream,
  backlog : Int,
  connection_cb : (Stream) -> Unit,
  error_cb : (Stream, Errno) -> Unit
) -> Unit raise Errno {
  stream.listen(backlog, connection_cb, error_cb)
}

///|
impl ToStream with listen(self, backlog, connection_cb, error_cb) {
  fn uv_cb(self : Stream, status : Int) {
    if status < 0 {
      error_cb(ToStream::of_stream(self), Errno::of_int(status))
    } else {
      connection_cb(ToStream::of_stream(self))
    }
  }

  let status = uv_listen(self.to_stream(), backlog, uv_cb)
  if status < 0 {
    raise Errno::of_int(status)
  }
}

///|
extern "c" fn uv_accept(server : Stream, client : Stream) -> Int = "moonbit_uv_accept"

///|
pub fn[Server : ToStream, Client : ToStream] accept(
  server : Server,
  client : Client
) -> Unit raise Errno {
  let status = uv_accept(server.to_stream(), client.to_stream())
  if status < 0 {
    raise Errno::of_int(status)
  }
}

///|
type Connect Bytes

///|
pub impl ToReq for Connect with to_req(self : Connect) -> Req = "%identity"

///|
extern "c" fn uv_connect_make() -> Connect = "moonbit_uv_connect_make"

///|
extern "c" fn uv_tcp_connect(
  req : Connect,
  handle : Tcp,
  addr : Sockaddr,
  cb : (Connect, Int) -> Unit
) -> Int = "moonbit_uv_tcp_connect"

///|
trait ToSockaddr {
  to_sockaddr(self : Self) -> Sockaddr
}

///|
pub fn[Sockaddr : ToSockaddr] Tcp::connect(
  self : Tcp,
  addr : Sockaddr,
  connect_cb : (Connect) -> Unit,
  error_cb : (Connect, Errno) -> Unit
) -> Unit raise Errno {
  fn cb(req : Connect, status : Int) {
    if status < 0 {
      error_cb(req, Errno::of_int(status))
    } else {
      connect_cb(req)
    }
  }

  let req = uv_connect_make()
  let status = uv_tcp_connect(req, self, addr.to_sockaddr(), cb)
  if status < 0 {
    raise Errno::of_int(status)
  }
}