
header "sys" as sys

use core
use coro

alias FD for int

// #if OS == win
// macro valk_fd (V:fd) : <{ return ((fd == -1.@cast(uint)) ? -1 : fd.@cast(int)) }
// macro os_fd (V:fd) : <{ return fd.@cast(uint) }
// #else
// macro valk_fd (V:fd) : <{ return fd.@cast(i32) }
// macro os_fd (V:fd) : <{ return fd.@cast(i32) }
// #end

#if OS == win
+ fn valk_fd(fd: int) FD { return ((fd == -1) ? -1 : fd.to(FD)) }
+ fn os_fd(fd: FD) uint { return fd.to(uint) }
#else
+ fn valk_fd(fd: int) FD { return fd.to(FD) }
+ fn os_fd(fd: FD) i32 { return fd.to(i32) }
#end

+ fn write(fd: FD, str: String) !failed !again {
    #if OS == win
    let bytes = str.bytes.to(u32)
    #else
    let bytes = str.bytes
    #end
    if bytes == 0 : return
    let c = sys:write(fd.to(i32), str.data, bytes)
    if c == -2 {
        throw again
    }
    if c < 0 {
        throw failed
    }
}

+ fn write_bytes(fd: FD, data: ptr, length: uint) uint !failed !again {
    #if OS == win
    let bytes = length.to(u32)
    #else
    let bytes = length
    #end
    let c = sys:write(fd.to(i32), data, bytes)
    if c == -2 {
        throw again
    }
    if c < 0 {
        throw failed
    }
    return c.to(uint)
}

+ fn read(fd: FD, buffer: ptr, buffer_size: uint) uint !failed {
    while true {
        let ofd = os_fd(fd)

        let rcvd = sys:read(ofd.to(i32), buffer, buffer_size)
        if rcvd < 0 {
            #if OS == win
            let err = core:WSA.error()
            if err == sys:WSAEWOULDBLOCK {
                // EAGAIN
                coro:await_fd(fd, true, false)
                continue
            }
            #else
            let err = core:libc_errno()
            if err == sys:EAGAIN {
                // EAGAIN
                coro:await_fd(fd, true, false)
                continue
            }
            #end
            throw failed
        }
        return rcvd.to(uint)
    }
    throw failed
}

+ fn set_non_block(fd: FD, value: bool) {
    let ofd = os_fd(fd)
    #if OS == win
    let nonblock : uint = value ? 1 : 0
    let err = sys:ioctlsocket(ofd, sys:FIONBIO, @ref(nonblock))
    #else
    let flags = sys:fcntl(ofd, sys:F_GETFL, 0)
    let max : i32 = -1
    let v : i32 = value ? (flags | sys:O_NONBLOCK) : flags & (max - sys:O_NONBLOCK)
    let err = sys:fcntl(ofd, sys:F_SETFL, v)
    #end
    if err == -1 {
        err = core:socket_errno()
        #if OS == win
        if err == sys:WSAEINTR : set_non_block(fd, value) // Repeat
        #else
        if err == sys:EINTR : set_non_block(fd, value) // Repeat
        #end

        // TODO: handle all errors
    }
}

+ fn close(fd: FD) {
    let err = sys:close(fd.to(i32))
    if err == -1 {
        // TODO?
    }
}
