/* Web Polygraph       http://www.web-polygraph.org/
 * (C) 2003-2006 The Measurement Factory
 * Licensed under the Apache License, Version 2.0 */

#include "xstd/xstd.h"

#include "xstd/h/os_std.h"

#include "xstd/Assert.h"
#include "xstd/Select.h"
#include "xstd/gadgets.h"


FD_Set::FD_Set(): theMaxFD(-1), theResCount(0) {
	FD_ZERO(&theSet);
	FD_ZERO(&theReadySet);
}

FD_Set::~FD_Set() {
}

void FD_Set::fdLimit(int) {
}

void FD_Set::setFD(int fd, FileScanUser *u) {
	Assert(fd >= 0 && u);

	FD_SET(fd, &theSet);
	if (fd > theMaxFD)
		theMaxFD = fd;
	theResCount++;
}

void FD_Set::clearFD(int fd) {
	Assert(fd >= 0);
	Assert(fd <= theMaxFD);

	FD_CLR(fd, &theSet);
	FD_CLR(fd, &theReadySet); // poll() cannot do that

	if (fd == theMaxFD) {
		while (theMaxFD >= 0 && !FD_ISSET(theMaxFD, &theSet)) {
			theMaxFD--;
		}
	}

	theResCount--;
}


/* Select */

Select::Select() {
}

void Select::configure(int fdLimit) {
	FileScanner::configure(fdLimit);
	theSets[dirRead].fdLimit(fdLimit);
	theSets[dirWrite].fdLimit(fdLimit);
}

FileScanReserv Select::setFD(int fd, IODir dir, FileScanUser *u) {
	theSets[dir].setFD(fd, u);
	if (fd > theMaxFD)
		theMaxFD = fd;
	return FileScanner::setFD(fd, dir, u);
}

void Select::clearFD(int fd, IODir dir) {
	theSets[dir].clearFD(fd);
	if (fd == theMaxFD)
		theMaxFD = Max(theSets[dirRead].maxFD(), theSets[dirWrite].maxFD());
	FileScanner::clearFD(fd, dir);
}

bool Select::interestedUser(int idx, IODir dir) const {
	return theSets[dir].isSet(idx) && theRegs[idx].blocked() != -dir;
}

FileScanUser *Select::readyUser(int idx, IODir dir, int &fd) {
	fd = idx;
	return theSets[dir].isReady(fd) ? theRegs[fd].user() : 0;
}

fd_set *Select::prepReadySet(IODir dir) {
	if (fd_set *res = theSets[dir].prepReadySet()) {
		for (int fd = 0; fd <= theMaxFD; ++fd) {
			if (!interestedUser(fd, dir))
				FD_CLR(fd, res);
		}
		return res;
	}
	return 0;
}

int Select::sweep(Time *timeout) {
	// have to check all fds, cannot choose like poll() does
	const int hotCount = theMaxFD+1;

	fd_set *rdSet = prepReadySet(dirRead);
	fd_set *wrSet = prepReadySet(dirWrite);
	fd_set *excSet = 0;
#	if defined(WIN32)
		// MS Windows reports connect(2) failures via exception set
		// collect exceptions and later "backport" to wrSet to fake
		// Unix-like behavior
		if (wrSet) {
			static fd_set es;
			es = *wrSet;
			excSet = &es;
		} else
		if (!rdSet) {
			// Windows select(2) fails if no FDs are checked for
			// we must emulate sleep instead of calling select(2)
			if (timeout)
				sleep(timeout->sec());
			return 0;
		}
#	endif

	const int res = ::select(hotCount, rdSet, wrSet, excSet, timeout);

#	if defined(WIN32)
		if (res > 0 && excSet && wrSet) {
			for (int fd = 0; fd < hotCount; ++fd) {
				if (FD_ISSET(fd, excSet))
					FD_SET(fd, wrSet);
			}
		}
#	endif

	return res <= 0 ? res : hotCount;
}


syntax highlighted by Code2HTML, v. 0.9.1