/* -----------------------------------------------------------------------------
 * 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.
 *
 * -----------------------------------------------------------------------------
 *
 * cdevDefCollectionRequest.h : This is the default class that will be used
 *                              to support collection operations for services.
 *
 * Author:  Walt Akers
 *
 * Revision History:
 *   cdevDefCollectionRequest.cc,v
// Revision 1.2  1998/10/22  17:57:33  chen
// fix a bug on setting collection values
//
// Revision 1.1  1996/11/12  20:32:23  akers
// New collection device source code
//
 * -----------------------------------------------------------------------------
 */

#include <cdevClock.h>
#include <cdevDefCollectionRequest.h>
#include <defCallbackCollector.h>

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

// *****************************************************************************
// * cdevDefCollectionRequest::cdevDefCollectionRequest :
// *	This is the constructor for the cdevDefCollectionRequest object.  It 
// *	will initialize all of the internals for the class.
// *****************************************************************************
cdevDefCollectionRequest::cdevDefCollectionRequest 
	(char **devices, int nDevices, char *msg, cdevSystem& sys)
	: cdevCollectionRequest(devices, nDevices, msg, sys),
	  requests(NULL),
	  nRequests(nDevices),
	  format()
	{
	requests = new cdevRequestObject *[nDevices];
	for(int i=0; i<nDevices; i++)
		{
		requests[i] = cdevRequestObject::attachPtr(devices[i], msg);
		}
	}
	

// *****************************************************************************
// * cdevDefCollectionRequest::~cdevDefCollectionRequest :
// *	This is the destructor for the cdevDefCollectionRequest object.
// *****************************************************************************
cdevDefCollectionRequest::~cdevDefCollectionRequest ( void ) 
	{
	delete requests;
	}
	

// *****************************************************************************
// * setContext:
// *	This methods executes the setContext method on each of the underlying
// *	cdevRequestObjects.
// *****************************************************************************
int cdevDefCollectionRequest::setContext ( cdevData & ctx )
	{
	for(int i=0; i<nRequests; i++)
		{
		if(requests[i]!=NULL) requests[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 cdevDefCollectionRequest::getState ( void )
	{
	int state;

	if(nRequests==0) state = CDEV_STATE_INVALID;
	else for(int i=0; i<nRequests; i++)
		{
		int tState;
		if(requests[i]==NULL) state=CDEV_STATE_INVALID;
		else if((tState = requests[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 cdevDefCollectionRequest::getAccess ( void )
	{
	int accessCode;

	if(nRequests==0) accessCode = CDEV_ACCESS_NONE;
	else for(int i=0; i<nRequests; i++)
		{
		int tAccess;
		if(requests[i]==NULL) accessCode=CDEV_STATE_INVALID;
		else if((tAccess = requests[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 cdevDefCollectionRequest::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 cdevDefCollectionRequest::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 defCallbackCollector that is
// *	associated with this requestObject...
// *****************************************************************************
void cdevDefCollectionRequest::asyncCallback
	(int status, void * arg, cdevRequestObject &, cdevData &data)
	{
	defCallbackCollector::Request * request   = NULL;
	defCallbackCollector          * collector = NULL;
	
	if(arg!=NULL)
		{
		request = (defCallbackCollector::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->requests[request->index]==request)
		{
		collector->processRequest(request->index, status, &data);

		// *************************************************************
		// * Having processed this entry, I'll test to determine if all
		// * of the members in this defCallbackCollector 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 cdevDefCollectionRequest::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 cdevDefCollectionRequest::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 cdevDefCollectionRequest::sendCallback (cdevData* in, cdevCallback& callback)
	{
	int         failCount = 0;
	int         status    = CDEV_SUCCESS;
	cdevData*   out       = 0;
	cdevTranObj *xobj     = 
	  new cdevTranObj(&system_, this, NULL, new cdevCallback(callback));
	defCallbackCollector *collector = 
	  new defCallbackCollector(nRequests, format, *xobj);

	xobj->enableDeleteCbk();

	// ********************************************************************
	// * first split incoming data to nRequest data
	// * this may produce memory leaks: transaction object should has
	// * an option to copy data pointer and delete it. -- Jie Chen 10/22/98
	// ********************************************************************
	if (in) 
	  out = cdevDefCollectionRequest::splitData (*in, nRequests);

	
	// ********************************************************************
	// * 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 defCallbackCollector
	// * object - this will be used to populate the proper entry in the 
	// * status attribute of the defCallbackCollector 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<nRequests; 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->requests[i]));
		if(requests[i]!=NULL) {
		  if (!in)
		    status = requests[i]->sendCallback(in, cb);
		  else if (out) 
		    status = requests[i]->sendCallback(&out[i], cb);
		  else
		    status = CDEV_CONVERT;
		}
		else status = CDEV_NOTFOUND;
		if(status!=CDEV_SUCCESS) 
			{
			collector->processRequest(i, status);
			failCount++;
			}
		}
	
	// *********************************************************************
	// * Note: Due to memory leaks, we are going to delete the out array 
	// * of cdevData objects.  In the event that the underlying cdevRequest
	// * Object does not make a cpy of the cdevData object - then this will
	// * most certainly cause a crash.  Blame the service developer.
	// *********************************************************************
	if (out) delete [] out;
	
	// *********************************************************************
	// * 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>=nRequests)
		{
		status = CDEV_ERROR;
		delete collector;
		}
	else status = CDEV_SUCCESS;
	return status;
	}


// *********************************************************************
// * This is routine set scaler value to array of cdevData with a
// * specified tag
// *********************************************************************
int
cdevDefCollectionRequest::setScalerDataValue (cdevData& data, int tag,
					      cdevData* res, int num)
{
  cdevDataTypes type;
  int i, status;

  status = CDEV_SUCCESS;

  type = data.getType (tag);
  switch (type) {
  case CDEV_BYTE:
    {
      unsigned char val = 0;
      if ((status = data.get (tag, &val)) == CDEV_SUCCESS)
	for (i = 0; i < num; i++)
	  res[i].insert (tag, val);
    }
  break;
  case CDEV_INT16:
    {
      short val = 0;
      if ((status = data.get (tag, &val)) == CDEV_SUCCESS)
	for (i = 0; i < num; i++)
	  res[i].insert (tag, val);
    }
  break;
  case CDEV_UINT16:
    {
      unsigned short val = 0;
      if ((status = data.get (tag, &val)) == CDEV_SUCCESS)
	for (i = 0; i < num; i++)
	  res[i].insert (tag, val);
    }
  break;
  case CDEV_INT32:
    {
      int val = 0;
      if ((status = data.get (tag, &val)) == CDEV_SUCCESS)
	for (i = 0; i < num; i++)
	  res[i].insert (tag, val);
    }      
  break;
  case CDEV_UINT32:
    {
      unsigned int val = 0;
      if ((status = data.get (tag, &val)) == CDEV_SUCCESS)
	for (i = 0; i < num; i++)
	  res[i].insert (tag, val);
    }
  break;
  case CDEV_FLOAT:
    {
      float val = 0;
      if ((status = data.get (tag, &val)) == CDEV_SUCCESS)
	for (i = 0; i < num; i++)
	  res[i].insert (tag, val);
    }
  break;
  case CDEV_DOUBLE:
    {
      double val = 0;
      if ((status = data.get (tag, &val)) == CDEV_SUCCESS)
	for (i = 0; i < num; i++)
	  res[i].insert (tag, val);
    }      
  break;
  case CDEV_STRING:
    {
      char val[1024];
      if ((status = data.get (tag, val, sizeof (val) - 1)) == CDEV_SUCCESS)
	for (i = 0; i < num; i++)
	  res[i].insert (tag, val);
    }
  break;
  case CDEV_TIMESTAMP:
    {
      cdev_TS_STAMP val;
      if ((status = data.get (tag, &val)) == CDEV_SUCCESS)
	for (i = 0; i < num; i++)
	  res[i].insert (tag, val);
    }      
  break;
  default:
    status = CDEV_ERROR;
    break;
  }
  return status;
}

// *********************************************************************
// * This is routine set vector value to array of cdevData with a
// * specified tag
// *********************************************************************
int
cdevDefCollectionRequest::setVectorDataValue (cdevData& data, int tag, size_t dim, 
					      cdevData* res, int num)
{
  int i = 0;
  int j = 0;
  int status = CDEV_SUCCESS;
  cdevDataTypes type;
  size_t       elems, outelems;
  cdevBounds*  databounds;
  cdevBounds*  resbounds = 0;
  
  databounds = new cdevBounds[dim];

  if (data.getElems (tag, &elems) != CDEV_SUCCESS)
    return CDEV_ERROR;

  // Note: the last entry in the bounds information must be the same
  // value as the num of data we want to generate
  // We are only concerning about whole package of data. For incomplete
  // data packet (i.e. offset != 0 length != whole length), we are not
  // going to deal with it.
  if (data.getBounds (tag, databounds, dim) == CDEV_SUCCESS &&
      databounds[0].length == num) {

    // do an extra check: not necessary
    if (dim == 1 && num != elems) {
	cdevSystem::defaultSystem().reportError
		(CDEV_SEVERITY_ERROR, "cdevDefCollectionRequest", NULL, "One-dim array has wrong number of elements");
	return CDEV_ERROR;
    }

    // calculate number of elements of output data 
    outelems = 1;
    for (i = 1; i < dim; i++)
      outelems *= databounds[i].length;

    if (outelems > 1) {
      resbounds = new cdevBounds[dim - 1];
      for (i = 0; i < dim - 1; i++) {
	resbounds[i].length = databounds[i + 1].length;
	resbounds[i].offset = databounds[i + 1].offset;
      }
    }

    type = data.getType (tag);
    switch (type) {
    case CDEV_BYTE:
      {
	void* tmp = 0;
	unsigned char* val = 0;
	
	if ((status = data.find (tag, tmp)) == CDEV_SUCCESS) {
	  val = (unsigned char *)tmp;
	  
	  if (outelems > 1) {
	    j = 0;
	    for (i = 0; i < num; i++) {
	      res[i].insert (tag, &(val[j]), outelems, dim - 1);
	      res[i].setBounds (tag, resbounds, dim - 1);
	      j += outelems;
	    }
	  }
	  else {
	    j = 0;
	    for (i = 0; i < num; i++) 
	      res[i].insert (tag, val[j++]);
	  }
	}
      }
    break;
    case CDEV_INT16:
      {
	void* tmp = 0;
	short* val = 0;
	
	if ((status = data.find (tag, tmp)) == CDEV_SUCCESS) {
	  val = (short *)tmp;
	  
	  if (outelems > 1) {
	    j = 0;
	    for (i = 0; i < num; i++) {
	      res[i].insert (tag, &(val[j]), outelems, dim - 1);
	      res[i].setBounds (tag, resbounds, dim - 1);
	      j += outelems;
	    }
	  }
	  else {
	    j = 0;
	    for (i = 0; i < num; i++) 
	      res[i].insert (tag, val[j++]);
	  }
	}
      }
    break;
    case CDEV_UINT16:
      {
	void* tmp = 0;
	unsigned short* val = 0;
	
	if ((status = data.find (tag, tmp)) == CDEV_SUCCESS) {
	  val = (unsigned short *)tmp;
	  
	  if (outelems > 1) {
	    j = 0;
	    for (i = 0; i < num; i++) {
	      res[i].insert (tag, &(val[j]), outelems, dim - 1);
	      res[i].setBounds (tag, resbounds, dim - 1);
	      j += outelems;
	    }
	  }
	  else {
	    j = 0;
	    for (i = 0; i < num; i++) 
	      res[i].insert (tag, val[j++]);
	  }
	}
      }
    break;
    case CDEV_INT32:
      {
	void* tmp = 0;
	int* val = 0;
	
	if ((status = data.find (tag, tmp)) == CDEV_SUCCESS) {
	  val = (int *)tmp;
	  
	  if (outelems > 1) {
	    j = 0;
	    for (i = 0; i < num; i++) {
	      res[i].insert (tag, &(val[j]), outelems, dim - 1);
	      res[i].setBounds (tag, resbounds, dim - 1);
	      j += outelems;
	    }
	  }
	  else {
	    j = 0;
	    for (i = 0; i < num; i++) 
	      res[i].insert (tag, val[j++]);
	  }
	}
      }
    break;
    case CDEV_UINT32:
      {
	void* tmp = 0;
	unsigned int* val = 0;
	
	if ((status = data.find (tag, tmp)) == CDEV_SUCCESS) {
	  val = (unsigned int *)tmp;
	  
	  if (outelems > 1) {
	    j = 0;
	    for (i = 0; i < num; i++) {
	      res[i].insert (tag, &(val[j]), outelems, dim - 1);
	      res[i].setBounds (tag, resbounds, dim - 1);
	      j += outelems;
	    }
	  }
	  else {
	    j = 0;
	    for (i = 0; i < num; i++) 
	      res[i].insert (tag, val[j++]);
	  }
	}
      }
    break;
    case CDEV_FLOAT:
      {
	void* tmp = 0;
	float* val = 0;
	
	if ((status = data.find (tag, tmp)) == CDEV_SUCCESS) {
	  val = (float *)tmp;
	  
	  if (outelems > 1) {
	    j = 0;
	    for (i = 0; i < num; i++) {
	      res[i].insert (tag, &(val[j]), outelems, dim - 1);
	      res[i].setBounds (tag, resbounds, dim - 1);
	      j += outelems;
	    }
	  }
	  else {
	    j = 0;
	    for (i = 0; i < num; i++) 
	      res[i].insert (tag, val[j++]);
	  }
	}
      }
    break;
    case CDEV_DOUBLE:
      {
	void* tmp = 0;
	double* val = 0;
	
	if ((status = data.find (tag, tmp)) == CDEV_SUCCESS) {
	  val = (double *)tmp;
	  
	  if (outelems > 1) {
	    j = 0;
	    for (i = 0; i < num; i++) {
	      res[i].insert (tag, &(val[j]), outelems, dim - 1);
	      res[i].setBounds (tag, resbounds, dim - 1);
	      j += outelems;
	    }
	  }
	  else {
	    j = 0;
	    for (i = 0; i < num; i++) 
	      res[i].insert (tag, val[j++]);
	  }
	}
      }
    break;
    case CDEV_STRING:
      {
	if (dim == 1) { // Strings with dimention > 1 makes no sense
	  char** tmp = new char*[num];

	  if ((status = data.get (tag, tmp)) == CDEV_SUCCESS) {
	    for (i = 0; i < num; i++) 
	      res[i].insert (tag, tmp[i]);
	  }
	  
	  for (i = 0; i < num; i++)
	    delete []tmp[i];
	  delete []tmp;
	}
	else
	  status = CDEV_ERROR;
      }
    break;
    case CDEV_TIMESTAMP:
      {
	void* tmp = 0;
	cdev_TS_STAMP* val = 0;
	
	if ((status = data.find (tag, tmp)) == CDEV_SUCCESS) {
	  val = (cdev_TS_STAMP *)tmp;
	  
	  if (outelems > 1) {
	    j = 0;
	    for (i = 0; i < num; i++) {
	      res[i].insert (tag, &(val[j]), outelems, dim - 1);
	      res[i].setBounds (tag, resbounds, dim - 1);
	      j += outelems;
	    }
	  }
	  else {
	    j = 0;
	    for (i = 0; i < num; i++) 
	      res[i].insert (tag, val[j++]);
	  }
	}
      }
    break;
    default:
      status = CDEV_ERROR;
    }
  }
  else
    status = CDEV_ERROR;
 
  // free databounds
  delete []databounds;

  if (resbounds)
    delete []resbounds;

  return status;
}
    

// *********************************************************************
// * This is routine split a input cdevData into an array of cdevData.
// * The input data is either a scaler value which will be duplicated
// * to all splited data values, or a array of data whose bound[0].length
// * must be the same as the 'num'. This works for a single tag
// *********************************************************************    
int
cdevDefCollectionRequest::splitDataItem (cdevData& data, int tag, 
					 cdevData* res, int num)
{
  size_t dim;
  int    status;
  
  if (data.getDim (tag, &dim) != CDEV_SUCCESS)
    return CDEV_ERROR;
  
  if (dim == 0) // scaler data, make every entry in the array the same
    status = setScalerDataValue (data, tag, res, num);
  else 
    status = setVectorDataValue (data, tag, dim, res, num);

  return status;
}

// *********************************************************************
// * This is routine split a input cdevData into an array of cdevData.
// * The input data is either a scaler value which will be duplicated
// * to all splited data values, or a array of data whose bound[0].length
// * must be the same as the 'num'
// * caller must free return cdevData array if it is not zero.
// *********************************************************************
cdevData *
cdevDefCollectionRequest::splitData (cdevData& data, int num)
{
  int tag, status;
  cdevData* res = 0;
  cdevDataIterator ite (&data);
  
  // first allocate arrary of data
  res = new cdevData[num];

  status = CDEV_SUCCESS;
  // walk through each tagged item to insert data into correct cdevData
  // in the newly allocated array
  for (ite.init (); !ite; ++ite) {
    tag = ite.tag ();
    if (splitDataItem (data, tag, res, num) != CDEV_SUCCESS) {
      status = CDEV_CONVERT;
      break;
    }
  }
  if (status != CDEV_SUCCESS) {
    delete []res;
    res = 0;
  }
  return res;
}
