/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/* Cherokee
 *
 * Authors:
 *      Alvaro Lopez Ortega <alvaro@alobbs.com>
 *
 * Copyright (C) 2001-2011 Alvaro Lopez Ortega
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

#include "common-internal.h"
#include "fdpoll-protected.h"

#include <poll.h>
#include <errno.h>

#define POLL_ERROR  (POLLHUP | POLLERR | POLLNVAL)
#define POLL_READ   (POLLIN  | POLL_ERROR)
#define POLL_WRITE  (POLLOUT | POLL_ERROR)

/***********************************************************************/
/* poll()                                                              */
/*                                                                     */
/* #include <sys/poll.h>                                               */
/* int poll(struct pollfd *ufds, unsigned int nfds, int timeout);      */
/*                                                                     */
/***********************************************************************/


typedef struct {
	struct cherokee_fdpoll poll;

	struct pollfd *pollfds;
	int           *fdidx;
} cherokee_fdpoll_poll_t;


static ret_t
_free (cherokee_fdpoll_poll_t *fdp)
{
	if (fdp == NULL)
		return ret_ok;

	free (fdp->pollfds);
	free (fdp->fdidx);

	/* Caller has to set this pointer to NULL.
	 */
	free (fdp);

	return ret_ok;
}


static ret_t
_add (cherokee_fdpoll_poll_t *fdp, int fd, int rw_mode)
{
	short              events;
	cherokee_fdpoll_t *nfd     = FDPOLL(fdp);

	/* Check the fd limit
	 */
	if (cherokee_fdpoll_is_full(nfd)) {
		LOG_WARNING_S (CHEROKEE_ERROR_FDPOLL_POLL_FULL);
		return ret_error;
	}

	/* Translate mode */
	events = 0;
	if (rw_mode & poll_mode_read) {
		events |= POLLIN;
	}
	if (rw_mode & poll_mode_write) {
		events |= POLLOUT;
	}

	/* Set values */
	fdp->pollfds[nfd->npollfds].events  = events;
	fdp->pollfds[nfd->npollfds].fd      = fd;
	fdp->pollfds[nfd->npollfds].revents = 0;

	fdp->fdidx[fd] = nfd->npollfds;
	nfd->npollfds++;

	return ret_ok;
}


static ret_t
_set_mode (cherokee_fdpoll_poll_t *fdp, int fd, int rw_mode)
{
	short events = 0;

	if (rw_mode & poll_mode_read) {
		events |= POLLIN;
	}
	if (rw_mode & poll_mode_write) {
		events |= POLLOUT;
	}

	fdp->pollfds[fdp->fdidx[fd]].events = events;
	return ret_ok;
}


static ret_t
_del (cherokee_fdpoll_poll_t *fdp, int fd)
{
	int                idx = fdp->fdidx[fd];
	cherokee_fdpoll_t *nfd = FDPOLL(fdp);

	if (idx < 0 || idx >= nfd->nfiles) {
		LOG_ERROR (CHEROKEE_ERROR_FDPOLL_POLL_DEL, fd, idx);
		return ret_error;
	}

	/* Check the fd counter.
	 */
	if (cherokee_fdpoll_is_empty (nfd)) {
		SHOULDNT_HAPPEN;
		return ret_error;
	}

	/* Decrease the total number of descriptors
	 */
	nfd->npollfds--;

	/* Move the last one to the empty space
	 */
	if (idx != nfd->npollfds) {
		fdp->pollfds[idx] = fdp->pollfds[nfd->npollfds];
		fdp->fdidx[fdp->pollfds[idx].fd] = idx;
	}

	/* Clean the new empty entry
	 */
	fdp->fdidx[fd]                      = -1;
	fdp->pollfds[nfd->npollfds].fd      = -1;
	fdp->pollfds[nfd->npollfds].events  = 0;
	fdp->pollfds[nfd->npollfds].revents = 0;

	return ret_ok;
}


static int
_check (cherokee_fdpoll_poll_t *fdp, int fd, int rw_mode)
{
	int revents;
	int idx      = fdp->fdidx[fd];

	if (idx < 0 || idx >= FDPOLL(fdp)->nfiles)
		return -1;

	revents = fdp->pollfds[idx].revents;

	/* Actual result */
	if ((rw_mode & poll_mode_read) && (revents & POLLIN)) {
		return 1;
	}
	if ((rw_mode & poll_mode_write) && (revents & POLLOUT)) {
		return 1;
	}

	/* Error */
	if (revents & (POLLERR|POLLHUP|POLLNVAL)) {
		return 1;
	}

	return 0;
}


static ret_t
_reset (cherokee_fdpoll_poll_t *fdp, int fd)
{
	UNUSED(fdp);
	UNUSED(fd);

	/* fdp->fdidx[fd] = -1;
	 */
	return ret_ok;
}


static int
_watch (cherokee_fdpoll_poll_t *fdp, int timeout_msecs)
{
	int re;

	if (unlikely (FDPOLL(fdp)->npollfds < 0)) {
		SHOULDNT_HAPPEN;
	}

	re = poll (fdp->pollfds, FDPOLL(fdp)->npollfds, timeout_msecs);

#if 0
	{
		int                i;
		cherokee_fdpoll_t *nfd = FDPOLL(fdp);

		printf ("total=%d = ", nfd->npollfds);
		for (i=0; i < nfd->npollfds; i++) {
			printf ("fd=%d[%d,%d], ", fdp->pollfds[i].fd, fdp->pollfds[i].events, fdp->pollfds[i].revents);
		}
		printf ("\n");
	}
#endif

	return re;
}


ret_t
fdpoll_poll_get_fdlimits (cuint_t *system_fd_limit, cuint_t *fd_limit)
{
	*system_fd_limit = 0;
	*fd_limit        = 0;

	return ret_ok;
}


ret_t
fdpoll_poll_new (cherokee_fdpoll_t **fdp, int system_fd_limit, int fd_limit)
{
	int                i;
	cherokee_fdpoll_t *nfd;
	CHEROKEE_CNEW_STRUCT (1, n, fdpoll_poll);

	nfd = FDPOLL(n);

	/* Init base class properties
	 */
	nfd->type          = cherokee_poll_poll;
	nfd->nfiles        = fd_limit;
	nfd->system_nfiles = system_fd_limit;
	nfd->npollfds      = 0;

	/* Init base class virtual methods
	 */
	nfd->free      = (fdpoll_func_free_t) _free;
	nfd->add       = (fdpoll_func_add_t) _add;
	nfd->del       = (fdpoll_func_del_t) _del;
	nfd->reset     = (fdpoll_func_reset_t) _reset;
	nfd->set_mode  = (fdpoll_func_set_mode_t) _set_mode;
	nfd->check     = (fdpoll_func_check_t) _check;
	nfd->watch     = (fdpoll_func_watch_t) _watch;

	/* Get memory: pollfds structs
	 */
	n->pollfds = (struct pollfd *) calloc (nfd->nfiles, sizeof(struct pollfd));
	if (n->pollfds == NULL) {
		_free(n);
		return ret_nomem;
	}
	for (i = 0; i < nfd->nfiles; i++) {
		n->pollfds[i].fd      = -1;
		n->pollfds[i].events  =  0;
		n->pollfds[i].revents =  0;
	}

	/* Get memory: reverse fd index
	 */
	n->fdidx = (int*) calloc (nfd->system_nfiles, sizeof(int));
	if (n->fdidx == NULL) {
		_free(n);
		return ret_nomem;
	}
	for (i = 0; i < nfd->system_nfiles; i++) {
		n->fdidx[i] = -1;
	}

	/* Return the object
	 */
	*fdp = nfd;
	return ret_ok;
}

