/*<copyright>
 * <year>2005-2008</year>
 * <holder>Ericsson AB, All Rights Reserved</holder>
 *</copyright>
 *<legalnotice>
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Initial Developer of the Original Code is Ericsson AB.
 *</legalnotice>
 */

/*
 * Purpose: Hide poll() and select() behind an API so that we
 * can use either one.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef __WIN32__
#include "esock_winsock.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <sys/types.h>
#include <errno.h>

#ifdef __WIN32__
#include <process.h>
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/time.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <fcntl.h>
#endif

#include "esock.h"
#include "esock_ssl.h"
#include "esock_utils.h"
#include "esock_poll.h"
#include "debuglog.h"

#if !defined(USE_SELECT)

/* At least on FreeBSD, we need POLLRDNORM for normal files, not POLLIN. */
/* Whether this is a bug in FreeBSD, I don't know. */
#ifdef POLLRDNORM
#define POLL_INPUT	(POLLIN | POLLRDNORM)
#else
#define POLL_INPUT	POLLIN
#endif

static void poll_fd_set(EsockPoll *ep, FD fd, short events)
{
    int i, j;
    int prev_num_fds = ep->num_fds;

    if (ep->num_fds <= fd) {
	ep->num_fds = fd + 64;
	ep->fd_to_poll = (int *) esock_realloc(ep->fd_to_poll,
					       ep->num_fds*sizeof(int));
	for (j = prev_num_fds; j < ep->num_fds; j++)
	    ep->fd_to_poll[j] = -1;
    }
    i = ep->fd_to_poll[fd];
    if (i > 0 && i < ep->active && ep->fds[i].fd == fd) {
	/* Already present in poll array */
	ep->fds[i].events |= events;
    } else {
	/* Append to poll array */
	if (ep->active >= ep->allocated) {
	    ep->allocated *= 2;
	    ep->fds = (struct pollfd *)
		esock_realloc(ep->fds, ep->allocated*sizeof(struct pollfd));
	}
	ep->fd_to_poll[fd] = ep->active;
	ep->fds[ep->active].fd = fd;
	ep->fds[ep->active].events = events;
	ep->fds[ep->active].revents = 0;
	ep->active++;
    }
}

static int poll_is_set(EsockPoll *ep, FD fd, short mask)
{
    if (fd >= ep->num_fds) {
	return 0;
    } else {
	int i = ep->fd_to_poll[fd];
	return 0 <= i && i < ep->active && ep->fds[i].fd == fd &&
	    (ep->fds[i].revents & mask) != 0;
    }
}

#endif

void esock_poll_init(EsockPoll *ep)
{
#ifdef USE_SELECT
    /* Nothing to do here */
#else
    ep->allocated = 2;
    ep->fds = (struct pollfd *) esock_malloc(ep->allocated*sizeof(struct pollfd));
    ep->num_fds = 1;
    ep->fd_to_poll = esock_malloc(ep->num_fds*sizeof(int));
#endif    
}

void esock_poll_zero(EsockPoll *ep)
{
#ifdef USE_SELECT
    FD_ZERO(&ep->readmask);
    FD_ZERO(&ep->writemask);
    FD_ZERO(&ep->exceptmask);
#else
    int i;

    for (i = 0; i < ep->num_fds; i++)
	ep->fd_to_poll[i] = -1;
    ep->active = 0;
#endif    
}

void esock_poll_fd_set_read(EsockPoll *ep, FD fd)
{
#ifdef USE_SELECT
    FD_SET(fd, &ep->readmask);
#else
    poll_fd_set(ep, fd, POLL_INPUT);
#endif    
}

void esock_poll_fd_set_write(EsockPoll *ep, FD fd)
{
#ifdef USE_SELECT
    FD_SET(fd, &ep->writemask);
#else
    poll_fd_set(ep, fd, POLLOUT);
#endif    
}

int esock_poll_fd_isset_read(EsockPoll *ep, FD fd)
{
#ifdef USE_SELECT
    return FD_ISSET(fd, &ep->readmask);
#else
    return poll_is_set(ep, fd, (POLL_INPUT|POLLHUP|POLLERR|POLLNVAL));
#endif    
}

int esock_poll_fd_isset_write(EsockPoll *ep, FD fd)
{
#ifdef USE_SELECT
    return FD_ISSET(fd, &ep->writemask);
#else
    return poll_is_set(ep, fd, (POLLOUT|POLLHUP|POLLERR|POLLNVAL));
#endif    
}

#ifdef __WIN32__
void esock_poll_fd_set_exception(EsockPoll *ep, FD fd)
{
    FD_SET(fd, &ep->exceptmask);
}

int esock_poll_fd_isset_exception(EsockPoll *ep, FD fd)
{
    return FD_ISSET(fd, &ep->exceptmask);
}
#endif

int esock_poll(EsockPoll *ep, int seconds)
{
    int sret;

#ifdef USE_SELECT
    struct timeval tv;

    tv.tv_sec = seconds;
    tv.tv_usec = 0;
    sret = select(FD_SETSIZE, &ep->readmask, &ep->writemask, &ep->exceptmask, &tv);
    if (sret == 0) {
	FD_ZERO(&ep->readmask);
	FD_ZERO(&ep->writemask);
	FD_ZERO(&ep->exceptmask);
    }
#else
    sret = poll(ep->fds, ep->active, 1000*seconds);
#endif
    return sret;
}

void esock_poll_clear_event(EsockPoll* ep, FD fd)
{
#ifdef USE_SELECT
    FD_CLR(fd, &ep->readmask);
    FD_CLR(fd, &ep->writemask);
    FD_CLR(fd, &ep->exceptmask);
#else
    int i = ep->fd_to_poll[fd];
    if (i > 0 && ep->fds[i].fd == fd)
	ep->fds[i].revents = 0;
#endif
}