/*--------------------------------------------------------------------------
 Copyright 1999,2000, Dan Kegel http://www.kegel.com/
 See the file COPYING
 (Also freely licensed to Disappearing, Inc. under a separate license
 which allows them to do absolutely anything they want with it, without
 regard to the GPL.)

 This module is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This module 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
--------------------------------------------------------------------------*/
#include "dprint.h"
#include "Poller_select.h"

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>

int Poller_select::init()
{
	DPRINT(("Poller_select::init()\n"));

	m_max_fd = -1;
	m_cur_fd = -1;
	m_rbits = 0;

	memset(m_clients, 0, FD_SETSIZE*sizeof(m_clients[0]));

	// Clear file descriptor sets
	FD_ZERO(&m_readfds);
	FD_ZERO(&m_testrfds);
	FD_ZERO(&m_testwfds);
	FD_ZERO(&m_writefds);
#ifdef Poller_URGENT
	FD_ZERO(&m_exceptfds);
	FD_ZERO(&m_testefds);
#endif

	Poller::init();

	return 0;
}

void Poller_select::shutdown()
{
	if (m_max_fd != -1) {
		m_max_fd = -1;
		m_cur_fd = -1;
		m_rbits = 0;
	}
	Poller::shutdown();
}

int Poller_select::add(int fd, Client *client, short eventmask)
{
	if ((fd < 0) || (fd >= FD_SETSIZE)) {
		LOG_ERROR(("add(fd %d): fd out of range\n", fd));
		return EINVAL;
	}
	if (m_clients[fd]) {
		LOG_ERROR(("add(fd %d): already monitoring that fd!\n", fd));
		return EALREADY;
	}

	// Update limits.
	if (fd > m_max_fd)
		m_max_fd = fd;

	// Finally, add this fd and its eventmask.
	m_clients[fd] = client;
	setMask(fd,eventmask);

	DPRINT(("add(fd %d, %p, %x) max_fd %d\n",
		fd, client, eventmask, m_max_fd));
	return 0;
}

int Poller_select::del(int fd)
{
	// Sanity checks
	if ((fd < 0) || (fd >= FD_SETSIZE)) {
		LOG_ERROR(("del(fd %d): fd out of range\n", fd));
		return EINVAL;
	}
	if (!m_clients[fd]) {
		LOG_ERROR(("del(fd %d): not monitoring that fd!\n", fd));
		return EALREADY;
	}
	assert(m_max_fd >= fd);

	// Remove this fd
	setMask(fd,0);

	// Fix bug reported by Jeon: make sure getNextEvent doesn't
	// return a deleted client
	FD_CLR(fd,&m_testrfds);
	FD_CLR(fd,&m_testwfds);
#ifdef Poller_URGENT
	FD_CLR(fd,&m_testefds);
#endif

	m_clients[fd] = NULL;

	// Update limits
	while ((m_max_fd >= 0) && (m_clients[m_max_fd] == NULL)) 
		m_max_fd--;
		
	return 0;
}

int Poller_select::setMask(int fd, short eventmask)
{
	// Sanity checks
	if ((fd < 0) || (fd >= FD_SETSIZE)) {
		LOG_ERROR(("setMask(fd %d): fd out of range\n", fd));
		return EINVAL;
	}
	if (!m_clients[fd]) {
		LOG_ERROR(("setMask(fd %d): not monitoring that fd!\n", fd));
		return EALREADY;
	}
	assert(m_max_fd >= fd);

	FD_CLR(fd,&m_readfds);
	if (eventmask & POLLIN) FD_SET(fd,&m_readfds);
	FD_CLR(fd,&m_writefds);
	if (eventmask & POLLOUT) FD_SET(fd,&m_writefds);
#ifdef Poller_URGENT
	FD_CLR(fd,&m_exceptfds);
	if (eventmask & POLLPRI) FD_SET(fd,&m_exceptfds);
#endif

	DPRINT(("setMask(fd %d, %x)\n", fd, eventmask));

	return 0;
}

int Poller_select::orMask(int fd, short eventmask)
{
	// Sanity checks
	if ((fd < 0) || (fd >= FD_SETSIZE)) {
		LOG_ERROR(("orMask(fd %d): fd out of range\n", fd));
		return EINVAL;
	}
	if (!m_clients[fd]) {
		LOG_ERROR(("orMask(fd %d): not monitoring that fd!\n", fd));
		return EALREADY;
	}
	assert(m_max_fd >= fd);

	if (eventmask & POLLIN) FD_SET(fd,&m_readfds);
	if (eventmask & POLLOUT) FD_SET(fd,&m_writefds);
#ifdef Poller_URGENT
	if (eventmask & POLLPRI) FD_SET(fd,&m_exceptfds);
#endif

	DPRINT(("orMask(fd %d, %x)\n", fd, eventmask));

	return 0;
}

int Poller_select::andMask(int fd, short eventmask)
{
	// Sanity checks
	if ((fd < 0) || (fd >= FD_SETSIZE)) {
		LOG_ERROR(("andMask(fd %d): fd out of range\n", fd));
		return EINVAL;
	}
	if (!m_clients[fd]) {
		LOG_ERROR(("andMask(fd %d): not monitoring that fd!\n", fd));
		return EALREADY;
	}
	assert(m_max_fd >= fd);

	if (!(eventmask & POLLIN)) FD_CLR(fd,&m_readfds);
	if (!(eventmask & POLLOUT)) FD_CLR(fd,&m_writefds);
#ifdef Poller_URGENT
	if (!(eventmask & POLLPRI)) FD_CLR(fd,&m_exceptfds);
#endif

	DPRINT(("andMask(fd %d, %x)\n", fd, eventmask));

	return 0;
}

/**
 Sleep at most timeout_millisec waiting for an I/O readiness event
 on the file descriptors we're watching.  Fills internal array
 of readiness events.  Call getNextEvent() repeatedly to read its
 contents.
 @return 0 on success, EWOULDBLOCK if no events ready
 */
int Poller_select::waitForEvents(int timeout_millisec)
{
	int err;
	struct timeval timeout;

	// Set timeout
	timeout.tv_sec  = timeout_millisec/1000;
	timeout.tv_usec = (timeout_millisec % 1000) * 1000;

	// Reset file descriptor sets
	// FIXME: can't do this on some operating systems.
	m_testrfds=m_readfds;
	m_testwfds=m_writefds;
#ifdef Poller_URGENT
	m_testefds=m_exceptfds;
#endif

	// Wait for I/O events the clients are interested in.
	m_rbits = select(m_max_fd+1, &m_testrfds, &m_testwfds, 
#ifdef Poller_URGENT
	&m_testefds, 
#else
	(fd_set *)0,
#endif
	&timeout);
	if (m_rbits == -1) {
		err = errno;
		DPRINT(("waitForEvents: select() returned -1, errno %d\n", err));
		return err;
	}

	m_cur_fd = -1;		// it will get preincremented by getNextEvent
	DPRINT(("waitForEvents: got %d events\n", m_rbits));
	return m_rbits ? 0 : EWOULDBLOCK;
}

/**
 Get the next event that was found by waitForEvents.
 @return 0 on success, EWOULDBLOCK if no more events
 */
int Poller_select::getNextEvent(Poller::PollEvent *e)
{
	int revents;
	DPRINT(("getNextEvent: m_rbits %d\n",m_rbits));

	if (m_rbits < 1)
		return EWOULDBLOCK;

	// Find the next fd with bits set
	for (revents=0; m_cur_fd++ < m_max_fd; ) {
		// Avoiding branches inside this loop only improves benchmark by 2%,
		// so don't bother.
		if (FD_ISSET(m_cur_fd, &m_testrfds)) revents = POLLIN;
		if (FD_ISSET(m_cur_fd, &m_testwfds)) revents |= POLLOUT;
#ifdef Poller_URGENT
		if (FD_ISSET(m_cur_fd, &m_testefds)) revents |= POLLPRI;
#endif
		if (revents)
			break;
	}
	if (!revents) {
		// None found
		return EWOULDBLOCK;
	}

	// Found one.  Update count of set bits for next call.
	if (revents & POLLIN)  m_rbits--;
	if (revents & POLLOUT) m_rbits--;
#ifdef Poller_URGENT
	if (revents & POLLPRI) m_rbits--;
#endif

	// Fill event struct.
	e->fd = m_cur_fd;
	e->revents = revents;
	e->client = m_clients[m_cur_fd];

	DPRINT(("getNextEvent: fd %d revents %x m_rbits %d\n",
		e->fd, e->revents, m_rbits));

	return 0;
}

int Poller_select::waitAndDispatchEvents(int timeout_millisec)
{
	int err;
	PollEvent event;

	err = waitForEvents(timeout_millisec);
	if (err)
		return err;

	// Pump any network traffic into the appropriate Clients
	while (m_rbits > 0) {
		err = getNextEvent(&event);
		if (err) {
			if (err != EWOULDBLOCK)
				DPRINT(("waitAndDispatchEvents: getNextEvent() returned %d\n", err));
			break;
		}
		err = event.client->notifyPollEvent(&event);
		if (err) {
			DPRINT(("waitAndDispatchEvents: %p->notifyPollEvent(fd %d) returned %d, deleting\n",
				event.client, event.fd, err));
			del(event.fd);
		}
	}

	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1