// **********************************************************************
//
// Copyright (c) 1999
// Object Oriented Concepts, Inc.
// Billerica, MA, USA
//
// All Rights Reserved
//
// **********************************************************************

#include <OB/Basic.h>
#include <OB/Except.h>
#include <OB/Reactor.h>
#include <OB/Timer.h>
#include <OB/Net.h>

#include <SelectReactor.h>

OBReactor* OBReactor::instance_ = 0;

class OBReactorDestroyer
{
public:

    ~OBReactorDestroyer()
    {
	delete OBReactor::instance_;
	OBReactor::instance_ = 0;
    }
};

static OBReactorDestroyer destroyer;

// ----------------------------------------------------------------------
// OBReactor public member implementation
// ----------------------------------------------------------------------

OBReactor*
OBReactor::instance()
{
    if(!instance_)
	return OBSelectReactor::instance(); // OBSelectReactor is default

    return instance_;
}

// ----------------------------------------------------------------------
// OBSelectReactor private and protected member implementation
// ----------------------------------------------------------------------

void
OBSelectReactor::reapHandlerInfoList()
{
    if(handlerInfoListUsageCount_ > 0)
	return;

    HandlerInfo** p = &handlerInfoList_;
    while(*p)
    {
	HandlerInfo* info = *p;

	if(info -> handler == 0)
	{
	    *p = info -> next;
	    delete info;
	}
	else
	{
	    p = &(info -> next);
	}
    }
}

int
OBSelectReactor::setFdSets(fd_set& readFdSet, fd_set& writeFdSet) const
{
    FD_ZERO(&readFdSet);
    FD_ZERO(&writeFdSet);
    int nfds = 0;

    HandlerInfo* info = handlerInfoList_;
    while(info)
    {
	if(info -> handler)
	{
	    OBMask mask = info -> mask;
	    OBHandle handle = info -> handle;
	    
	    if(mask && handle >= nfds)
		nfds = handle + 1;
	    
	    if(mask & OBEventRead)
		FD_SET(handle, &readFdSet);
	    
	    if(mask & OBEventWrite)
		FD_SET(handle, &writeFdSet);
	}

	info = info -> next;
    }

    return nfds;
}

void
OBSelectReactor::evalFdSets(fd_set& readFdSet, fd_set& writeFdSet)
{
    handlerInfoListUsageCount_++;
    HandlerInfo* info = handlerInfoList_;
    
    while(info)
    {
	if(info -> handler)
	{
	    OBMask mask = 0;
	    OBHandle handle = info -> handle;
	    
	    if(FD_ISSET(handle, &readFdSet))
		mask |= OBEventRead;
	    
	    if(FD_ISSET(handle, &writeFdSet))
		mask |= OBEventWrite;
	    
	    if(mask)
	    {
		try
		{
		    info -> handler -> handleEvent(mask);
		}
		catch(...)
		{
		    handlerInfoListUsageCount_--;
		    reapHandlerInfoList();
		    throw;
		}
	    }
	}
	
	info = info -> next;
    }

    handlerInfoListUsageCount_--;
    reapHandlerInfoList();
}

// ----------------------------------------------------------------------
// OBSelectReactor constructor and destructor
// ----------------------------------------------------------------------

OBSelectReactor::OBSelectReactor() 
    : stop_(false), handlerInfoList_(0), handlerInfoListUsageCount_(0)
{
}

OBSelectReactor::~OBSelectReactor()
{
    //
    // Delete all handler infos
    //
    assert(handlerInfoListUsageCount_ == 0);
    while(handlerInfoList_)
    {
	HandlerInfo* next = handlerInfoList_ -> next;
	delete handlerInfoList_;
	handlerInfoList_ = next;
    }
}

// ----------------------------------------------------------------------
// OBSelectReactor public member implementation
// ----------------------------------------------------------------------

OBReactor*
OBSelectReactor::instance()
{
    if(!instance_)
	instance_ = new OBSelectReactor;

    return instance_;
}

void
OBSelectReactor::registerHandler(OBEventHandler* handler,
				 OBMask mask, OBHandle handle)
{
    assert(handler);

    HandlerInfo* info = handlerInfoList_;
    while(info)
    {
	if(info -> handler == handler)
	{
	    //
	    // Handler already in list, update mask and handle
	    //
	    info -> mask = mask;
	    info -> handle = handle;
	    return;
	}
	
	info = info -> next;
    }

    //
    // Add new handler info
    //
    info = new HandlerInfo;
    info -> handler = handler;
    info -> mask = mask;
    info -> handle = handle;
    info -> next = handlerInfoList_;
    handlerInfoList_ = info;
}

void
OBSelectReactor::unregisterHandler(OBEventHandler* handler)
{
    assert(handler);

    HandlerInfo* info = handlerInfoList_;
    while(info)
    {
	if(info -> handler == handler)
	{
	    //
	    // Handler info found, invalidate the handler
	    //
	    info -> handler = 0;
	    reapHandlerInfoList();
	    return;
	}
	
	info = info -> next;
    }

    assert(false);
}

void
OBSelectReactor::dispatch()
{
    stop_ = false;

    OBTimerList* timerList = OBTimerList::instance();

    while(!stop_ && handlerInfoList_)
    {
	//
	// Get the current time
	//
	timeval now = OBTimerList::timeNow();

	// 
	// Check for expired timers
	//
	if(!timerList -> isEmpty())
	    timerList -> expire(now);
	
	//
	// The filedescriptor sets and the nfds parameter for select()
	//
	fd_set readFdSet;
	fd_set writeFdSet;
	FD_ZERO(&readFdSet);
	FD_ZERO(&writeFdSet);
	int nfds = setFdSets(readFdSet, writeFdSet);

	//
	// Return value for select()
	//
	int result;
	    
	//
	// Is there a timer left?
	//
	if(!timerList -> isEmpty())
	{
	    //
	    // Calculate timer list timeout value
	    //
	    timeval tv =
		OBTimerList::timeSub(timerList -> first() -> timeout(), now);

	    //
	    // Do select
	    //
	    result = select(nfds, &readFdSet, &writeFdSet, 0, &tv);
	    if(result == 0)
		continue; // Timer list timeout
	}
	else
	{
	    //
	    // Do select
	    //
	    result = select(nfds, &readFdSet, &writeFdSet, 0, 0);
	    assert(result != 0);
	}

	//
	// Select error?
	//
	if(result == -1)
	{
            if(OBIsInterrupted())
                continue;

            throw CORBA_COMM_FAILURE(OBLastError(), OBMinorSelect,
				     CORBA_COMPLETED_NO);
	}
	
	//
	// Evaluate file descriptor sets
	//
	evalFdSets(readFdSet, writeFdSet);
    }
}

bool
OBSelectReactor::dispatchOneEvent(CORBA_Long timeout)
{
    OBTimerList* timerList = OBTimerList::instance();

    while(!stop_ && handlerInfoList_)
    {
	//
	// Get the current time
	//
	timeval now = OBTimerList::timeNow();

	// 
	// Check for expired timers
	//
	if(!timerList -> isEmpty())
	    if(timerList -> expireOneTimer(now))
		return true; // A timer event is treated just like a
                             // regular event
	
	//
	// The filedescriptor sets and the nfds parameter for select()
	//
	fd_set readFdSet;
	fd_set writeFdSet;
	FD_ZERO(&readFdSet);
	FD_ZERO(&writeFdSet);
	int nfds = setFdSets(readFdSet, writeFdSet);
	    
	//
	// Return value for select()
	//
	int result;

	//
	// Is there a timer left?
	//
	if(!timerList -> isEmpty())
	{
	    //
	    // Calculate timer list timeout value
	    //
	    timeval tv =
		OBTimerList::timeSub(timerList -> first() -> timeout(), now);

	    if(timeout >= 0)
	    {
		//
		// Calculate regular timeout value
		//
		struct timeval rtv;
		rtv.tv_sec = timeout / 1000;
		rtv.tv_usec = (timeout - rtv.tv_sec * 1000) * 1000;

		if(OBTimerList::timeCmp(rtv, tv) < 0)
		{
		    //
		    // Do select
		    //
		    result = select(nfds, &readFdSet, &writeFdSet, 0, &rtv);
		    if(result == 0)
			return false; // Regular timeout
		}
		else
		{
		    //
		    // Do select
		    //
		    result = select(nfds, &readFdSet, &writeFdSet, 0, &tv);
		    if(result == 0)
			continue; // Timer list timeout
		}
	    }
	    else
	    {
		//
		// Do select
		//
		result = select(nfds, &readFdSet, &writeFdSet, 0, &tv);
		if(result == 0)
		    continue; // Timer list timeout
	    }
	}
	else
	{
	    if(timeout >= 0)
	    {
		//
		// Calculate regular timeout value
		//
		struct timeval rtv;
		rtv.tv_sec = timeout / 1000;
		rtv.tv_usec = (timeout - rtv.tv_sec * 1000) * 1000;

		//
		// Do select
		//
		result = select(nfds, &readFdSet, &writeFdSet, 0, &rtv);
		if(result == 0)
		    return false; // Regular timeout
	    }
	    else
	    {
		//
		// Do select
		//
		result = select(nfds, &readFdSet, &writeFdSet, 0, 0);
		assert(result != 0);
	    }
	}
	
	//
	// Select error?
	//
	if(result == -1)
	{
            if(OBIsInterrupted())
                continue;

            throw CORBA_COMM_FAILURE(OBLastError(), OBMinorSelect,
                                     CORBA_COMPLETED_NO);
	}

	//
	// Evaluate file descriptor sets
	//
	evalFdSets(readFdSet, writeFdSet);
	return true;
    }

    return false;
}
