/*
 * Wrapper system around poll() that lets me treat it more or less
 * like select(), but avoiding the inherent limitation of select()
 * that it can't handle the full range of fds that are capable of
 * existing.
 *
 * The pollwrapper structure contains the 'struct pollfd' array passed
 * to poll() itself, and also a tree234 that maps each fd to its
 * location in the list, which makes it convenient to add or remove
 * individual fds from the system or change what events you're
 * watching for on them. So the API is _shaped_ basically like select,
 * even if none of the details are identical: from outside this
 * module, a pollwrapper can be used wherever you'd otherwise have had
 * an fd_set.
 *
 * Also, this module translate between the simple select r/w/x
 * classification and the richer poll flags. We have to stick to r/w/x
 * in this code base, because it ports to other systems where that's
 * all you get.
 */

/* On some systems this is needed to get poll.h to define eg.. POLLRDNORM */
#define _XOPEN_SOURCE

#include <poll.h>

#include "putty.h"
#include "tree234.h"

struct pollwrapper {
    struct pollfd *fds;
    size_t nfd, fdsize;
    tree234 *fdtopos;
};

typedef struct pollwrap_fdtopos pollwrap_fdtopos;
struct pollwrap_fdtopos {
    int fd;
    size_t pos;
};

static int pollwrap_fd_cmp(void *av, void *bv)
{
    pollwrap_fdtopos *a = (pollwrap_fdtopos *)av;
    pollwrap_fdtopos *b = (pollwrap_fdtopos *)bv;
    return a->fd < b->fd ? -1 : a->fd > b->fd ? +1 : 0;
}

pollwrapper *pollwrap_new(void)
{
    pollwrapper *pw = snew(pollwrapper);
    pw->fdsize = 16;
    pw->nfd = 0;
    pw->fds = snewn(pw->fdsize, struct pollfd);
    pw->fdtopos = newtree234(pollwrap_fd_cmp);
    return pw;
}

void pollwrap_free(pollwrapper *pw)
{
    pollwrap_clear(pw);
    freetree234(pw->fdtopos);
    sfree(pw->fds);
    sfree(pw);
}

void pollwrap_clear(pollwrapper *pw)
{
    pw->nfd = 0;
    for (pollwrap_fdtopos *f2p;
         (f2p = delpos234(pw->fdtopos, 0)) != NULL ;)
        sfree(f2p);
}

void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events)
{
    pollwrap_fdtopos *f2p, f2p_find;

    assert(fd >= 0);

    f2p_find.fd = fd;
    f2p = find234(pw->fdtopos, &f2p_find, NULL);
    if (!f2p) {
        sgrowarray(pw->fds, pw->fdsize, pw->nfd);
        size_t index = pw->nfd++;
        pw->fds[index].fd = fd;
        pw->fds[index].events = pw->fds[index].revents = 0;

        f2p = snew(pollwrap_fdtopos);
        f2p->fd = fd;
        f2p->pos = index;
        pollwrap_fdtopos *added = add234(pw->fdtopos, f2p);
        assert(added == f2p);
    }

    pw->fds[f2p->pos].events |= events;
}

/* Omit any of the POLL{RD,WR}{NORM,BAND} flag values that are still
 * not defined by poll.h, just in case */
#ifndef POLLRDNORM
#define POLLRDNORM 0
#endif
#ifndef POLLRDBAND
#define POLLRDBAND 0
#endif
#ifndef POLLWRNORM
#define POLLWRNORM 0
#endif
#ifndef POLLWRBAND
#define POLLWRBAND 0
#endif

#define SELECT_R_IN (POLLIN  | POLLRDNORM | POLLRDBAND)
#define SELECT_W_IN (POLLOUT | POLLWRNORM | POLLWRBAND)
#define SELECT_X_IN (POLLPRI)

#define SELECT_R_OUT (SELECT_R_IN | POLLERR | POLLHUP)
#define SELECT_W_OUT (SELECT_W_IN | POLLERR)
#define SELECT_X_OUT (SELECT_X_IN)

void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx)
{
    int events = 0;
    if (rwx & SELECT_R)
        events |= SELECT_R_IN;
    if (rwx & SELECT_W)
        events |= SELECT_W_IN;
    if (rwx & SELECT_X)
        events |= SELECT_X_IN;
    pollwrap_add_fd_events(pw, fd, events);
}

int pollwrap_poll_instant(pollwrapper *pw)
{
    return poll(pw->fds, pw->nfd, 0);
}

int pollwrap_poll_endless(pollwrapper *pw)
{
    return poll(pw->fds, pw->nfd, -1);
}

int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds)
{
    assert(milliseconds >= 0);
    return poll(pw->fds, pw->nfd, milliseconds);
}

static void pollwrap_get_fd_events_revents(pollwrapper *pw, int fd,
                                           int *events_p, int *revents_p)
{
    pollwrap_fdtopos *f2p, f2p_find;
    int events = 0, revents = 0;

    assert(fd >= 0);

    f2p_find.fd = fd;
    f2p = find234(pw->fdtopos, &f2p_find, NULL);
    if (f2p) {
        events = pw->fds[f2p->pos].events;
        revents = pw->fds[f2p->pos].revents;
    }

    if (events_p)
        *events_p = events;
    if (revents_p)
        *revents_p = revents;
}

int pollwrap_get_fd_events(pollwrapper *pw, int fd)
{
    int revents;
    pollwrap_get_fd_events_revents(pw, fd, NULL, &revents);
    return revents;
}

int pollwrap_get_fd_rwx(pollwrapper *pw, int fd)
{
    int events, revents;
    pollwrap_get_fd_events_revents(pw, fd, &events, &revents);
    int rwx = 0;
    if ((events & POLLIN) && (revents & SELECT_R_OUT))
        rwx |= SELECT_R;
    if ((events & POLLOUT) && (revents & SELECT_W_OUT))
        rwx |= SELECT_W;
    if ((events & POLLPRI) && (revents & SELECT_X_OUT))
        rwx |= SELECT_X;
    return rwx;
}
