#include <stdio.h>
#include <string.h>
#include "cdevReactor.h"

// *****************************************************************************
// * This is the number of times that InitializeNetwork was called to perform
// * network initialization.  The InitializeNetwork and corresponding 
// * TerminateNetwork methods are wrappers to provide support for WIN32
// * WSAStartup and WSACleanup methods.  They perform no actiual function on
// * UNIX platforms.
// *****************************************************************************
int cdevReactor::netInitCount = 0;

// *****************************************************************************
// * cdevReactor::cdevReactor :
// *	This is the constructor for the cdevReactor class.  It takes no
// *	arguments.  It will set maxEntries to the value of the MAX_SIZE
// *	variable and the size variable (the highest fd installed) will be set
// *	to zero.
// *
// *	The method will allocate space in the handlers array for the 
// *	cdevEventHandlers that will be installed during operation.  The 
// *	read_set, write_set and except_set masks will be cleared.  These masks
// *	will be populated later as file descriptors are added to the 
// *	cdevReactor.
// *****************************************************************************
cdevReactor::cdevReactor ( void )
	: maxEntries(cdevHandleSet::MAX_SIZE), 
	  size      (0),
	  handlers  (NULL),
	  timers    (NULL)
	{
	if(!netInitCount)
		{
		if(InitializeNetwork()==0) netInitCount++;
		}
	else netInitCount++;

	handlers =  (cdevEventHandler **)malloc((maxEntries+1)*sizeof(cdevEventHandler *));
	memset(handlers, 0, sizeof(cdevEventHandler *)*(maxEntries+1));
	read_set.reset();	
	write_set.reset();	
	except_set.reset();	
	}


// *****************************************************************************
// * cdevReactor::~cdevReactor :
// *	This is the destructor for the cdevReactor class.  It wil first remove
// *	all of the timer cdevEventHandlers from the timer array, it will then
// *	remove all of the file-descriptor based cdevEventHandlers from the
// *	handlers array.  It will finish by deleteing the handlers array that
// *	was allocated in the constructor.
// *****************************************************************************
cdevReactor::~cdevReactor ( void )
	{
	if(netInitCount && (-netInitCount)==0) TerminateNetwork();

	while(timers!=NULL) removeHandler(timers);
	for(int i=0; i<=maxEntries; i++)
		{
		if(handlers[i]!=NULL) removeHandler(handlers[i]);
		}
	delete handlers;
	}


// *****************************************************************************
// * cdevReactor::calculateMask :
// *	This method will walk through the handlers array and setup the read_set,
// *	write_set and except_set masks.
// *****************************************************************************
void cdevReactor::calculateMask ( void )
	{
	int i;
	read_set.reset();	
	write_set.reset();	
	except_set.reset();	
	size = 0;

	for(i=0; i<=maxEntries; i++)
		{
		unsigned mask;
		if(handlers[i]!=NULL)
			{
			mask = handlers[i]->getMask();
			if(!(mask&cdevEventHandler::DONT_CALL))
				{
				if(mask&cdevEventHandler::READ_MASK) 
					{
					read_set.set_bit(i);	
					size = i+1;
					}
				if(mask&cdevEventHandler::WRITE_MASK)
					{
					write_set.set_bit(i);	
					size = i+1;
					}
				if(mask&cdevEventHandler::EXCEPT_MASK)
					{
					except_set.set_bit(i);	
					size = i+1;
					}
				}
			}
		}
	}
	
	
	
// *****************************************************************************
// * cdevReactor::calculateTimeout :
// *	This method will walk through the timers and determine the duration of
// *	time that the cdevReactor should wait for events on the file 
// *	descriptors before automatically terminating.
// *****************************************************************************
int cdevReactor::calculateTimeout ( cdevTime defaultPeriod, struct timeval &timeout )
	{
	if(defaultPeriod==cdevTime(0,0)) timeout.tv_sec=(timeout.tv_usec=0);
	else	{		
		cdevTime           now;
		cdevTime           target;
		cdevEventHandler * timer;
		
		if(defaultPeriod<cdevTime(0,0)) defaultPeriod.setTime(60, 0);

		now.setTime();
		target.setTime(now+defaultPeriod);
		
		for(timer=timers; timer!=NULL && target>now; timer=timer->getNext())
			{
			cdevTime nextTimeout = timer->getNextTimeout();
				
			if(!(timer->getMask()&cdevEventHandler::DONT_CALL) &&
			   (double)nextTimeout > 0.0)
				{
				if(nextTimeout<target) target = nextTimeout;
				}
			}
		
		if(target<=now) timeout.tv_sec=(timeout.tv_usec=0);
		else            timeout=target-now+(cdevTime)0.01;
		}
		
	return SUCCESS;
	}

// *****************************************************************************
// * cdevReactor::checkHandlers :
// *	This method is used to remove any cdevEventHandlers that have dead
// *	sockets from the cdevReactor prior to beginning I/O operations.  The
// *	method calls select with all of the file handles installed. If select
// *	returns a -1, then each of the file handles is checked independently.
// *	When the bad handler is identified, its cdevEventHandler object will
// *	be elliminated.

// *	This method returns the number of cdevEventHandlers that were deleted.
// *****************************************************************************
int cdevReactor::checkHandlers ( void )
	{
	int i;
	int eventCnt = 0;
	struct timeval t;

	read_set.reset();	
	write_set.reset();			
	except_set.reset();	
	t.tv_sec  = 0;
	t.tv_usec = 0;

	for(i=0; i<maxEntries; i++) 
		{
		if(handlers[i]!=NULL)
			{
			read_set.set_bit(i);	
			size = i+1;
			}
		}

	if(cdevSelect(size, read_set, write_set, except_set, &t)<0)
		{
		for(i=0; i<maxEntries; i++)
			{
			if(handlers[i]!=NULL)
				{
				read_set.set_bit(i);
					
				if(cdevSelect(i, read_set, write_set, except_set, &t)<0)
					{
					if(handlers[i]->handleSignal()<0)
						{
						removeHandler(handlers[i]);
						eventCnt++;
						}
					}
				read_set.clr_bit(i);
				}
			}
		}
	return eventCnt;
	}

// *****************************************************************************
// * cdevReactor::registerHandler :
// *	This method is called to register a file-descriptor based 
// *	cdevEventHandler with the reactor.  It will first check the validity
// *	of the handler and its file descriptor.  It will then determine if
// *	another handler already occupies the position associated with the
// *	specified handle.  
// *
// *	If the handler is valid, it will install it in the handlers array and
// *	will add it to the read_set, write_set and except_set masks as
// *	required.  The size variable will be incremented if the new handler
// *	has the highest file-descriptor in the array.
// *****************************************************************************
int cdevReactor::registerHandler ( cdevEventHandler * handler, unsigned mask )
	{
	int            fd     = -1;
	REACTOR_RESULT result = SUCCESS;
	
	if(handler == NULL)
		{
		result = INVALID_HANDLER;
		}
	else if(handler->getReactor()!=NULL && handler->getReactor()!=this)
		{
		result = UNKNOWN_HANDLER;
		}
	else if((fd = handler->getHandle())<=0)
		{
		result = INVALID_HANDLE;
		}
	else if(fd<maxEntries && handlers[handler->getHandle()]!=NULL) 
		{
		result = HANDLE_EXISTS;
		}
	else	{
		if(fd>=maxEntries)
			{
			int oldMaxEntries = maxEntries;
			maxEntries = fd+1;
			handlers = (cdevEventHandler **)realloc(handlers, (maxEntries+1) * sizeof(cdevEventHandler *));
			memset(&handlers[oldMaxEntries], 0, sizeof(cdevEventHandler *)*((maxEntries+1)-oldMaxEntries));
			}
		handler->setReactor(this);
		handler->setMask (mask);
		handlers[fd] = handler;
		}

	return (int)result;
	}
	

// *****************************************************************************
// * cdevReactor::removeHandler :
// *	This method allows the caller to remove an cdevEventHandler from the
// *	cdevReactor and delete it.  The method will call the extractHandler
// *	method to remove the cdevEventHandler from the cdevReactor after which
// *	the handler will be deleted.
// *****************************************************************************
int cdevReactor::removeHandler ( cdevEventHandler * handler )
	{
	REACTOR_RESULT result = INVALID_HANDLER;
	if(handler!=NULL)
		{
		extractHandler(handler);
		delete handler;
		result = SUCCESS;
		}
	return (int)result;
	}
	
// *****************************************************************************
// * cdevReactor::removeHandler :
// *	This method will remove the specified handler using the file descriptor
// *	provided by the caller.
// *****************************************************************************
int cdevReactor::removeHandler ( int fd )
	{
	REACTOR_RESULT result = SUCCESS;

	if(fd>0 && fd<=maxEntries) 
		{
		result = (REACTOR_RESULT)removeHandler(handlers[fd]);
		}
	else result = INVALID_HANDLE;
	
	return (int) result;
	}
	
// *****************************************************************************
// * cdevReactor::extractHandler :
// *	This method will remove the specified cdevEventHandler from the 
// *	cdevReactor, however, it will not delete the cdevEventHandler when
// *	finished.
// *****************************************************************************
int cdevReactor::extractHandler ( cdevEventHandler * handler )
	{
	int                fd        = -1;
	REACTOR_RESULT     result    = UNKNOWN_HANDLER;
	
	if(handler == NULL)                  result = INVALID_HANDLER;
	else	{
		cdevEventHandler * currTimer = timers;
		cdevEventHandler * prevTimer = NULL;
	
		while(currTimer!=NULL && currTimer!=handler)
			{
			prevTimer = currTimer;
			currTimer = prevTimer->getNext();
			}

		if(currTimer)
			{
			if(prevTimer) prevTimer->setNext(currTimer->getNext());
			else timers = currTimer->getNext();
			result = SUCCESS;
			}
			
		if((fd = handler->getHandle())>=0 && 
		    fd<=maxEntries                &&
		    handlers[fd]==handler)
			{
			handlers[fd] = NULL;
			result = SUCCESS;
			}
		else for(fd=0; fd<=maxEntries; fd++)
			{
			if(handlers[fd]==handler)
				{
				handlers[fd] = NULL;
				result       = SUCCESS;
				}
			}

		if(result==SUCCESS)
			{
			handler->setMask(0);
			handler->setReactor(NULL);
			}
		}

	return (int)result;
	}
	

// *****************************************************************************
// * cdevReactor::getHandler :
// *	This method allows the caller to retrieve the cdevEventHandler
// *	associated with a specific file descriptor.
// *****************************************************************************
int cdevReactor::getHandler ( int fd, cdevEventHandler * & handler )
	{
	REACTOR_RESULT result = SUCCESS;
	
	handler = NULL;
	
	if(fd<=0 || fd>maxEntries)              result = INVALID_HANDLE;
	else if((handler = handlers[fd])==NULL) result = INVALID_HANDLE;
	
	return (int) result;
	}
	
// *****************************************************************************
// * cdevReactor::registerTimer :
// *	This method will install a timer cdevEventHandler in the timers array.
// *	The handleTimeout method of this class will be called each time the
// *	timer expires.  The timer's handleTimeout method will be called 
// *	immediately, and then the timer will be set to expire at the next
// *	time period.
// *****************************************************************************
int cdevReactor::registerTimer ( cdevEventHandler * timer )
	{
	REACTOR_RESULT result = SUCCESS;
	
	if(timer==NULL)
		{
		result = INVALID_HANDLER;
		}
	else if(timer->getReactor()!=NULL && timer->getReactor()!=this)
		{
		result = UNKNOWN_HANDLER;
		}
	else if((double)timer->getTimeoutRate()<=0.0)
		{
		result = INVALID_TIMEOUT;
		}
	else	{
		timer->setReactor(this);
		timer->resetTimer();
		timer->setNext(timers);
		timers = timer;
		}
	
	return (int)result;
	}
	
// *****************************************************************************
// * cdevReactor::cancelTimer :
// *	This method will remove the timer from the array of timers that are
// *	being serviced by the reactor, however, it will NOT delete the 
// *	cdevEventHandler.  Additionally, if the handler is also being used
// *	for file-descriptor based operations - it will be not be removed from
// *	that array.
// *	
// *	The user is responsible for deleteing the cdevEventHandler if it will
// *	no longer be needed.
// *****************************************************************************
int cdevReactor::cancelTimer ( cdevEventHandler * timer )
	{
	REACTOR_RESULT result = SUCCESS;
	
	if(timer==NULL) result = INVALID_HANDLER;
	else
		{
		cdevEventHandler * currTimer = timers;
		cdevEventHandler * prevTimer = NULL;
		
		while(currTimer!=NULL && currTimer!=timer)
			{
			prevTimer = currTimer;
			currTimer = prevTimer->getNext();
			}

		if(currTimer) 
			{
			if(prevTimer) prevTimer->setNext(currTimer->getNext());
			else          timers = currTimer->getNext();
			currTimer->setNext(NULL);
			}
		else result = UNKNOWN_HANDLER;
		}

	return (int)result;		
	}
	

// *****************************************************************************
// * cdevReactor::handleFileEvent :
// *	This method will process the events that have occurred on a single
// *	file descriptor.  The caller must provide two arguments...
// *	
// *	fd_set * fds        : The fd_set to be checked 
// *	REACTOR_EVENT event : The type of event being processed...
// *	                      INPUT, OUTPUT, EXCEPTION, or SIGNAL.
// *	
// *	This method will return the number of events that have been processed
// *	to completion.  Note that if a cdevEventHandler returns a value greater
// *	than 0, it will be called again after all other ready file descriptors
// *	have been called - therefore, its occurance will not be included in the
// *	number of events returned by this method.
// *****************************************************************************
int cdevReactor::handleFileEvent ( cdevHandleSet * fds, REACTOR_EVENT event)
	{
	int result   = 0;
	int eventCnt = 0;
	int i;
	
	for(i=0; i<=size; i++)
		{
		if(fds->is_set(i))	
			{
			if(handlers[i])
				{
				if(event==INPUT)          result = handlers[i]->handleInput();
				else if(event==OUTPUT)    result = handlers[i]->handleOutput();
				else if(event==EXCEPTION) result = handlers[i]->handleExcept();
				else if(event==SIGNAL)    result = handlers[i]->handleSignal();
				
				if(result<=0) 
					{
					if(result<0) removeHandler(handlers[i]);
					eventCnt++;
					fds->clr_bit(i);	
					}
				}
			else eventCnt++;
			}
		}

	return eventCnt;
	}
	
// *****************************************************************************
// * cdevReactor::handleEvents :
// *	This method is called to allow the cdevReactor to wait for events to 
// *	occur on the cdevEventHandler objects.  This method will process events
// *	until the specified time period has expired - if a negative time period 
// *	(or no time period) has been specified, then it will process messages 
// *	until the next event occurs.
// *
// *	The flags parameter was added to allow the caller to specify either
// *	UNTIL_TIMEOUT or UNTIL_EVENT.  If the caller specifies UNTIL_TIMEOUT, 
// *	the cdevReactor will continue to handle events until the time period
// *	has expired.  If the caller specifies UNTIL_EVENT, the cdevReactor will
// *	return after the first SOCKET-BASED EVENT occurs or after the time
// *	expires, whichever comes first.
// * 
// *	Note:	If the cdevEventHandler may return a -1, 0, or 1.  These return 
// *		codes will have the following effect.
// *
// *		-1 : The cdevEventHandler will be removed from the cdevReactor
// *		     and deleted.
// *		 0 : The bit associated with the cdevEventHandler will be 
// *		     cleared and processing will continue.
// *		 1 : The bit associated with the cdevEventHandler will not be
// *		     cleared and it will be called to process more data after
// *		     after all of the subsequent cdevEventHandlers have been 
// *		     called - this allows the cdevEventHandler in a lengthy
// *		     process to yeild time back for processing other events.
// *****************************************************************************
int cdevReactor::handleEvents ( cdevTime period, int flags )
	{
	int            result   = 0;
	int            finished = 0;
	cdevTime       startTime, currTime;
	struct timeval timeout;
	
	startTime.setTime();
	
	do
		{ 
		int i;
		
		calculateMask   ();
		calculateTimeout(period, timeout); 	
		
		result=cdevSelect(size, read_set, write_set, except_set, &timeout);
		
		if(result<0)
			{
			finished = 1;
			checkHandlers();
			}
		else	{
			if(result>0)
				{
				int cnt = 0;
			
				while(cnt<result)
					{
					cnt+=handleFileEvent(&read_set, INPUT);
					cnt+=handleFileEvent(&write_set, OUTPUT);
					cnt+=handleFileEvent(&except_set, EXCEPTION); 
					}
				if(flags==UNTIL_EVENT && cnt>0) finished = 1;
				}

			if(timers!=NULL)
				{
				cdevEventHandler * timer=timers;
			
				currTime.setTime();
					
				while(timer!=NULL)
					{
					cdevTime nextTimeout = timer->getNextTimeout();
				
					if((timer->getMask()&cdevEventHandler::DONT_CALL)==0 &&
					    (double)nextTimeout>0.0 && nextTimeout<=currTime)
						{
						if(timer->handleTimeout()<0)
							{
							cdevEventHandler *temp = timer->getNext();
							removeHandler(timer);
							timer = temp;
							}
						else 
							{
							timer->resetTimer();
							timer = timer->getNext();
							}
						currTime.setTime();
						}
					else timer = timer->getNext();
					}
				}
			}

		currTime.setTime();
		period = period-(currTime-startTime);
		} while(!finished && period>cdevTime(0,0));

	return result;
	}
