#include "poll.hpp"
#include <unistd.h>
#include <assert.h>


#define TICKLE_INTERVAL (SOCK_TIMEOUT/3) // tickle via timeout every this seconds (for PINGs)
#define EVENTS_MAX 64


Poll* Poll::inst = NULL;


Poll::Poll(): fds(0), activity(NOW) {
    assert(!inst);
    efd = epoll_create(1);
    assert(efd != -1);
}


Poll::~Poll() {
    close(efd);
    assert(inst == this);
    inst = NULL;
    if (fds) {
        LOG("poll: still having active fds!");
    }
    assert((fds == 0) == timeouts.empty());
}


bool Poll::ctl(int op, int cfd, event_t events) {
    events |= EVENT_CLOSE; // explicit default
    struct epoll_event ev = {}; // make valgrind happy
    ev.data.fd = cfd;
    ev.events = events;
    if (epoll_ctl(efd, op, cfd, &ev) == -1) {
        LOG_ERRNO("EPOLL_CTL_(%d)", op);
        return false;
    }
    return true;
}


bool Poll::add(int cfd, poll_handler_t handler, event_t events) {
    assert(!handlers[cfd]);
    if (ctl(EPOLL_CTL_ADD, cfd, events)) {
        handlers[cfd] = handler;
        timeouts.push(cfd, NOW);
        ++fds;
        return true;
    }
    return false;
}


bool Poll::mod(int cfd, poll_handler_t handler, event_t events) {
    assert(handlers[cfd]);
    handlers[cfd] = handler;
    return ctl(EPOLL_CTL_MOD, cfd, events);
}


bool Poll::del(int cfd) {
    assert(handlers[cfd]);
    if (ctl(EPOLL_CTL_DEL, cfd, EVENT_NONE)) {
        handlers[cfd] = NULL;
        timeouts.pop(cfd);
        --fds;
        return true;
    }
    return false;
}


bool Poll::wait(int tout_ms) {
    struct epoll_event events[EVENTS_MAX];
    int n = epoll_wait(efd, events, EVENTS_MAX, tout_ms);
    if (n == -1) {
        if (errno == EINTR) {
            return true;
        } else {
            LOG_ERRNO("epoll_wait()");
            return false;
        }
    }

    update_now();
    if (n > 0) {
        activity = NOW;
    }

    for (int i=0; i<n; i++) {
        struct epoll_event* event = &events[i];
        int fd = event->data.fd;

        // reset fd (including handler mask) as recently active
        timeouts.pop(fd);
        timeouts.push(fd, NOW);

        // call handler - this might delete the fd
        handlers[fd](fd, event->events);
    }

    int fd;
    while ((fd = timeouts.pop(NOW-TICKLE_INTERVAL)) != -1) {
        timeouts.push(fd, NOW);
        handlers[fd](fd, EVENT_TOUT);
    }

    return true;
}