//-----------------------------------------------------------------------------
// 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.
//
//-----------------------------------------------------------------------------
//
// Description:
//      cdevGroup class implementation
//
// Author:  Jie Chen
//
// Revision History:
//   cdevGroup.cc,v
// Revision 1.11  1997/07/28  12:58:24  chen
// fix a bug that causes missing fds
//
// Revision 1.10  1997/04/02  17:54:05  akers
// Ongoing Development of CDEV 1.6
//
// Revision 1.9  1997/03/03  17:36:04  chen
// add buffering to channel access connection
//
// Revision 1.8  1997/01/29  19:37:06  akers
// Removed assertion
//
// Revision 1.7  1996/05/02  14:17:27  chen
// handle unfinished transaction
//
// Revision 1.6  1996/01/17  17:50:48  chen
// change status implementation
//
// Revision 1.5  1995/12/14  19:11:45  chen
// fix a bug on a default group
//
// Revision 1.4  1995/12/08  15:35:30  chen
// handle deferred mode
//
// Revision 1.3  1995/07/05  18:42:50  chen
// change status interface
//
// Revision 1.2  1995/06/30  16:03:35  chen
// use generic list
//
// Revision 1.1.1.1  1995/06/16  17:14:07  epics
// initial import of cdev
//
//
#include <cdevClock.h>
#include <cdevErrCode.h>
#include <cdevRequestObject.h>
#include "cdevGroup.h"

cdevGroup::cdevGroup (unsigned int blockSize, cdevSystem &system)
:cdevSync(), objList_ ((cdevTranObj *)0, blockSize), ite_ (objList_),
 eobjList_ ((cdevExecObj *)0, blockSize), eite_ (eobjList_),
 serviceList_(), system_ (system), 
 remXobj_ (1), hwMark_(0), active_ (0), 
 mode_ (CDEV_EXEC_IMMEDIATE), numEobjs_ (0), execStage_ (0)
{
#ifdef _TRACE_OBJECTS
  printf("    Create cdevGroup class\n");
#endif
  unregOn_ = 1;
  errBit_ = system.registerGroup (this);
}

cdevGroup::~cdevGroup (void)
{
#ifdef _TRACE_OBJECTS
  printf("    Destroy cdevGroup class\n");
#endif  
  cleanAll ();
  cleanAllEobjs ();
  active_ = 0;
  // system will clean this up depending on unregOn_ flag
  if (unregOn_)
    system_.removeGroup (this);
}

void
cdevGroup::cleanAll (void)
{
  // first to clean out all transaction objects
  // we may change this implementation.
  // eg. we put all unfinished transaction objects to some
  // free list in the system

  // lock the list with remXobj_ set off
  remXobj_ = 0;
  int i = 0;
  cdevTranObj* obj = 0;
  for (ite_.init(); i < hwMark_ && !ite_; ++ite_){
    obj = (cdevTranObj *)ite_ ();
    if (obj != 0 && obj->status_ == 1){
      obj->trash (this);
      ite_ = 0;
    }
    i++;
  }
  remXobj_ = 1;

  // clear all service pointer from the service list
  serviceList_.deleteAllValues ();
  // clear out high water mark
  hwMark_ = 0;
}

int
cdevGroup::start (void)
{
  if (!active_){
    cleanAll ();
    cleanAllEobjs ();
    execStage_ = 0;
    ite_.init ();
    eite_.init ();
    if (errBit_){
      system_.reportError(
	  	 CDEV_SEVERITY_SEVERE, "cdevGroup", NULL,
		"Exceeds maximum number of groups\n");
      return CDEV_ERROR;
    }
    active_ = 1;
    return system_.registerActiveGroup (this);
  }
  else
    return CDEV_SUCCESS;
}

int
cdevGroup::end (void)
{
  if (active_){
    active_ = 0;
    return system_.removeActiveGroup (this);
  }
  else
    return CDEV_SUCCESS;
}

cdevTranObj **
cdevGroup::addTranObj (cdevTranObj *obj)
{
  ite_.forcePut ((void *)obj);
  cdevTranObj **tmp = (cdevTranObj **)ite_.currentPosition();
  ++ite_;
  hwMark_ ++;
  return tmp;
}


void
cdevGroup::setupFdMask (void)
{
  cdevSlistIterator sit(serviceList_);
  cdevService *tsvc = 0;

  rdMask_.reset();
  wrMask_.reset();
  exMask_.reset();
  for (sit.init(); !sit; ++sit){
    tsvc = (cdevService *)sit ();
    int *fd = 0;
    int numFds = 0;
    tsvc->getFd (fd, numFds);
    if (numFds != 0){
      for (int j = 0; j < numFds; j++)
	attachReadFd (fd[j]);
      numFds = 0;
    }
  }
}

void
cdevGroup::qpoll (void)
{
  // service list is constructed already
  cdevSlistIterator sit(serviceList_);
  cdevService *tsvc = 0;
  for (sit.init(); !sit; ++sit){
    tsvc = (cdevService *)sit ();
    tsvc->poll();
  }
}

int
cdevGroup::flush (void)
{
  // must send out commands before closing the group
  if (mode_ == CDEV_EXEC_DEFERRED) {
    if (allFinished ()) {
      cleanAll ();
      ite_.init ();
      execAllCommands ();
    }
    else
      return CDEV_ERROR;
  }
  if (hwMark_) {
    getServices ();
    cdevSlistIterator sit(serviceList_);
    cdevService *tsvc = 0;
    for (sit.init(); !sit; ++sit){
      tsvc = (cdevService *)sit ();
      tsvc->flush();
    }
  }
  return CDEV_SUCCESS;
}

int
cdevGroup::poll (void)
{
  if (active_) // implicitly end this group
    end ();
  if (hwMark_) {
    getServices ();
    cdevSlistIterator sit(serviceList_);
    cdevService *tsvc = 0;
    for (sit.init(); !sit; ++sit){
      tsvc = (cdevService *)sit ();
      tsvc->poll();
    }
  }
  return CDEV_SUCCESS;
}

int
cdevGroup::pend (int /* fd */)
{
  if (active_) // implicitly end this group
    end ();
  if (hwMark_) {
    getServices ();
    cdevSlistIterator sit(serviceList_);
    cdevService *tsvc = 0;
    // first flush out all requests
    for (sit.init(); !sit; ++sit) {
      tsvc = (cdevService *)sit ();
      tsvc->flush ();
    }

    rdMask_.reset();
    wrMask_.reset();
    exMask_.reset();
    for (sit.init(); !sit; ++sit){
      tsvc = (cdevService *)sit ();
      int *fd = 0;
      int numFds = 0;
      tsvc->getFd (fd, numFds);
      if (numFds != 0){
	for (int j = 0; j < numFds; j++) 
	  attachReadFd (fd[j]);
	numFds = 0;
      }
    }
    // check IO ready or not
    return handleEvents ((cdevTimeValue *)0);
  }
  else
    return CDEV_SUCCESS;
}

int
cdevGroup::pend (double seconds, int /* fd */)
{
  if (active_) // implicitly end this group
    end ();
  if (hwMark_) {
    if (seconds > 0.0){
      getServices ();
      cdevSlistIterator sit(serviceList_);
      cdevService *tsvc = 0;
      for (sit.init(); !sit; ++sit) {
	tsvc = (cdevService *)sit ();
	tsvc->flush();
      }
      rdMask_.reset ();
      wrMask_.reset ();
      exMask_.reset ();

      for (sit.init(); !sit; ++sit){
	tsvc = (cdevService *)sit ();
	int *fd = 0;
	int numFds = 0;
	tsvc->getFd (fd, numFds);
	for (int j = 0; j < numFds; j++) {
	  attachReadFd (fd[j]);
	}
	numFds = 0;
      }
      //check IO ready
      cdevTimeValue how_long (seconds);
      return handleEvents (&how_long);
    }
    else
      return poll ();
  }
  else
    return CDEV_SUCCESS;
}

int
cdevGroup::handleEvents (cdevTimeValue *tv)
{
  cdevFdSet rmaskret;
  cdevFdSet wmaskret;
  cdevFdSet emaskret;
  int    nfound = 0;
  cdevClock timer;
  
  // some times this group is already finished by some other group's
  // pend activities
  if (allFinished ()) 
    return CDEV_SUCCESS;

  if (tv){ // finite time out value
    // poke low level service first
    qpoll ();
    
    // check latest fd's
    setupFdMask ();

    // split time out value into smaller chunks
    cdevTimeValue ttv (0.1);
    int numloops = 0;
    int i = 0;

    if (*tv > ttv) {
      numloops = (int)(tv->sec ()/0.1 + tv->usec ()/100000.0);
	
      while (i < numloops && !allFinished ()) {
	i++;
	nfound = waitFor (rmaskret, wmaskret, emaskret, &ttv);
	if (nfound <= 0)
	  qpoll ();
	else
	  dispatch (nfound, rmaskret, wmaskret, emaskret);
	

	// check fd mask to make sure new fds are added
	if( !allFinished() )   
	  setupFdMask ();      
      }
      if (i < numloops) // success
	return CDEV_SUCCESS;
    }
    else {
	nfound = waitFor (rmaskret, wmaskret, emaskret, tv);
	if (nfound <= 0)
	  qpoll ();
	else
	  dispatch (nfound, rmaskret, wmaskret, emaskret);
      }
    if (allFinished())
      return CDEV_SUCCESS;
    // timed out
    return CDEV_TIMEOUT;
  }
  else { // infinite long time out
    while (!allFinished ()){
      // poke low level service first
      qpoll ();

      // check latest fd's
      setupFdMask ();

      // make sure there is no block
      cdevTimeValue ttv (0.1);

      nfound = waitFor (rmaskret, wmaskret, emaskret, &ttv);

      if (nfound <= 0) {
	// just poke at the low level service
	qpoll ();
      }
      else {
	// really handle lower level IO here
	dispatch (nfound, rmaskret, wmaskret, emaskret);
      }
    }
  }
  return CDEV_SUCCESS;
}

void 
cdevGroup::notifyService (int handle)
{
  cdevService *service = pendingService (handle);
  
  if (service && service->pend (handle) < 0)
    { 
      // remove this file descriptor
      detachReadFd (handle);
    }
}

cdevService *
cdevGroup::pendingService (int handle)
{
  cdevSlistIterator sit (serviceList_);
  cdevService *tsvc = 0;

  for (sit.init(); !sit; ++sit){
    tsvc = (cdevService *)sit ();
    int *fd = 0;
    int numFds = 0;
    tsvc->getFd (fd, numFds);
    if (numFds != 0){
      for (int j = 0; j < numFds; j++)
	if (handle == fd[j])
	  return tsvc;
      numFds = 0;
    }
  }
  return (cdevService *)0;
}

int
cdevGroup::allFinished (void)
{
  int i = 0;
  cdevTranObj* obj = 0;
  for (ite_.init(); i < hwMark_ && !ite_; ++ite_){
    obj = (cdevTranObj *)ite_();
    if (obj != 0 && obj->status_ == 1)
      return 0;
    i++;
  }
  return 1;
}

int
cdevGroup::status (int status[], int & numTransactions)
{
  int num = numTransactions;
  numTransactions = (hwMark_ < num) ? hwMark_ : num;
  cdevTranObj* obj = 0;
  ite_.init();
  for (int i = 0; i < numTransactions; i++){
    obj = (cdevTranObj *)ite_ ();
    if (obj != 0)
      status[i] = obj->status_;
    else
      status[i] = 0;
    ++ite_;
  }
  return CDEV_SUCCESS;
}

void
cdevGroup::getServices (void)
{
  serviceList_.deleteAllValues();
  int i = 0;
  cdevTranObj* obj = 0;
  for (ite_.init(); i < hwMark_ && !ite_; ++ite_){
    obj = (cdevTranObj *) ite_ ();
    if (obj != 0 && obj->status_ == 1)
      if (!serviceList_.includes((void *)&(obj->reqObj_->service()))) {
	serviceList_.add ((void *)&(obj->reqObj_->service() ));
      }
    i++;
  }
}


cdevExecObj **
cdevGroup::addExecObj (cdevExecObj *eobj)
{
  eite_.forcePut ((void *)eobj);
  cdevExecObj **tmp = (cdevExecObj **)eite_.currentPosition();
  ++eite_;
  numEobjs_ ++;
  return tmp;
}

void
cdevGroup::cleanAllEobjs (void)
{
  // first to clean out all execution objects

  // lock the list with remEobj_ set off
  remEobj_ = 0;
  cdevExecObj* obj = 0;
  int i = 0;
  
  for (eite_.init(); i < numEobjs_ && !eite_; ++eite_){
    obj = (cdevExecObj *) eite_ ();
    if (obj != 0 && obj->status_ == 1){
      delete obj;
      eite_ = 0;
    }
    i++;
  }
  remEobj_ = 1;
  numEobjs_ = 0;
}

void
cdevGroup::execDeferred (void)
{
  mode_ = CDEV_EXEC_DEFERRED;
}

void
cdevGroup::execImmediate (void)
{
  mode_ = CDEV_EXEC_IMMEDIATE;
}

int
cdevGroup::executionMode (void) const
{
  return mode_;
}

int
cdevGroup::execAllCommands (void)
{
  int status= CDEV_SUCCESS;
  int ist;

  // change execution stage to execution
  execStage_ = 1;

  // reopen this group for handling synchronization of all operations
  if (!active_) {
    active_ = 1;
    system_.registerActiveGroup (this);
  }

  cdevExecObj *obj = 0;
  int i = 0;
  for (eite_.init (); i < numEobjs_ && !eite_; ++eite_) {
    obj = (cdevExecObj *) eite_ ();
    cdevRequestObject *reqobj = obj->reqObj_;
    if (obj->userCallback_ != 0) {
      if ((ist = reqobj->sendCallback (obj->outData_, *(obj->userCallback_)))
	  != CDEV_SUCCESS)
	status = ist;
    }
    else {
      if ((ist= reqobj->sendNoBlock (obj->outData_, obj->resultData_))
	  != CDEV_SUCCESS)
	status = ist;
    }
    // do not delete transaction objects until restart this group
    // user can use flush to resend all commands
    i++;
  }
  // turn the execution stage to buffering stage again
  execStage_ = 0;

  return status;
}

int
cdevGroup::readyToExec (void) const
{
  return execStage_;
}
