/* -----------------------------------------------------------------------------
 * Copyright (c) 1994,1995 Southeastern Universities Research Association,
 *                         Continuous Electron Beam Accelerator Facility
 *
 * This software was developed under a United States Government license
 * described in the NOTICE file included as part of this distribution.
 *
 * -----------------------------------------------------------------------------
 *
 * cdevGrpCollectionRequest.h : This class is used to collect the results that
 *                              are generated by numerous service-specific
 *                              cdevCollectionRequest objects.  When all of the
 *                              component collections have responded to this 
 *                              object, the data will be consolidated and
 *                              returned to the caller.
 *
 * Author:  Walt Akers
 *
 * Revision History:
 *   cdevGrpCollectionRequest.cc,v
// Revision 1.2  1997/04/02  17:54:03  akers
// Ongoing Development of CDEV 1.6
//
// Revision 1.1  1996/11/12  20:32:32  akers
// New collection device source code
//
 * -----------------------------------------------------------------------------
 */
#include <cdevClock.h>
#include <cdevGrpCollectionRequest.h>
#include <grpCallbackCollector.h>

int                                  cdevGrpCollectionRequest::sendCheckSum = 0;
cdevGrpCollectionRequest::SendStatus cdevGrpCollectionRequest::sendStatus;

// *****************************************************************************
// * cdevGrpCollectionRequest::cdevGrpCollectionRequest :
// *	This is the constructor for the cdevGrpCollectionRequest object.  It 
// *	will initialize all of the internals for the class.
// *****************************************************************************
cdevGrpCollectionRequest::cdevGrpCollectionRequest 
	(char **devices, int nDevices, char *msg, cdevSystem& sys)
	: cdevCollectionRequest(devices, nDevices, msg, sys),
	  collections(NULL),
	  nCollections(1),
	  requestOrder(NULL),
	  nRequests(0),
	  format()
	{
	// *********************************************************************
	// * This is a simple single linked-list that will be used to hold the
	// * individual services as they are loaded.
	// *********************************************************************
	class slist
		{
		public:
		cdevService * service;
		slist       * next;
		char        * value;
		int           index;
		int           nItems;
		
		slist(char * Value="\0", int Index=0)
			: service(NULL), next(NULL), value(strdup(Value)), 
			  index(Index), nItems(0) {}
		~slist( void )
			{
			if(next)  delete next;
			if(value) delete value;
			}
		} serviceList, *slistPtr;
		
	// *********************************************************************
	// * Obtain a pointer to the cdevDirectory device.  This will be used
	// * to lookup the name of the service that is associated with each 
	// * device/message combination.
	// *********************************************************************
	cdevDevice *dir = cdevDevice::attachPtr("cdevDirectory");

	// *********************************************************************
	// * Allocate the array of integers that will be used to store the 
	// * service number associated with each of the device/message 
	// * combinations.
	// *********************************************************************
	requestOrder    = new int[nDevices];
	
	for(nRequests=0; nRequests<nDevices; nRequests++)
		{
		cdevData input, output;
                                
		// ************************************************************
		// * Insert the name of the device and message to be resolved.
		// ************************************************************
		input.insert("device", devices[nRequests]);
		input.insert("message", msg);
                              
		// ************************************************************
		// Submit the request to the cdevDirectory object using the
		// send command, and output the result if successful.
		// ************************************************************
		if(dir->send("service", input, output)==CDEV_SUCCESS)
			{
			int    found;
			char * ptr;
			output.find("value", (void * &)ptr);
			if(ptr && *ptr)
				{
				slistPtr = &serviceList;
				while(!(found=!strcmp(slistPtr->value, ptr)) && 
				      slistPtr->next!=NULL)
					{
					slistPtr = slistPtr->next;
					}
				// *********************************************
				// * If the specified service was not found, 
				// * then create a new list entry for it.
				// * Load the shared-library here if necessary.
				// *********************************************
				if(!found)
					{
					slistPtr->next = new slist(ptr, slistPtr->index+1);
					slistPtr       = slistPtr->next;
					if(!system_.serviceCreated(ptr)) 
						{
						system_.loadService(ptr);
						}
					slistPtr->service = system_.service(ptr);
					nCollections++;
					}
				requestOrder[nRequests] = slistPtr->index;
				slistPtr->nItems++;
				}
			// *****************************************************
			// * If the required service is not specified, then
			// * add the item to the "0" list.
			// *****************************************************
			else	{
				requestOrder[nRequests] = 0;
				serviceList.nItems++;
				}
			}
		// *************************************************************
		// * If the service message could not be processed, then 
		// * add the item to the "0" list.
		// *************************************************************
		else	{
			requestOrder[nRequests] = 0;
			serviceList.nItems++;
			}
		}
	
	// *********************************************************************
	// * Allocate a block of cdevCollectionRequest pointers sufficient to
	// * hold all of the cdevCollectionRequest objects from the different
	// * servers.
	// *********************************************************************
	collections    = new cdevCollectionRequest *[nCollections];	
	for(int k=0; k<nCollections; k++) collections[k] = NULL;

	// *********************************************************************
	// * Walk through the list and obtain the cdevCollectionRequest pointer
	// * that is associated with each service.
	// *********************************************************************
	for(slistPtr = serviceList.next; slistPtr!=NULL; slistPtr=slistPtr->next)
		{
		if(slistPtr->service!=NULL)
			{
			int i, j;
			// *****************************************************
			// * Allocate a block of character pointers to hold the
			// * names associated with this service.
			// *****************************************************
			char ** names = new char *[slistPtr->nItems];
		
			// *****************************************************
			// * Copy the device name pointer for every device/msg
			// * combination that is associated with this service to
			// * the names array.
			// *****************************************************
			for(i=0, j=0; i<nRequests; i++)
				{
				if(requestOrder[i] == slistPtr->index)
					{
					names[j++] = devices[i];
					}
				}
			
			// *****************************************************
			// * Create/obtain a cdevCollectionRequest for the
			// * group of device names that has been transfered to 
			// * the list.
			// *****************************************************
			slistPtr->service->getCollectionRequest
				(names, j, msg, collections[slistPtr->index]);

			// *****************************************************
			// * Delete the names array after it has been used.
			// *****************************************************
			delete names;
			}
		}
	}
	

// *****************************************************************************
// * cdevGrpCollectionRequest::~cdevGrpCollectionRequest :
// *	This is the destructor for the cdevGrpCollectionRequest object.
// *****************************************************************************
cdevGrpCollectionRequest::~cdevGrpCollectionRequest ( void ) 
	{
	for(int i=0; i<nCollections; i++)
		{
		if(collections[i]!=NULL) delete collections[i];
		}
	delete collections;
	delete requestOrder;
	}
	

// *****************************************************************************
// * setContext:
// *	This methods executes the setContext method on each of the underlying
// *	cdevRequestObjects.
// *****************************************************************************
int cdevGrpCollectionRequest::setContext ( cdevData & ctx )
	{
	for(int i=0; i<nCollections; i++)
		{
		if(collections[i]!=NULL) collections[i]->setContext(ctx);
		}
	return cdevRequestObject::setContext(ctx);
	}

// *****************************************************************************
// * getState:
// *	This method allows the caller to obtain the combined state of the
// *	collection of cdevRequestObjects that reside within this class.  The
// *	most restrictive state will be returned.
// *
// *	Because the state definition variables proceeds from 
// *	CDEV_STATE_CONNECTED(0) to CDEV_STATE_INVALID(2), it can be assumed that
// *	the higher a state result code is the more restrictive it is...  
// *	therefore, the highest state result code that is obtained from the 
// *	cdevRequestObjects will be returned to the caller.
// *****************************************************************************
int cdevGrpCollectionRequest::getState ( void )
	{
	int state;

	if(nCollections==0) state = CDEV_STATE_INVALID;
	else for(int i=0; i<nCollections; i++)
		{
		int tState;
		if(collections[i]==NULL) state=CDEV_STATE_INVALID;
		else if((tState = collections[i]->getState())>state)
			{
			state = tState;
			}
		}
	return state;
	}
	
// *****************************************************************************
// * getAccess:
// *	This method allows the caller to obtain the combined access of the
// *	collection of cdevRequestObjects that reside within this class.  The
// *	most restrictive access code will be returned.
// *
// *	Because the access definition variables proceed from 
// *	CDEV_ACCESS_NONE(0) to CDEV_ACCESS_WRITE(2), it can be assumed that
// *	the lower an access result code is the more restrictive it is...  
// *	therefore, the smallest access result code that is obtained from the 
// *	cdevRequestObjects will be returned to the caller.
// *****************************************************************************
int cdevGrpCollectionRequest::getAccess ( void )
	{
	int accessCode;

	if(nCollections==0) accessCode = CDEV_ACCESS_NONE;
	else for(int i=0; i<nCollections; i++)
		{
		int tAccess;
		if(collections[i]==NULL) accessCode=CDEV_STATE_INVALID;
		else if((tAccess = collections[i]->getAccess())<accessCode)
			{
			accessCode = tAccess;
			}
		}
	return accessCode;
	}
	

// *********************************************************************
// * This is the syncCallback method.  It will be used by the send
// * method in order to detect the completion of the operation.
// *********************************************************************
void cdevGrpCollectionRequest::syncCallback
	(int status, void * arg, cdevRequestObject &, cdevData &data)
	{
	int checkSum = (int)arg;
	if( checkSum == sendCheckSum ) 
		{
		sendStatus.completionCode            = status;
		sendStatus.finished                  = 1;
		if(sendStatus.data) *sendStatus.data = data;
		}
	}
		
		
// *********************************************************************
// * This is the asyncNoBlockCallback method.  It will be used by the 
// * sendNoBlock method in order to detect the completion of the 
// * operation.
// *********************************************************************
void cdevGrpCollectionRequest::asyncNoBlockCallback
	(int, void *arg, cdevRequestObject &, cdevData &data)
	{
	cdevData * userData = (cdevData *)arg;
	if(userData!=NULL) *userData = data;
	}

// *****************************************************************************
// * asyncCallback:
// *	This method is the callback method that is called each time one of the
// *	subordinate cdevRequestObjects completes.  This method is provided with
// *	a pointer to the requests entry from the grpCallbackCollector that is
// *	associated with this requestObject...
// *****************************************************************************
void cdevGrpCollectionRequest::asyncCallback
	(int status, void * arg, cdevRequestObject &, cdevData &data)
	{
	grpCallbackCollector::Request * request   = NULL;
	grpCallbackCollector          * collector = NULL;
	
	if(arg!=NULL)
		{
		request = (grpCallbackCollector::Request *)arg;
		collector = request->parent;
		}
	// *********************************************************************
	// * You'll note that this is a bone-jarring test to ensure that the
	// * data that is provided is valid.
	// *********************************************************************
	if(collector!=NULL && &collector->collections[request->index]==request)
		{
		collector->processCollection(request->index, status, &data);

		// *************************************************************
		// * Having processed this entry, I'll test to determine if all
		// * of the members in this grpCallbackCollector are finished - 
		// * if they are I'll dispatch the callback that was provided
		// * earlier by the user and will delete the collector.
		// *************************************************************
		if(collector->finished())
			{
			(collector->xobj->userCallback_->callbackFunction())
				(CDEV_SUCCESS,
				 collector->xobj->userCallback_->userarg(),
				 *collector->xobj->reqObj_,
				 collector->result);
			delete collector;
			}
		}
	}
	
	
// *****************************************************************************
// * 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 cdevGrpCollectionRequest::send ( cdevData * in, cdevData * out )
	{             
	int status                = CDEV_SUCCESS;
	sendStatus.completionCode = 0;
	sendStatus.finished       = 0;
	sendStatus.data           = out;

	cdevCallback cb(syncCallback, (void *)sendCheckSum);

	if((status = sendCallback(in, cb))==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(5.0);
		cdevClock timer;
		timer.schedule(NULL,t);

		do 	{
			system_.poll();
			} while(!sendStatus.finished && !timer.expired());

		if   (!sendStatus.finished)
			{
			status = CDEV_ERROR;
			system_.reportError(
				CDEV_SEVERITY_ERROR, 
				"cdevCollectionRequest", 
				this,
				"Services failed to respond after 5 seconds");
			}
		else status = sendStatus.completionCode;
		}
	sendCheckSum++;
	return status;
	}


// *****************************************************************************
// * cdevClientRequestObject::sendNoBlock :
// * 	This function allows the caller to submit an asynchronous message to the
// *	server for processing.
// *****************************************************************************	
int cdevGrpCollectionRequest::sendNoBlock (cdevData * in, cdevData * out) 
	{
	cdevCallback cb(asyncNoBlockCallback, (void *)out);
	return sendCallback(in, cb);
	}


// *****************************************************************************
// * sendCallback:
// *	This method is used to submit a asynchronous call to the underlying
// *	cdevRequestObjects.
// *****************************************************************************
int cdevGrpCollectionRequest::sendCallback (cdevData* in, cdevCallback& callback)
	{
	int                   failCount = 0;
	int                   status    = CDEV_SUCCESS;
	cdevTranObj          *xobj      = new cdevTranObj(&system_, this, NULL, new cdevCallback(callback));
	grpCallbackCollector *collector = new grpCallbackCollector
		(nCollections, requestOrder, nRequests, format, *xobj);

	xobj->enableDeleteCbk();
	
	// ********************************************************************
	// * This loop will walk through each of the cdevRequestObjects that 
	// * are in the requests array and will call their sendCallback method.
	// *
	// * The callback that is passed to them will provide a pointer to the
	// * specific entry in the requests array of the grpCallbackCollector
	// * object - this will be used to populate the proper entry in the 
	// * status attribute of the grpCallbackCollector object.
	// *
	// * If the cdevRequestObject is NULL, or if the call fails - a 
	// * cdevError code will be placed in the statusCode tag of the status 
	// * data and failCount will be incremented.  If all of the requests are 
	// * invalid or fail, then an error will be returned and the user 
	// * callback will not be executed.
	// *********************************************************************
	for(int i=0; i<nCollections; i++)
		{
		// *************************************************************
		// * This method is using a local copy of a cdevCallback object.
		// * A well behaved service always makes a copy of the 
		// * cdevCallback object that is provided with a sendCallback
		// * request in order to avoid contention over when the 
		// * cdevCallback object should be deleted.
		// *************************************************************
		cdevCallback cb(asyncCallback, (void *)&(collector->collections[i]));
		if(collections[i]!=NULL) status = collections[i]->sendCallback(in, cb);
		else status = CDEV_NOTFOUND;
		if(status!=CDEV_SUCCESS) 
			{
			collector->processCollection(i, status);
			failCount++;
			}
		}
	
	// *********************************************************************
	// * If all of the requests failed, then the collector object will be 
	// * deleted and an error will be returned...  Note that when the 
	// * collector object is deleted, then the cdevTranObj that it contains
	// * will also be deleted.
	// *********************************************************************
	if (failCount>=nCollections)
		{
		status = CDEV_ERROR;
		delete collector;
		}
	else status = CDEV_SUCCESS;
	return status;
	}
