/*******************************************************************************
 *	BPoll.cpp	File poll class
 *			T.Barnaby,	BEAM Ltd,	1/4/05
 *	updated by	D.Korchagin,	CERN AB-BI-SW,	2007-08-31
 *******************************************************************************
 */
#include	<stdlib.h>
#include	<unistd.h>
#include	<errno.h>
#include	<BPoll.h>

#ifndef __Lynx__
#else
#include <string.h>

/* Few defines for LynxOS portability  NMN */
#define howmany(x,y)   (((x)+((y)-1))/(y))
#define nfds_t unsigned long int
#define roundup(x, y) ((((x) + ((y) - 1)) / (y)) * (y))
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)

int poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
	static int max_fd_size;
	struct timeval tv;
	fd_set *rset, *wset, *xset;
	struct pollfd *f;
	int ready;
	int maxfd = 0;
	int bytes;
	
	if (!max_fd_size) max_fd_size = getdtablesize ();
	bytes = howmany (max_fd_size, NFDBITS);
	rset = (fd_set *) alloca (bytes);
	wset = (fd_set *) alloca (bytes);
	xset = (fd_set *) alloca (bytes);
	
	/* We can't call FD_ZERO, since FD_ZERO only works with sets of exactly FD_SETSIZE size.  */
	bzero (rset, bytes);
	bzero (wset, bytes);
	bzero (xset, bytes);
	
	for (f = fds; f < &fds[nfds]; ++f)
	{
		f->revents = 0;
		if (f->fd >= 0)
		{
			if (f->fd >= max_fd_size)
			{
				/* The user provides a file descriptor number which is higher than the maximum we got from the `getdtablesize' call. Maybe this is ok so enlarge the arrays.  */
				fd_set *nrset, *nwset, *nxset;
				int nbytes;
				max_fd_size = roundup (f->fd, NFDBITS);
				nbytes = howmany (max_fd_size, NFDBITS);
				nrset = (fd_set *) alloca (nbytes);
				nwset = (fd_set *) alloca (nbytes);
				nxset = (fd_set *) alloca (nbytes);
				bzero ((char *) nrset + bytes, nbytes - bytes);
				bzero ((char *) nwset + bytes, nbytes - bytes);
				bzero ((char *) nxset + bytes, nbytes - bytes);
				rset = (fd_set *) memcpy (nrset, rset, bytes);
				wset = (fd_set *) memcpy (nwset, wset, bytes);
				xset = (fd_set *) memcpy (nxset, xset, bytes);
				bytes = nbytes;
			}
			if (f->events & POLLIN) FD_SET (f->fd, rset);
			if (f->events & POLLOUT) FD_SET (f->fd, wset);
			if (f->events & POLLPRI) FD_SET (f->fd, xset);
			if (f->fd > maxfd && (f->events & (POLLIN|POLLOUT|POLLPRI))) maxfd = f->fd;
		}
	}
	
	tv.tv_sec = timeout / 1000;
	tv.tv_usec = (timeout % 1000) * 1000;
	
	while (true)
	{
		ready = select (maxfd + 1, rset, wset, xset, timeout == -1 ? NULL : &tv);
		
		/* It might be that one or more of the file descriptors is invalid. We now try to find and mark them and then try again.  */
		if (ready == -1 && errno == EBADF)
		{
			fd_set *sngl_rset = (fd_set *) alloca (bytes);
			fd_set *sngl_wset = (fd_set *) alloca (bytes);
			fd_set *sngl_xset = (fd_set *) alloca (bytes);
			struct timeval sngl_tv;
			
			/* Clear the original set.  */
			bzero (rset, bytes);
			bzero (wset, bytes);
			bzero (xset, bytes);
			
			/* This means we don't wait for input.  */
			sngl_tv.tv_sec = 0;
			sngl_tv.tv_usec = 0;
			maxfd = -1;
			
			/* Reset the return value.  */
			ready = 0;
			
			for (f = fds; f < &fds[nfds]; ++f)
				if (f->fd != -1 && (f->events & (POLLIN|POLLOUT|POLLPRI)) && (f->revents & POLLNVAL) == 0)
				{
					int n;
					bzero (sngl_rset, bytes);
					bzero (sngl_wset, bytes);
					bzero (sngl_xset, bytes);
					if (f->events & POLLIN) FD_SET (f->fd, sngl_rset);
					if (f->events & POLLOUT) FD_SET (f->fd, sngl_wset);
					if (f->events & POLLPRI) FD_SET (f->fd, sngl_xset);
					n = select (f->fd + 1, sngl_rset, sngl_wset, sngl_xset, &sngl_tv);
					if (n != -1)
					{
						/* This descriptor is ok.  */
						if (f->events & POLLIN) FD_SET (f->fd, rset);
						if (f->events & POLLOUT) FD_SET (f->fd, wset);
						if (f->events & POLLPRI) FD_SET (f->fd, xset);
						if (f->fd > maxfd) maxfd = f->fd;
						if (n > 0) ++ready; /* Count it as being available.  */
					}
					else if (errno == EBADF)
						f->revents |= POLLNVAL;
				}
				/* Try again.  */
				continue;
		}
		break;
	}
	
	if (ready > 0) for (f = fds; f < &fds[nfds]; ++f)
	{
		if (f->fd >= 0)
		{
			if (FD_ISSET (f->fd, rset)) f->revents |= POLLIN;
			if (FD_ISSET (f->fd, wset)) f->revents |= POLLOUT;
			if (FD_ISSET (f->fd, xset)) f->revents |= POLLPRI;
		}
	}
	return ready;
}
#endif

BPoll::BPoll(){
	ofdsNum = 0;
	ofds = 0;
	ofdsNext = 0;
}

BPoll::~BPoll(){
	if(ofds)
		free(ofds);
	ofds = 0;
	ofdsNum = 0;
	ofdsNext = 0;
}

void BPoll::append(int fd, int events){
	ofds = (PollFd*)realloc(ofds, (ofdsNum + 1) * sizeof(PollFd));
	ofds[ofdsNum].fd = fd;
	ofds[ofdsNum].events = events;
	ofds[ofdsNum].revents = 0;
	ofdsNum++;
}

void BPoll::delFd(int fd){
	int	n;
	
	for(n = 0; n < ofdsNum; n++){
		if(ofds[n].fd == fd){
			break;
		}
	}
	
	if((n + 1) < ofdsNum)
		memcpy(&ofds[n], &ofds[n + 1], (ofdsNum - (n + 1)) * sizeof(PollFd));
	
	ofdsNext = 0;

	ofdsNum--;
	ofds = (PollFd*)realloc(ofds, ofdsNum * sizeof(PollFd));
}

int BPoll::nextFd(int i){
	i++;
	if(i >= ofdsNum)
		i = 0;
	return i;
}

int BPoll::getPollFdsNum(){
	return ofdsNum;
}

BPoll::PollFd* BPoll::getPollFds(){
	return ofds;
}

BError BPoll::doPoll(int& fd, int timeoutUs){
	BError		err;
	int		timeoutMs;
	int		r;
	int		i;
	
	if(timeoutUs >= 0)
		timeoutMs = timeoutUs / 1000;
	else
		timeoutMs = -1;

	r = poll(ofds, ofdsNum, timeoutMs);

	if(r > 0){
		// Get next available fd in round robin fashion
		for(i = ofdsNext; ;){
			if(ofds[i].revents){
				fd = ofds[i].fd;
				ofdsNext = nextFd(i);
				break;
			}
			
			i = nextFd(i);
			if(i == ofdsNext)
				break;
		}
	}
	else if(r < 0){
		err.set(-errno, strerror(errno));
		fd = -errno;
	}
	else if(r == 0){
		err.set(-ETIMEDOUT, "Timeout");
		fd = -ETIMEDOUT;
	}

	return err;
}

BError BPoll::doPollEvents(int& fd, int& events, int timeoutUs){
	BError		err;
	int		timeoutMs;
	int		r;
	int		i;
	
	if(timeoutUs >= 0)
		timeoutMs = timeoutUs / 1000;
	else
		timeoutMs = -1;

	r = poll(ofds, ofdsNum, timeoutMs);

	if(r > 0){
		// Get next available fd in round robin fashion
		for(i = ofdsNext; ;){
			if(ofds[i].revents){
				fd = ofds[i].fd;
				events = ofds[i].revents;
				ofdsNext = nextFd(i);
				break;
			}
			
			i = nextFd(i);
			if(i == ofdsNext)
				break;
		}
	}
	else if(r < 0){
		err.set(-errno, strerror(errno));
		fd = -errno;
	}
	else if(r == 0){
		err.set(-ETIMEDOUT, "Timeout");
		fd = -ETIMEDOUT;
	}

	return err;
}

void BPoll::clear(){
	if(ofds)
		free(ofds);
	ofds = 0;
	ofdsNum = 0;
	ofdsNext = 0;
}
