#include <cdevDevice.h>
#include <cdevRequestObject.h>
#include <cdevCollection.h>
#include <cdevCollectionRequest.h>

#include "cdevGateway.h"

GatewayServer *gatewayServer;

// *****************************************************************************
// * GatewayServer::GatewayServer :
// *	This is the constructor for the gateway server.  It initializes the
// *	underlying cdevServer object and sets the nextCollectionInt variable
// *	to zero.
// *
// *	The constructor will also extract all of the file descriptors from the
// *	cdevSystem object and will create a GatewayHandler for each of them...
// *	The fdChangedCallback method will be register with the cdevSystem to
// *	ensure that these GatewayHandlers will be regularly updated/removed 
// *	as they become connected and disconnected.
// *****************************************************************************
GatewayServer::GatewayServer ( char * domain, char * server, unsigned short port, double pulse )
	: cdevServer(domain, server, port, pulse), nextCollectionInt(0)
	{
	int   fdBufSize = 256;
	int * fdBuf     = NULL;
	
	// *********************************************************************
	// * Get the list of all file descriptors that are in use by the 
	// * cdevSystem class.  
	// *********************************************************************
	do 	{
		fdBuf  = new int[fdBufSize];
		if(cdevSystem::defaultSystem().getFd(fdBuf, fdBufSize)!=CDEV_SUCCESS)
			{
			delete fdBuf;
			fdBufSize *= 2;
			fdBuf      = NULL;
			}
		} while (fdBuf==NULL);
		
	// *********************************************************************
	// * Create a GatewayHandler to manage each of the file descriptors
	// * that was provided by the cdevSystem class, and register them with
	// * the Reactor.
	// *********************************************************************
	for(int i=0; i<fdBufSize; i++) 
		{
		GatewayHandler * hdlr;
		hdlr = new GatewayHandler(cdevSystem::defaultSystem(), fdBuf[i]);
		Reactor.registerHandler(hdlr, cdevEventHandler::READ_MASK);
		}

	// *********************************************************************
	// * Install the static fdChangedCallback method to be called each time
	// * one of the cdevSystem's file descriptors is added or deleted.
	// *********************************************************************
	cdevSystem::defaultSystem().addFdChangedCallback(fdChangedCallback, this);

	// *********************************************************************
	// * Delete the temporary file descriptor buffer before returning.
	// *********************************************************************
	delete fdBuf;
	}

// *****************************************************************************
// * GatewayServer::~GatewayServer :
// *	This is the virtual destructor for the cdevGateway class.  It does 
// * 	nothing except serve as a placeholder to ensure that the delete 
// *	mechanism calls the most senior object.
// *****************************************************************************
GatewayServer::~GatewayServer ( void )
	{
	}
	

// *****************************************************************************
// * GatewayServer::fdChangedCallback :
// *	This method will be called by CDEV anytime one of the file descriptors
// *	associated with the cdevSystem is added or deleted.  This method may
// *	then add or delete the file descriptor from the Reactor object.
// *****************************************************************************
void GatewayServer::fdChangedCallback (int fd, int opened, void * arg )
	{
	GatewayServer * server = (GatewayServer *)arg;

	if(opened)
		{
		GatewayHandler * hdlr;
		hdlr = new GatewayHandler(cdevSystem::defaultSystem(), fd);
		server->Reactor.registerHandler(hdlr, cdevEventHandler::READ_MASK);	
		}
	else server->Reactor.removeHandler(fd);
	}


// *****************************************************************************
// * GatewayServer::handleTimeout  :
// *	This is the handleTimeout method.  It is called by the cdevReactor
// *	periodically to make sure that all events are being handled.
// *****************************************************************************
int GatewayServer::handleTimeout  ( void )
	{
	cdevSystem::defaultSystem().pend(0.001);
	return 0;
	}

// *****************************************************************************
// * GatewayServer::processMessages :
// *	This is the processMessages method.  It receives the inbound requests
// *	from the clients and then calls CDEV to process the requests.
// *****************************************************************************
void GatewayServer::processMessages ( void )
{
int           data_handled = 0;
cdevMessage * request;
while(dequeue(request)==0)
	{
	if(!strcmp(request->getMessage(), "register") ||
	   !strcmp(request->getMessage(), "unregister"))
		{
		enqueue(request);
		delete (request);
		}
	else	
		{
		int                 result      (CDEV_SUCCESS);
		cdevCallback        cb          (gatewayCallback, request);
		int                 deviceCount (request->getDeviceCount());
		char             ** deviceList = request->getDeviceList();
		char              * message    = request->getMessage();
		cdevData          * context    = request->getContext();
		cdevData          * dataOut    = request->getData();
		cdevRequestObject * req        = NULL;

		if(deviceCount<=0 || deviceList==NULL || message==NULL)
			{
			result = CDEV_ERROR;
			}
		else
			{
			if(deviceCount==1)
				{
				req = cdevRequestObject::attachPtr(deviceList[0], message);
				}
			else	{
				char name[32];
				sprintf(name, "~Collection%i", nextCollectionInt++);
				cdevCollection *col = cdevCollection::attachPtr(name);
				col->add(deviceCount, deviceList);
				req = col->getRequestObject(message);
				}
			if(context) req->setContext(*context);
			result = req->sendCallback(dataOut, cb);
			if(result==CDEV_SUCCESS) data_handled++;
			}

		if(result != CDEV_SUCCESS)
			{
			request->setCompletionCode(result);
			enqueue(request);
			delete request;
			}
		}

	// *********************************************************************
	// * Note:  The developer MUST do a pend at this point in order to 
	// * have the file descriptors updated...  However, the wait time can be
	// * very, very short.
	// *********************************************************************
	if(data_handled) cdevSystem::defaultSystem().pend(0.000001);
	}
}
	
// *****************************************************************************
// * GatewayServer::gatewayCallback :
// *	This is the callback function that is called each time a request has
// *	been processed by CDEV.  The cdevMessage that was used to make the
// *	request is stored in the userarg parameter and this method will pass
// *	the result to the cdevGateway for processing.
// *
// *	When the isTransactionDone flag is set to TRUE, this method will
// *	delete the cdevMessage object.
// *****************************************************************************
void GatewayServer::gatewayCallback ( int status, void *userarg, cdevRequestObject &, cdevData &data)
	{
	cdevMessage * message = (cdevMessage *)userarg;
		
	
	message->setCompletionCode(status);
	message->setData(&data, 1);
	message->setOperationCode(cdevCallback::isTransactionDone()?1:0);
	gatewayServer->enqueue(message);
	message->setData(NULL);

	if(cdevCallback::isTransactionDone()) delete message;
	}


int main(int argc, char ** argv)
	{
	char serverName[255];
	int  serverPort = 9573;
	int  mismatch    = 0;
	
	strcpy(serverName, "Gateway1");
	
	for(int i=1; i<argc && !mismatch; i++)
		{
		char * ptr;
		switch(*argv[i])
			{
			case 's':
			if((ptr = strchr(argv[i], '='))!=NULL && *(++ptr))
				{
				strcpy(serverName, ptr);
				}
			else mismatch = 1;
			break;
			
			case 'p':
			if((ptr = strchr(argv[i], '='))!=NULL && *(++ptr))
				{
				if((serverPort = atoi(ptr))==0)
					{
					mismatch = 1;
					}
				}
			else mismatch = 1;
			break;
			
			default:
			mismatch = 1;
			break;
			}
		}
	
	if(mismatch)
		{
		fprintf(stderr, "%s <s=server name> <p=server port>\\n", argv[0]);
		}
	else
		{
		gatewayServer = new GatewayServer("GATEWAY", serverName, serverPort, 60);
		gatewayServer->runServer();
		}
	return mismatch?-1:0;
	}
