/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

/**
 * @file The file declares several ffi functions and classes.
 */
package std.net

foreign func CJ_MRT_TcpsockInit(): Int32

foreign func CJ_MRT_UdpsockInit(): Int32

foreign func CJ_MRT_RawsockInit(): Int32

@When[os != "Windows"]
foreign func CJ_MRT_DomainsockInit(): Int32

/**
 * For static link, we have to call this init function in coroutine module.
 * Save the result as a global variable to make sure this init function is called before other functions.
 */
let CJ_MRT_TCP_SOCK_INIT: Int32 = unsafe { CJ_MRT_TcpsockInit() }
let CJ_MRT_UDP_SOCK_INIT: Int32 = unsafe { CJ_MRT_UdpsockInit() }
let CJ_MRT_RAW_SOCK_INIT: Int32 = unsafe { CJ_MRT_RawsockInit() }
@When[os != "Windows"]
let CJ_MRT_DOMAIN_SOCK_INIT: Int32 = unsafe {
    CJ_MRT_DomainsockInit()
}

/**
 * CJ_MRT_SockCreate type
 */
const SOCK_STREAM: Int32 = 1
const SOCK_DGRAM: Int32 = 2
@When[os == "Windows" || os == "macOS" || os == "iOS"]
const SOCK_RDM: Int32 = 4
@When[os == "Windows" || os == "macOS" || os == "iOS"]
const SOCK_SEQPACKET: Int32 = 5

/* Error code in runtime */
const ERRNO_SOCK_ARG_INVALID: Int32 = 0x100C0000
const ERRNO_SOCK_NOT_SUPPORTED: Int32 = 0x100C0001
const ERRNO_SOCK_NOT_REGISTERED: Int32 = 0x100C0002
const ERRNO_SOCK_HANDLE_INVALID: Int32 = 0x100C0003
const ERRNO_SOCK_FD_NUM_OVER_LIMIT: Int32 = 0x100C0004
const ERRNO_SOCK_TIMEOUT: Int32 = 0x100C0005
const ERRNO_SOCK_CREATE: Int32 = 0x100C0006
const ERRNO_SOCK_EAGAIN: Int32 = 0x100C0007
const ERRNO_SOCK_CLOSED: Int32 = 269484036

func localAddrGet(handle: Int64, addr: CPointer<SockAddr>): Int32 {
    unsafe { CJ_MRT_SockLocalAddrGet(handle, addr) }
}

func peerAddrGet(handle: Int64, addr: CPointer<SockAddr>): Int32 {
    unsafe { CJ_MRT_SockPeerAddrGet(handle, addr) }
}

/*
 * @throws SocketException while some system errors are happened.
 * @throws SocketException while malloc socket address is failed.
 */
func getAddress(handle: Int64, getAddressFunc: (Int64, CPointer<SockAddr>) -> Int32, msg: String): SockAddr {
    var addr = SockAddr()
    let res: Int32 = getAddressFunc(handle, inout addr)
    if (res < 0) {
        let errno: Int32 = unsafe { CJ_SockErrnoGet() }
        unsafe { addr.free() }
        throw SocketException("Failed to obtain the ${msg} address: ${sockError(errno)}.")
    }
    return addr
}

func getLocalAddress(handle: Int64): SocketAddress {
    let addr = getAddress(handle, localAddrGet, "local")
    let local = unsafe { addr.toSocketAddress() }
    unsafe { addr.free() }
    return local
}

func getRemoteAddress(handle: Int64): SocketAddress {
    let addr = getAddress(handle, peerAddrGet, "remote")
    let local = unsafe { addr.toSocketAddress() }
    unsafe { addr.free() }
    return local
}

foreign {
    func CJ_MRT_SockCreate(domain: Int32, sock_type: Int32, protocol: Int32, net: CString): Int64

    func CJ_MRT_SockBind(sock: Int64, addr: CPointer<SockAddr>, backlog: Int32): Int64

    func CJ_MRT_SockAccept(sock: Int64, addr: CPointer<SockAddr>): Int64

    func CJ_MRT_SockAcceptTimeout(sock: Int64, addr: CPointer<SockAddr>, timeout: UInt64): Int64

    func CJ_MRT_SockConnect(sock: Int64, local_addr: CPointer<SockAddr>, peer_addr: CPointer<SockAddr>): Int64

    func CJ_MRT_SockConnectTimeout(sock: Int64, local_addr: CPointer<SockAddr>, peer_addr: CPointer<SockAddr>,
        timeout: UInt64): Int64

    func CJ_MRT_SockDisconnect(sock: Int64, isIpv6: Bool): Int32

    func CJ_MRT_SockClose(sock: Int64): Int32

    func CJ_MRT_SockShutdown(sock: Int64): Int32

    func CJ_MRT_SockSend(sock: Int64, buf: CPointer<UInt8>, length: UInt32, flags: Int32): Int32

    func CJ_MRT_SockSendTimeout(sock: Int64, buf: CPointer<UInt8>, length: UInt32, flags: Int32, timeout: UInt64): Int32

    func CJ_MRT_SockSendNonBlock(handle: Int64, buf: CPointer<UInt8>, len: UInt32, flags: Int32): Int32

    func CJ_MRT_SockRecv(sock: Int64, buf: CPointer<UInt8>, length: UInt32, flags: Int32): Int32

    func CJ_MRT_SockRecvTimeout(sock: Int64, buf: CPointer<UInt8>, length: UInt32, flags: Int32, timeout: UInt64): Int32

    func CJ_MRT_SockRecvNonBlock(handle: Int64, buf: CPointer<UInt8>, len: UInt32, flags: Int32): Int32

    func CJ_MRT_SockRecvfromTimeout(sock: Int64, buf: CPointer<UInt8>, length: UInt32, flags: Int32,
        addr: CPointer<SockAddr>, timeout: UInt64): Int32

    func CJ_MRT_SockWaitRecv(handle: Int64): Int32

    func CJ_MRT_SockWaitRecvTimeout(handle: Int64, timeout: UInt64): Int32

    func CJ_MRT_SockWaitSend(handle: Int64): Int32

    func CJ_MRT_SockWaitSendTimeout(handle: Int64, timeout: UInt64): Int32
    // raw socket
    func CJ_MRT_SockListen(sockfd: Int64, backlog: Int32): Int64

    func CJ_MRT_SockSendtoNonBlock(sockfd: Int64, buf: CPointer<UInt8>, size: UInt32, flags: Int32,
        sockaddr: CPointer<SockAddr>): Int32

    func CJ_MRT_SockRecvfromNonBlock(sockfd: Int64, buf: CPointer<UInt8>, size: UInt32, flags: Int32,
        sockaddr: CPointer<SockAddr>): Int32

    func CJ_MRT_SockLocalAddrGet(sock: Int64, addr: CPointer<SockAddr>): Int32

    func CJ_MRT_SockPeerAddrGet(sock: Int64, addr: CPointer<SockAddr>): Int32
}

enum ErrnoLabel <: ToString {
    Accept | Connect | Read | Write | CreateSock | Bind | Listen

    public override func toString(): String {
        match (this) {
            case Accept => "accept"
            case Connect => "connect"
            case Read => "read data"
            case Write => "write data"
            case CreateSock => "create socket"
            case Bind => "bind"
            case Listen => "listen"
        }
    }
}

func getSocketError(): Int32 {
    unsafe { CJ_SockErrnoGet() }
}

func socketProcessErrno(label: ErrnoLabel): Nothing {
    let errno = getSocketError()

    // There is no EWOULDBLOCK/ ETIMEDOUT / EAGAIN in runtime, the socket is in NONBLOCK mode.
    if (errno == ERRNO_SOCK_TIMEOUT) {
        throw SocketTimeoutException("Failed to ${label}: ${label} timeout.")
    }

    if (errno == ERRNO_SOCK_CLOSED) {
        SocketException.throwClosedException()
    }
    throw SocketException("Failed to ${label} ${errno}: ${sockError(errno)}.")
}

func sockError(errno: Int32): String {
    if (errno == 0) {
        return ""
    }
    return match {
        case errno == ERRNO_SOCK_ARG_INVALID => "socket input parameter invalid"
        case errno == ERRNO_SOCK_NOT_SUPPORTED => "socket not supported"
        case errno == ERRNO_SOCK_NOT_REGISTERED => "socket not registered"
        case errno == ERRNO_SOCK_HANDLE_INVALID => "socket handle invalid"
        case errno == ERRNO_SOCK_FD_NUM_OVER_LIMIT => "socket fd number over limit"
        case errno == ERRNO_SOCK_CREATE => "wrong combination of domain, type and protocol"
        case _ => formatError(errno)
    }
}

@When[os != "Windows"]
const ERRNO_EINVAL: Int32 = 0x16
@When[os == "Windows"]
const ERRNO_EINVAL: Int32 = 0x2726

func isSocketErrorInvalidArgument(errno: Int32): Bool {
    errno == ERRNO_SOCK_ARG_INVALID || errno == ERRNO_EINVAL
}