// Copyright (c) 2002 David Muse
// See the COPYING file for more information

#include <rudiments/listener.h>
#include <rudiments/error.h>

#include <stdlib.h>

// some systems need string.h to provide memset() for FD_ZERO/FD_SET
#include <string.h>
#include <sys/time.h>
#ifdef RUDIMENTS_HAVE_SYS_SELECT_H
	#include <sys/select.h>
#endif
#ifdef RUDIMENTS_HAVE_UNISTD_H
	#include <unistd.h>
#endif

#ifdef RUDIMENTS_NAMESPACE
namespace rudiments {
#endif

class listenerprivate {
	friend class listener;
	private:
		listenerlist	_filedescriptorlist;
		listenerlist	_readylist;
		bool		_retryinterruptedwaits;
};

listener::listener() {
	pvt=new listenerprivate;
	pvt->_retryinterruptedwaits=true;
}

listener::~listener() {
	removeAllFileDescriptors();
	delete pvt;
}

void listener::retryInterruptedWaits() {
	pvt->_retryinterruptedwaits=true;
}

void listener::dontRetryInterruptedWaits() {
	pvt->_retryinterruptedwaits=false;
}

void listener::addFileDescriptor(filedescriptor *fd) {
	pvt->_filedescriptorlist.append(fd);
}

void listener::removeFileDescriptor(filedescriptor *fd) {
	pvt->_filedescriptorlist.removeByData(fd);
}

void listener::removeAllFileDescriptors() {
	pvt->_filedescriptorlist.clear();
}

int listener::waitForNonBlockingRead(long sec, long usec) {
	return safeSelect(sec,usec,true,false);
}

int listener::waitForNonBlockingWrite(long sec, long usec) {
	return safeSelect(sec,usec,false,true);
}

listenerlist *listener::getReadyList() {
	return &pvt->_readylist;
}

int listener::safeSelect(long sec, long usec, bool read, bool write) {

	// set up the timeout
	timeval	tv;
	timeval	*tvptr=(sec>-1 && usec>-1)?&tv:NULL;

	for (;;) {

		// if we're using ssl, some of the filedescriptors may have
		// SSL data pending, in that case, we need to bypass the
		// select altogether and just return those filedescriptors
		// in the ready list immediately
		#ifdef RUDIMENTS_HAS_SSL
			pvt->_readylist.clear();
		#endif
		int	selectresult=0;

		// some versions of select modify the timeout, so reset it
		// every time
		tv.tv_sec=sec;
		tv.tv_usec=usec;

		// select() will modify the list every time it's called
		// so the list has to be rebuilt every time...
		fd_set	fdlist;
		int	largest=-1;
		FD_ZERO(&fdlist);
		listenerlistnode	*current=
				pvt->_filedescriptorlist.getNodeByIndex(0);
		while (current) {

			if (current->getData()->getFileDescriptor()>largest) {
				largest=current->getData()->getFileDescriptor();
			}

			FD_SET(current->getData()->getFileDescriptor(),&fdlist);

			// if we support SSL, check here to see if the
			// filedescriptor has SSL data pending
			#ifdef RUDIMENTS_HAS_SSL
				SSL	*ssl=current->getData()->getSSL();
				if (ssl && SSL_pending(ssl)) {
					pvt->_readylist.append(
						current->getData());
					selectresult++;
				}
			#endif

			current=current->getNext();
		}

		// if we support SSL and at least 1 of the filedescriptors
		// had SSL data pending, return here
		#ifdef RUDIMENTS_HAS_SSL
			if (selectresult) {
				return selectresult;
			}
		#endif

		// wait for data to be available on the file descriptor
		selectresult=select(largest+1,(read)?&fdlist:NULL,
						(write)?&fdlist:NULL,
						NULL,tvptr);

		if (selectresult==-1) {

			// if a signal caused the select to fall through, retry
			if (pvt->_retryinterruptedwaits &&
				error::getErrorNumber()==EINTR) {
				continue;
			}
			return RESULT_ERROR;

		} else if (!selectresult) {

			// timeout
			return RESULT_TIMEOUT;
		}
	
		// build the list of file descriptors that
		// caused the select() to fall through
		#ifndef RUDIMENTS_HAS_SSL
			pvt->_readylist.clear();
		#endif
		current=pvt->_filedescriptorlist.getNodeByIndex(0);
		while (current) {
			if (FD_ISSET(current->getData()->getFileDescriptor(),
					&fdlist)) {
				pvt->_readylist.append(current->getData());
			}
			current=current->getNext();
		}

		// return the number of file descriptors that
		// caused the select() to fall through
		return selectresult;
	}
}

#ifdef RUDIMENTS_NAMESPACE
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1