#include <assert.h>
#include <errno.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>

#include "evloop.h"

#define POLL_INIT_SIZE 64

struct poll_ev {
    struct fdev *ev;
    evloop_cb_t cb;
    void *arg;
};

static struct pollfd *m_pfds;
static struct poll_ev *m_pevs;

static int m_cap, m_size;
static int m_cur = -1, m_curdel;

static int
poll_grow(void)
{
    int ncap = m_cap * 2;
    struct pollfd *nm_pfds = realloc(m_pfds, ncap * sizeof(*m_pfds));
    struct poll_ev *nm_pevs = realloc(m_pevs, ncap * sizeof(*m_pevs));
    if (nm_pfds != NULL)
        m_pfds = nm_pfds;
    if (nm_pevs != NULL)
        m_pevs = nm_pevs;
    if (nm_pfds == NULL || nm_pevs == NULL)
        return errno;
    m_cap = ncap;
    return 0;
}

int
evloop_init(void)
{
    if (timeheap_init() != 0)
        return -1;
    m_cap = POLL_INIT_SIZE;
    m_size = 0;
    if ((m_pfds = calloc(m_cap, sizeof(*m_pfds))) == NULL)
        return -1;
    if ((m_pevs = calloc(m_cap, sizeof(*m_pevs))) == NULL) {
        free(m_pfds);
        return -1;
    }
    return 0;
}

int
fdev_new(struct fdev *ev, int fd, uint16_t flags, evloop_cb_t cb, void *arg)
{
    if (m_size == m_cap && poll_grow() != 0)
        return errno;
    ev->i = m_size;
    m_size++;
    m_pfds[ev->i].fd = fd;
    m_pfds[ev->i].events =
        ((flags & EV_READ) ? POLLIN : 0) |
        ((flags & EV_WRITE) ? POLLOUT : 0);
    m_pevs[ev->i].ev = ev;
    m_pevs[ev->i].cb = cb;
    m_pevs[ev->i].arg = arg;
    return 0;
}

int
fdev_enable(struct fdev *ev, uint16_t flags)
{
    m_pfds[ev->i].events |= 
        ((flags & EV_READ) ? POLLIN : 0) |
        ((flags & EV_WRITE) ? POLLOUT : 0);
    return 0;
}

int
fdev_disable(struct fdev *ev, uint16_t flags)
{
    short pflags =
        ((flags & EV_READ) ? POLLIN : 0) |
        ((flags & EV_WRITE) ? POLLOUT : 0);
    m_pfds[ev->i].events &= ~pflags;
    return 0;
}

int
fdev_del(struct fdev *ev)
{
    assert(ev->i < m_size);
    m_size--;
    m_pfds[ev->i] = m_pfds[m_size];
    m_pevs[ev->i] = m_pevs[m_size];
    m_pevs[ev->i].ev->i = ev->i;
    if (ev->i == m_cur)
        m_curdel = 1;
    return 0;
}

int
evloop(void)
{
    int millisecs;
    struct timespec delay;
    while (1) {
        evtimers_run();

        delay = evtimer_delay();
        if (delay.tv_sec >= 0)
            millisecs = delay.tv_sec * 1000 + delay.tv_nsec / 1000000;
        else
            millisecs = -1;

        if (poll(m_pfds, m_size, millisecs) < 0) {
            if (errno == EINTR)
                continue;
            else
                return -1;
        }

        m_cur = 0;
        while (m_cur < m_size) {
            struct pollfd *pfd = &m_pfds[m_cur];
            struct poll_ev *pev = &m_pevs[m_cur];
            if ((pfd->events & POLLIN &&
                    pfd->revents & (POLLIN|POLLERR|POLLHUP)))
                pev->cb(pfd->fd, EV_READ, pev->arg);
            if ((!m_curdel && pfd->events & POLLOUT &&
                    pfd->revents & (POLLOUT|POLLERR|POLLHUP)))
                pev->cb(pfd->fd, EV_WRITE, pev->arg);
            if (!m_curdel)
                m_cur++;
            else
                m_curdel = 0;
        }
        m_cur = -1;
    }
}