#include <ctype.h>
#include <cdevDirectory.h>
#include <cdevClientRequestObject.h>
#include <cdevClock.h>
#include <cdevTranObj.h>

// *****************************************************************************
// * cdevClientRequestObject::cdevClientRequestObject :
// *	This constructor initializes the internals of a device/message
// *	pair associated with a cdevClientService based service.
// *
// *	Returns nothing.
// *
// *	Remember it is the responsibility of the requestObject to ensure
// *	that the message is not laden with extraneous spaces. 
// *****************************************************************************
cdevClientRequestObject::cdevClientRequestObject ( char * device, char * msg, cdevSystem & system)
	: cdevRequestObject(device, msg, system), 
	  syncCallback(defaultCallback, (void *)&sendStatus),
	  handler(NULL), contextID(-1)
	{
	char message[256];
	char * ptr;
	
	*server     = 0;
	*DDL_server = 0;
	
	// *********************************************************************
	// * Remove any extra spaces from the message.
	// * Copy user supplied msg to a message buffer to allow
	// * modification in this routine since the user supplied
	// * msg could be a constant string
	// *********************************************************************
	strncpy (message, msg, sizeof (message) - 1);
	message[255] = 0;

	for(ptr=message; *ptr!=0; ptr++)
		{
		if(isspace(*ptr))
			{
			char * nextChar;
			
			for(nextChar=ptr+1; isspace(*nextChar) && *nextChar!=0; nextChar++);
			if(nextChar!=ptr+1) strcpy(ptr+1, nextChar);
			*ptr=' ';
			}
		}

	// *********************************************************************
	// * Remove all trailing spaces from the message.
	// *********************************************************************
	for(ptr = message+(strlen(message)-1); ptr>=message && isspace(*ptr); ptr--)
		{
		*ptr=0;
		}
	// *********************************************************************
	// * Determine the commandCode from the message string.
	// *********************************************************************
	if(!strncmp(message, "get ", 4))              commandCode = GET_COMMAND;
	else if(!strncmp(message, "set ", 4))         commandCode = SET_COMMAND;
	else if(!strncmp(message, "monitorOn ", 10))  commandCode = MONITOR_ON_COMMAND;
	else if(!strncmp(message, "monitorOff ", 11)) commandCode = MONITOR_OFF_COMMAND;
	else commandCode = OTHER_COMMAND;
	
	// *********************************************************************
	// * Determine the messageCode from the message string.
	// *********************************************************************
	if(!strcmp(message, "get servers"))         messageCode = GET_SERVERS_MESSAGE;
	else if(!strcmp(message, "get default"))    messageCode = GET_DEFAULT_MESSAGE;
	else if(!strcmp(message, "set default"))    messageCode = SET_DEFAULT_MESSAGE;
	else if(!strcmp(message, "disconnect"))     messageCode = DISCONNECT_MESSAGE;
	else if(!strcmp(message, "get ClientInfo")) messageCode = GET_CLIENTINFO_MESSAGE;
	else if(!strcmp(message, "get ServerInfo")) messageCode = GET_SERVERINFO_MESSAGE;
	else messageCode = OTHER_MESSAGE;
	
	// *********************************************************************
	// * Attempt to read the server name from the DDL file.
	// *********************************************************************
	cdevData output;

  	sprintf (DDL_server, "resolveServiceData %s %s", device, message);
  	if((system.nameServer()).send(DDL_server, NULL, &output)==CDEV_SUCCESS)
  		{
		*DDL_server = 0;
		output.get ("server", DDL_server, 256);
		}
	else *DDL_server = 0;
	} 

			     
// *****************************************************************************
// * cdevClientRequestObject::~cdevClientRequestObject:
// *	This is the destructor for the object.  It will free any memory that it
// *	has allocated and remove itself from its associated ServerHandler
// *	callback.
// *****************************************************************************
cdevClientRequestObject::~cdevClientRequestObject ( void )
	{
	if(handler!=NULL)    handler->unregisterServerCallback(this);
	}
	

// *****************************************************************************
// * cdevClientRequestObject::getState :
// *	Returns the connection state of the cdevClientRequestObject.
// *****************************************************************************
int cdevClientRequestObject::getState ( void )
	{
	ServerHandler * Handler = NULL;
	return (getServerHandler(&Handler)==CDEV_SUCCESS)?CDEV_STATE_NOTCONNECTED:CDEV_STATE_CONNECTED;
	}
	


// *****************************************************************************
// * cdevClientRequestObject::className :
// *	Obtains the name of this class as a string.
// *****************************************************************************
const char * cdevClientRequestObject::className ( void ) const 
	{ 
	return "cdevClientRequestObject";
	}


// *****************************************************************************
// * cdevClientRequestObject::setContext :
// *	This method is called to set the context of the request object. The 
// *	first step in this process is to determine if the caller has specified
// *	a server in the context.  If so, the server name should be set to that
// *	value.	Next call the setContext method of the underlying
// *	cdevRequestObject...
// *****************************************************************************
int cdevClientRequestObject::setContext (cdevData& cxt)
	{
	cdevClientService * svc          = (cdevClientService *)service_;
	int                 result       = CDEV_SUCCESS;
	int                 newContextID = 0;
	
	if((newContextID = svc->contexts.insert(cxt))!=contextID)
		{
		ServerHandler * Handler = NULL;
		
		if(handler!=NULL) handler->unregisterServerCallback(this);

		contextID = newContextID;
		*server   = 0;
		handler   = NULL;

		cxt.get         ("server", server, 256);
		getServerHandler(&Handler);
		
		result = cdevRequestObject::setContext(cxt);
		}
	return result;
	}


// *****************************************************************************
// * cdevClientRequestObject::sendNoBlock :
// * 	This function allows the caller to submit an asynchronous message to the
// *	server for processing.
// *****************************************************************************	
int cdevClientRequestObject::sendNoBlock (cdevData & in, cdevData & out) 
	{ return sendNoBlock(&in, &out); }
int cdevClientRequestObject::sendNoBlock (cdevData * in, cdevData & out) 
	{ return sendNoBlock(in, &out); }
int cdevClientRequestObject::sendNoBlock (cdevData & in, cdevData * out) 
	{ return sendNoBlock(&in, out); }
int cdevClientRequestObject::sendNoBlock (cdevData * in, cdevData * out) 
	{
	cdevClientService * svc     = (cdevClientService *)service_;
	cdevTranObj       * xobj    = new cdevTranObj(&system_, this, out, &svc->syncCallback);
	ServerHandler     * Handler = NULL;
	int                 status  = CDEV_SUCCESS;
	
	xobj->disableDeleteCbk();

	if((status=getServerHandler(&Handler))==CDEV_SUCCESS)
		{	
		status = svc->enqueue(Handler, in, *xobj);
		}
	else delete xobj;
	return status;
	}


// *****************************************************************************
// * cdevClientRequestObject::sendCallback :
// * 	This function allows the caller to submit an asynchronous message to the
// *	server for processing.
// *****************************************************************************	
int cdevClientRequestObject::sendCallback (cdevData & in, cdevCallback & callback) 
	{ return sendCallback(&in, callback); }
int cdevClientRequestObject::sendCallback (cdevData * in, cdevCallback & callback) 
	{
	cdevCallback      * cb      = new cdevCallback(callback);
	cdevClientService * svc     = (cdevClientService *)service_;
	cdevTranObj       * xobj    = new cdevTranObj(&system_, this, NULL, cb);
	ServerHandler     * Handler = NULL;
	int                 status  = CDEV_SUCCESS;

	xobj->enableDeleteCbk();

	if((status=getServerHandler(&Handler))==CDEV_SUCCESS)
		{	
		status = svc->enqueue(Handler, in, *xobj);
		}
	else delete xobj;
	return status;
	}


// *****************************************************************************
// * cdevClientRequestObject::send : 
// *	The send interface is used to provide synchronous I/O with the service.
// *
// *	Returns CDEV_SUCCESS on success or CDEV_ERROR on error.
// *****************************************************************************
int cdevClientRequestObject::send ( cdevData & in, cdevData & out ) 
	{ return send(&in, &out); }
int cdevClientRequestObject::send ( cdevData * in, cdevData & out ) 
	{ return send(in, &out);  }
int cdevClientRequestObject::send ( cdevData & in, cdevData * out ) 
	{ return send(&in, out);  }
int cdevClientRequestObject::send ( cdevData * in, cdevData * out )
	{             
	int                 status  = CDEV_SUCCESS;
	cdevClientService * svc     = (cdevClientService *)service_;
	cdevTranObj       * xobj    = new cdevTranObj(&system_, this, out, &syncCallback);
	ServerHandler     * Handler;

	sendStatus.completionCode   = 0;
	sendStatus.finished         = 0;
	xobj->disableDeleteCbk();

	if((status=getServerHandler(&Handler))==CDEV_SUCCESS)
		{
		if((status = svc->enqueue(Handler, in, *xobj))==CDEV_SUCCESS)
			{	
			// *****************************************************
			// * I used to wait for a response here only if the outbound 
			// * cdevData object was non-null.  However, that provided 
			// * unexpected behavior to the client.  Now I wait whether 
			// * output data is expected or not.
			// *****************************************************
			cdevTimeValue t(waitPeriod());
			cdevClock timer;
			timer.schedule(NULL,t);

			// *****************************************************
			// * WAITING WITH system_.pend():
			// * 	Previously I was using system_.pend() to process 
			// *	events while waiting for the service to respond.  
			// *	This resulted in a lock-up when the connection 
			// *	could not be established or if the connection 
			// *	collapsed while in use.
			// *
			// * WAITING WITH system_.poll():
			// *	When in a heavy inbound traffic situation, the 
			// *	calls from other services will trample all over 
			// *	the inbound data coming from the server.  
			// *	This results in unreliable delivery and processing
			// *	of messages from the server.
			// * 
			// * WAITING WITH service_.poll():
			// *	So far so good.
			// *****************************************************

			while(!sendStatus.finished && !timer.expired()) service_->poll();
				if   (!sendStatus.finished)
				{
				svc->cancel(*xobj);			
				status = CDEV_ERROR;
				system_.reportError(
					CDEV_SEVERITY_ERROR, 
					"cdevRequestObject", 
					this,
					"Server failed to respond after %.1f seconds",
					(float)waitPeriod());
				}
			else 
				{
				status = sendStatus.completionCode;
				}
			}
		}
	else delete xobj;

	return status;
	}


// *****************************************************************************
// * cdevClientRequestObject::defaultCallback :
// *	This is the callback function that is used by the object to 
// *	receive callbacks for send operations.
// *****************************************************************************
void cdevClientRequestObject::defaultCallback (int status, void * user, cdevRequestObject &, cdevData &)
	{
	SendStatus * result = (SendStatus *)user;

	if(result) 
		{
		result->completionCode = status;
		result->finished       = 1;
		}
	}


// *****************************************************************************
// * cdevClientRequestObject::executeServerHandlerCallback :
// *	The requestObject registers itself with the ServerHandler when it
// *	attaches to it...  If the Serverhandler is destroyed, it will
// *	call this method on all of the registered request objects in order
// *	to notify them that it is going away.  This allows the request
// *	object to clear the pointer to the ServerHandler.
// *****************************************************************************
void cdevClientRequestObject::executeServerHandlerCallback (ServerHandler * Handler)
	{
	if(Handler==handler)
		{
		/*
		 * system_.reportError(CDEV_SEVERITY_INFO, "cdevRequestObject", 
		 *	            this, "Connection to %s broken for %s:%s", 
		 *	            Handler->get_server(), device().name(), message());
		 */
		handler=NULL;
		}
	}


// *****************************************************************************
// * cdevClientRequestObject::getServerHandler : 
// * This method is used to obtain the ServerHandler for this request
// * object...
// *
// * The following rules will be followed:
// *
// *	1)  If a server has been specified in the context, then that
// *	    server will be used.
// *
// *	    - otherwise -
// *	
// *	2)  If a server has been specified in the service data of the
// *	    DDL file, then that server will be used.
// *
// *	    - otherwise -
// *	
// *	3)  The default server as specified at the cdevClientService will
// *	    be used.
// ***************************************************************************** 
int cdevClientRequestObject::getServerHandler (ServerHandler ** Handler)
	{
	cdevClientService * svc     = (cdevClientService *)service_;
	int                 result  = CDEV_SUCCESS;
	
	*Handler = NULL;
	
	if (*server==0 && *DDL_server!=0) strcpy(server, DDL_server);
	if (*server!=0 && handler==NULL)
		{
		if((*Handler = svc->connect(server))==NULL) result = CDEV_ERROR;
		else
			{
			handler = *Handler;
			handler->registerServerCallback(this);
			}
		}
	else *Handler = handler;
			
	return result;
	}

// *****************************************************************************
// * cdevClientRequestObject::isRequestRestartable :
// * 	This method allows the caller to determine if the request object 
// * 	is restartable. If a server goes down and then a new server comes
// * 	up in its place, this method will be called for each request object
// * 	that has an outstanding request that has not been serviced.  
// * 	If the isRequestRestartable method returns 1, the request will be 
// * 	sent to the new server - otherwise, the request will be terminated.
// *****************************************************************************
int cdevClientRequestObject::isRequestRestartable ( void )
	{
	int result = 0;

	switch(getCommandCode())
		{
		case GET_COMMAND:
		case MONITOR_ON_COMMAND:
		result = 1;
		break;
		
		case SET_COMMAND:
		case MONITOR_OFF_COMMAND:
		default:
		result = 0;
		break;
		}

	return result;
	}

// *****************************************************************************
// * cdevClientRequestObject::getContextID :
// *	This method will retrieve the identifier of the context that is 
// *	currently in use by this object.
// *****************************************************************************
int cdevClientRequestObject::getContextID ( void ) 
	{
	return contextID;
	}


// *****************************************************************************
// * getCommandCode : 
// *	This method will return the current value of the commandCode variable.  
// *	This variable is used to identify the VERB that is utilized by this 
// *	cdevClientRequestObject.  The default set of verbs are "get", "set", 
// *	"monitorOn", and "monitorOff".
// *****************************************************************************
int cdevClientRequestObject::getCommandCode ( void ) { return commandCode; }
	
// *****************************************************************************
// * getMessageCode : 
// *	This method will return the current value of the messageCode variable. 
// *	This variable is used to identify the message that is utilized by this 
// *	cdevClientRequestObject.  The default set of supported messages are 
// *	"get servers", "get default", "set default", and "disconnect".
// *****************************************************************************
int cdevClientRequestObject::getMessageCode ( void ) { return messageCode; }
	
