
header "sys" as sys

use type
use core
use gc

#if OS == win
use io
#end

class CoroPoll {
    poll_items: type:StructArray[sys:libc_pollfd]
    items1: Array[Coro]
    items2: Array[Coro]
    index: uint (0)

    static fn new() SELF {
        let poll = SELF {
            poll_items: type:StructArray[sys:libc_pollfd].new(4)
            items1: Array[Coro].new(4)
            items2: Array[Coro].new(4)
        }
        return poll
    }

    fn set(fd: FD, data: Coro, read: bool, write: bool) {
        if fd < 0 : return
        let pis = this.poll_items
        let index = this.index++
        let pi = pis.get(index)

        #if OS == win
        let ofd = io:os_fd(fd)
        pi.fd = ofd
        #else
        pi.fd = fd.@cast(i32)
        #end

        #if OS == win
        let events : i16 = 0
        #else
        let events : i16 = sys:POLLERR | sys:POLLHUP | sys:POLLRDHUP
        #end

        if read : events = events | sys:POLLIN
        if write : events = events | sys:POLLOUT
        pi.events = events

        this.items1.append(data)
    }

    fn wait(timeout: i32 (-1), result: Array[Coro] (null)) Array[Coro] !fd_limit !unknown {

        result.clear()

        let pis = this.poll_items
        let items1 = this.items1
        let items2 = this.items2
        let len = this.index

        gc:unlock()
        #if OS == win
        let count = sys:poll(pis.data, len, timeout)
        #else
        let count = sys:poll(pis.data, len.@cast(u32), timeout)
        #end
        gc:lock()

        if count == -1 {
            let e = core:socket_errno()
            #if OS == win
            if e == sys:WSAEINVAL {
                throw fd_limit
            }
            #else
            if e == sys:EINVAL {
                throw fd_limit
            }
            #end
            throw unknown
        }

        let new_index : uint = 0
        let i : uint = 0
        while i < len {
            let index = i++
            let coro = items1.get(index) ! continue
            let pi = pis.get(index)

            if pi.revents == 0 {
                if new_index != index {
                    let pi2 = pis.get(new_index)
                    pi2.fd = pi.fd
                    pi2.events = pi.events
                }
                items2.append(coro)
                new_index++
                continue
            }

            coro.poll_event = pi.revents
            result.append(coro)
        }

        this.items1 = items2
        this.items2 = items1
        this.index = new_index
        items1.clear()

        return result
    }
}
