/*
 * 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.
 */

package std.net

@When[os != "macOS" && os != "iOS"]
unsafe func setFamily(ptr: CPointer<Unit>, v: Int16): Unit {
    CPointer<Int16>(ptr).write(v)
}

@When[os != "macOS" && os != "iOS"]
unsafe func readFamily(ptr: CPointer<Unit>): Int16 {
    CPointer<Int16>(ptr).read()
}

@When[os == "macOS" || os == "iOS"]
unsafe func setFamily(ptr: CPointer<Unit>, v: Int16): Unit {
    CPointer<Int8>(ptr).write(1, Int8(v))
}

@When[os == "macOS" || os == "iOS"]
unsafe func readFamily(ptr: CPointer<Unit>): Int16 {
    Int16(CPointer<Int8>(ptr).read(1))
}

@C
struct SockAddr {
    let ptr: CPointer<Unit>
    let len: UInt32 // warning: this will never work as intended as the type in C is socklen_t

    private static const socket_address_in_len: UInt32 = 16 // sizeof(SockAddrIn)
    private static const socket_address_in6_len: UInt32 = 28 // sizeof(SockAddrIn6)
    private static const socket_address_un_len: UInt32 = 110 // sizeof(SockAddrUn)
    private static const socket_address_all_len: Int64 = 120

    init(buffer: CPointer<Unit>, length: Int64) {
        ptr = buffer
        len = UInt32(length)
        if (buffer.isNull()) {
            throw SocketException("Input parameter is null.")
        }
    }

    init() {
        let buffer = CPointer<Unit>(LibC.malloc<UInt8>(count: socket_address_all_len))
        if (buffer.isNull()) {
            throw SocketException("Failed to allocate memory")
        }
        zeroMemory(CPointer<UInt8>(buffer), socket_address_all_len)

        this.ptr = buffer
        this.len = UInt32(socket_address_all_len)
    }

    /**
     * Construct a SockAddr that stores address from SocketAddress instance
     * Malloc inside, should call free().
     */
    @When[backend == "cjnative"]
    init(sa: SocketAddress) {
        unsafe {
            match {
                case sa.family == AddressFamily.INET =>
                    len = socket_address_in_len
                    let ip = (sa as IPSocketAddress)?.address ?? throw SocketException("invalid IP family")
                    let port = (sa as IPSocketAddress)?.port ?? 0
                    ptr = CPointer<Unit>(LibC.malloc<UInt8>(count: Int64(socket_address_in_len)))
                    if (ptr.isNull()) {
                        throw SocketException("failed to malloc socket_address_in_len.")
                    }
                    setIpv4(ip.getAddressBytes(), port)
                case sa.family == AddressFamily.INET6 =>
                    len = socket_address_in6_len
                    let ip = (sa as IPSocketAddress)?.address ?? throw SocketException("invalid IP family")
                    let port = (sa as IPSocketAddress)?.port ?? 0
                    ptr = CPointer<Unit>(LibC.malloc<UInt8>(count: Int64(socket_address_in6_len)))
                    if (ptr.isNull()) {
                        throw SocketException("failed to malloc socket_address_in6_len.")
                    }
                    let scopeId = match (ip) {
                        case ipv6: IPv6Address => ipv6.scopeId ?? UInt32(0)
                        case _ => UInt32(0)
                    }
                    setIpv6(ip.getAddressBytes(), port, scopeId)
                case sa.family == AddressFamily.UNIX =>
                    len = socket_address_un_len
                    let un = (sa as UnixSocketAddress)?.toString() ?? throw SocketException("invalid IP family")
                    ptr = CPointer<Unit>(LibC.malloc<UInt8>(count: Int64(socket_address_un_len)))
                    if (ptr.isNull()) {
                        throw SocketException("failed to malloc socket_address_un_len.")
                    }
                    zeroMemory(CPointer<UInt8>(ptr), Int64(socket_address_un_len))
                    setUnix(un.toArray())
                case _ => throw SocketException("Unsupported address family: ${sa.family}.")
            }
        }
    }

    @When[backend == "cjnative"]
    @OverflowWrapping
    private unsafe func setPort(port: UInt16) {
        CPointer<UInt8>(ptr).write(2, UInt8(port >> 8)) // skip family
        CPointer<UInt8>(ptr).write(3, UInt8(port))
    }

    @When[backend == "cjnative"]
    private unsafe func setIpv4(addr: Array<UInt8>, port: UInt16) {
        setFamily(ptr, AF_INET)
        setPort(port)
        for (i in 0..SOCK_ADDR_IPV4_LEN) {
            CPointer<UInt8>(ptr).write(4 + i, addr[i])
        }
        CPointer<UInt64>(ptr).write(1, UInt64(0))
    }

    @When[backend == "cjnative"]
    private unsafe func setIpv6(addr: Array<UInt8>, port: UInt16, scopeId: UInt32) {
        setFamily(ptr, AF_INET6)
        setPort(port)
        CPointer<UInt32>(ptr).write(1, UInt32(0)) // sin6_flowinfo
        for (i in 0..SOCK_ADDR_IPV6_LEN) {
            CPointer<UInt8>(ptr).write(8 + i, addr[i])
        }
        CPointer<UInt32>(ptr).write(6, scopeId) // sin6_scope_id
    }

    /*
     * No windows.
     */
    @When[backend == "cjnative"]
    private unsafe func setUnix(addr: Array<UInt8>) {
        setFamily(ptr, AF_UNIX)
        for (i in 0..min(SOCK_ADDR_DOMAIN_LEN, addr.size)) {
            CPointer<UInt8>(ptr).write(2 + i, addr[i])
        }
    }

    @When[backend == "cjnative"]
    public unsafe func free(): Unit {
        LibC.free(ptr)
    }

    @When[backend == "cjnative"]
    private unsafe func getIpv4(): Array<UInt8> {
        return Array<UInt8>(SOCK_ADDR_IPV4_LEN, {i => CPointer<UInt8>(ptr).read(4 + i)}) // skip family, port.
    }

    @When[backend == "cjnative"]
    private unsafe func getIpv6(): Array<UInt8> {
        return Array<UInt8>(SOCK_ADDR_IPV6_LEN, {i => CPointer<UInt8>(ptr).read(8 + i)}) // skip family, port, sin6_flowinfo.
    }

    @When[backend == "cjnative"]
    private unsafe func getPort(): UInt16 {
        let high = CPointer<UInt8>(ptr).read(2) // skip family
        let low = CPointer<UInt8>(ptr).read(3)
        return UInt16(high) << 8 | UInt16(low)
    }

    @When[backend == "cjnative"]
    private unsafe func getDomain(): Array<UInt8> {
        return Array<UInt8>(SOCK_ADDR_DOMAIN_LEN, {i => CPointer<UInt8>(ptr).read(2 + i)}) // skip family.
    }

    @When[backend == "cjnative"]
    prop family: Int16 {
        get() {
            unsafe {
                readFamily(ptr)
            }
        }
    }

    @When[backend == "cjnative"]
    unsafe func toSocketAddress(): SocketAddress {
        match {
            case family == AF_INET =>
                let ip = getIpv4()
                IPSocketAddress(IPv4Address(ip[0], ip[1], ip[2], ip[3]), getPort())
            case family == AF_INET6 => IPSocketAddress(IPv6Address(getIpv6()), getPort())
            case family == AF_UNIX => UnixSocketAddress(getDomain())
            case _ => throw SocketException("Unsupported socket family ${family}")
        }
    }

    @When[backend == "cjnative"]
    unsafe func toRawAddress(): RawAddress {
        let remoteArr = Array<Byte>(Int64(len), {i => CPointer<Byte>(ptr).read(i)})
        return RawAddress(remoteArr)
    }
}