//-----------------------------------------------------------------------------
// 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:
//      Channel Access Channel Class (Implementation)
//
// Author:  Jie Chen
//
// Revision History:
//   caChannel.cc,v
// Revision 1.11  1998/04/10  17:02:20  chen
// get access information to all monitor callbacks
//
// Revision 1.10  1998/04/10  13:55:39  chen
// add access information to caService
//
// Revision 1.9  1998/03/05  18:49:11  chen
// fix a bug on context == 2 on property channels
//
// Revision 1.8  1998/03/04  13:22:29  chen
// fix minor bug
//
// Revision 1.7  1998/03/03  19:51:50  chen
// fix a bug for get string buffer of enum type
//
// Revision 1.6  1997/03/03  17:36:24  chen
// add buffering to channel access connection
//
// Revision 1.5  1996/09/18  14:46:45  chen
// Channel holds multiple monitor objects
//
// Revision 1.4  1995/10/03  19:56:00  chen
// contain property channels
//
// Revision 1.3  1995/09/05  17:20:47  chen
// Handle multiple callback correctly
//
// Revision 1.2  1995/08/28  18:06:16  chen
// allow multiple cdevCallbacks
//
// Revision 1.1.1.1  1995/06/16  17:14:02  epics
// initial import of cdev
//
//
#include <cdevTranObj.h>
#include <cdevData.h>
#include <cdevCallback.h>
#include "caRequestObject.h"
#include "caService.h"
#include "caChannel.h"
#include "caMonObj.h"


caChannel::caChannel (char *channelName, caService* svc)
:numConnT_ (0), count_ (0), readonly_ (0), 
 numElements_ (1), numMonitorObjs_ (0), accessBuffer_ (0),
 pchannels_ (0), pinited_ (0), egrp_ (0), service_ (svc)
{
#ifdef _TRACE_OBJECTS
  printf("    Create caChannel Class Object\n");
#endif

  channelName_ = new char[::strlen (channelName) + 1];
  ::strcpy (channelName_, channelName);

  // create group
  egrp_ = new cdevExecGroup (svc);

  connected_ = 0;

  // send out connection request
  execConnection ();
}

caChannel::~caChannel (void)
{
#ifdef _TRACE_OBJECTS
  printf("    Delete caChannel Class Object\n");
#endif
  // remove all monitor objects
#ifdef _CDEV_DEBUG
  printf ("There are %d monitorObjects\n", numMonitorObjs_);
#endif
  if (numMonitorObjs_) {
    for (int i = 0; i < numMonitorObjs_; i++)
      delete monitorObjs_[i];
    delete []monitorObjs_;
  }
  monitorObjs_ = 0;
  numMonitorObjs_ = 0;

  // close this channel
  ::ca_clear_channel (cId_);
  if (connected_){
    ::ca_flush_io ();
    connected_ = 0;
  }

  // clean up access buffer
  if (accessBuffer_)
    delete accessBuffer_;
  delete []channelName_;
  count_ = 0;
  cId_ = 0;

  // clean out children list
  if (pinited_) {
    for (int i = 0; i < MAX_NUM_PCHANNELS; i++)
      if (pchannels_[i].channel != 0) 
	delete pchannels_[i].channel;
    delete []pchannels_;
    pchannels_ = 0;
    pinited_ = 0;
  }
  // clean out buffer
  delete egrp_;
}


#ifdef _CA_SYNC_CONN
void
caChannel::connect (void)
{
  if (numConnT_ <= MAX_NUM_RETRIES){
    int caStatus = ::ca_search (channelName_, &cId_);
    caStatus = ::ca_pend_io (1.0);
    if (caStatus != ECA_NORMAL){
#ifdef _CDEV_DEBUG
      printf ("error on search for %s\n", channelName_);
#endif
      numConnT_ ++;
    }
    else{
      numConnT_ = 0;
      connected_ = 1;
      type_ = ca_field_type (cId_);
      // add disconnection and access right callback
      ::ca_change_connection_event (cId_, &(caChannel::discCallback));
      ::ca_replace_access_rights_event (cId_, &(caChannel::accessRightCallback));
      if (type_ == DBR_ENUM){
	accessBuffer_ = new db_access_val;
	::ca_array_get (DBR_CTRL_ENUM, 1, cId_, accessBuffer_);
	if (::ca_pend_io (4.0) != ECA_NORMAL) // error: set number of string 0
	  accessBuffer_->cenmval.no_str = 0;
      }
    }
  }
  else {
    service_->reportError( CDEV_SEVERITY_ERROR, channelName_, NULL, 
		"Cannot connect to channel after %d attempts.\n", MAX_NUM_RETRIES);
  }
}
#else
int
caChannel::asyncConnect (void)
{
  if (!connected_){ // channel not connected
    if (numConnT_ > MAX_NUM_RETRIES){
	  service_->reportError( CDEV_SEVERITY_ERROR, channelName_, NULL, 
		"Cannot connect to channel after %d attempts.\n", MAX_NUM_RETRIES);
      return CDEV_NOTCONNECTED;
    }
    numConnT_ ++;
#ifdef _CDEV_DEBUG
    printf("before doing ca_pend_event (1.0) at channel %s\n", 
	   channelName_);
#endif
    int i = 0;
    int numLoops = DEFAULT_TIMEOUT*DEFAULT_CONN_FREQ;
    float stimev = 1.0/DEFAULT_CONN_FREQ;
    while (i < numLoops && !connected_){
      ::ca_pend_event (stimev);
      i++;
    }
  }
  return CDEV_SUCCESS;
}
#endif  

char *
caChannel::channelName (void) const
{
  return channelName_;
}


int
caChannel::execConnection (void)
{
  numConnT_ ++;
#ifndef _CA_SYNC_CONN
#ifndef _EPICS_3_12
  //epics 3.11 version
  int caStatus = ::ca_build_and_connect (channelName_, 
					 TYPENOTCONN,
					 0, 
					 &cId_, 
					 0,
					 &(caChannel::connectCallback), 
					 (void *)this);

  if (caStatus != ECA_NORMAL) {
#ifdef _CDEV_DEBUG
    printf("Cannot do ca_build_and_connect \n");
#endif
  }
#else
  int caStatus = ::ca_search_and_connect (channelName_, 
					  &cId_, 
					  &(caChannel::connectCallback), 
					  (void *)this);
  if (caStatus != ECA_NORMAL){
#ifdef _CDEV_DEBUG
    printf("Cannot do ca_search_and_connect \n");
#endif
  }
#endif
#else
  int caStatus = ::ca_search (channelName, &cId_);
  caStatus = ::ca_pend_io (1.0);
  if (caStatus != ECA_NORMAL){
#ifdef _CDEV_DEBUG
    printf ("error on search for %s\n", channelName);
#endif
    numConnT_ ++;
  }    
  else{
    connected_ = 1;
    // get channel type 
    type_ = ca_field_type (cId_);
    // get number of elements of this channel
    numElements_ = ca_element_count (cId_);
    // register connection change event handler
    ::ca_change_connection_event (cId_, &(caChannel::discCallback));
    ::ca_replace_access_rights_event (cId_, &(caChannel::accessRightCallback));
    // cache string values. cdev will return string value instead of
    // short integer
    if (type_ == DBR_ENUM){
      accessBuffer_ = new db_access_val;
      ::ca_array_get (DBR_CTRL_ENUM, 1, cId_, accessBuffer_);
      if (::ca_pend_io (4.0) != ECA_NORMAL) // error: set number of string 0
	accessBuffer_->cenmval.no_str = 0;
    }
  }
#endif
  if (numConnT_ % 5 == 0) {
    service_->reportError (CDEV_SEVERITY_WARN, channelName_, 0, 
			   "Trying to connect to channel\n");
#ifdef _CDEV_DEBUG
    printf ("Trying to connect to %s at least 5 times...\n", channelName_);
#endif
  }
  
  return CDEV_SUCCESS;
}


int
caChannel::addMonitorObj (caMonObj *newObj)
{
  caMonObj **newList;
  // allocate a list large enough for one more element
  newList = new caMonObj*[numMonitorObjs_ + 1];
  // copy the contents of the previous list to the new list
  for (int i = 0; i < numMonitorObjs_; i++)
    newList[i] = monitorObjs_[i];
  newList[i] = newObj;
  // free the old list
  if (numMonitorObjs_ > 0)
    delete []monitorObjs_;
  // make the new list as the current list
  monitorObjs_ = newList;
  numMonitorObjs_ ++;
#ifdef _CDEV_DEBUG
  printf ("add monitorObject to list: %d objects\n",numMonitorObjs_);
#endif
  return 1;
}

int
caChannel::removeCallback (cdevCallback* callback) 
{
  int i = 0;

  if (callback->callbackFunction() == 0) { // remove everything and event ID
    if (numMonitorObjs_) {
      for (i = 0; i < numMonitorObjs_; i++)
	delete monitorObjs_[i];
      delete []monitorObjs_;
      numMonitorObjs_ = 0;
    }
    return 1;
  }

  // remove one particular callback
  for (i = 0; i < numMonitorObjs_; i++) {
    if (monitorObjs_[i]->removeCallback (callback))
      return 1;
  }
  return 0;

}

int
caChannel::removeMonitorObj (caMonObj* obj)
{
  int found = 0;

  for (int i = 0; i < numMonitorObjs_; i++) {
    if (obj == monitorObjs_[i]) {
      found = 1;
      break;
    }
  }
      
  if (found) {
    // Detroy the monitor object.
#ifdef _CDEV_DEBUG
    printf ("remove a monitor callback object 0x%x\n", monitorObjs_[i]);
#endif
    delete monitorObjs_[i];
    if (numMonitorObjs_ > 1) {
      caMonObj **newList = new caMonObj*[numMonitorObjs_ - 1];
      int k = 0;
      for (int j = 0; j < numMonitorObjs_; j++) {
	if (j != i) 
	  newList[k++] = monitorObjs_[j];
      }
      delete []monitorObjs_;
      monitorObjs_ = newList;
      numMonitorObjs_ --;
    }
    else {
      delete []monitorObjs_;
      numMonitorObjs_ = 0;
    }
#ifdef _CDEV_DEBUG
    printf ("remove a monitorObject:%d left \n", numMonitorObjs_);
#endif
    return 1;
  }
  else
    return 0;
}

int
caChannel::hasSameCallback (cdevCallback& cbk)
{
  int found = 0;
  for (int i = 0; i < numMonitorObjs_; i++) {
    if (monitorObjs_[i]->hasSameCallback (cbk)) {
#ifdef _CDEV_DEBUG
      printf ("Found a monobject with the same callback\n");
#endif
      found = 1;
      break;
    }
  }
  return found;
}

caMonObj *
caChannel::hasSameCallingCxt (int type, int mask)
{
  for (int i = 0; i < numMonitorObjs_; i++) {
    if (type == monitorObjs_[i]->reqType_ &&
	mask == monitorObjs_[i]->reqMask_) {
#ifdef _CDEV_DEBUG
      printf ("Found a monobject with the same calling context\n");
#endif
      return monitorObjs_[i];
    }
  }
  return 0;
}  

caMonObj *
caChannel::firstMonitorObj (void)
{
  if (numMonitorObjs_ > 0)
    return monitorObjs_[0];
  return 0;
}

caPchannel *
caChannel::createPchannels (void)
{
  caPchannel* pchannels = new caPchannel[MAX_NUM_PCHANNELS];

  int i = 0;
  pchannels[i++].tag = caService::CA_TAG_STATUS;
  pchannels[i++].tag = caService::CA_TAG_SEVERITY;
  pchannels[i++].tag = caService::CA_TAG_PRECISION;
  pchannels[i++].tag = caService::CA_TAG_DISPHI;
  pchannels[i++].tag = caService::CA_TAG_DISPLO;  
  pchannels[i++].tag = caService::CA_TAG_ALRMHI;
  pchannels[i++].tag = caService::CA_TAG_ALRMLO;
  pchannels[i++].tag = caService::CA_TAG_WRNHI;
  pchannels[i++].tag = caService::CA_TAG_WRNLO;
  pchannels[i++].tag = caService::CA_TAG_CTRLHI;
  pchannels[i++].tag = caService::CA_TAG_CTRLLO;
  for (i = 0; i < MAX_NUM_PCHANNELS; i++) {
    pchannels[i].channel = 0;
    pchannels[i].monitored = 0;
  }
  return pchannels;
}

void
caChannel::propertyChannels (caPchannel* pch)
{
  if (!pinited_) {
    pinited_ = 1;
    pchannels_ = pch;
  }
}

void
caChannel::connectCallback (struct connection_handler_args conn)
{
  caChannel *obj = (caChannel *)ca_puser (conn.chid);
#ifdef _CDEV_DEBUG
  printf("Connect %s callback called\n", obj->channelName_);
#endif

  if (ca_field_type (conn.chid) == TYPENOTCONN){
    obj->connected_ = 0;
    obj->numConnT_ ++;
    if (obj->numConnT_ > MAX_NUM_RETRIES){
#ifdef _CDEV_DEBUG
      printf ("Error: Cannot connect to the channel %s\n", obj->channelName_);
#endif
    }
  }
  else{
    obj->numConnT_ = 0;
    obj->connected_ = 1;
    ::ca_change_connection_event (conn.chid,
    				  &(caChannel::discCallback));
    ::ca_replace_access_rights_event (conn.chid,
				      &(caChannel::accessRightCallback));
    obj->type_ = ca_field_type (conn.chid);
    obj->numElements_ = ca_element_count (conn.chid);
#ifdef _CDEV_DEBUG
    printf("type is 0x%x and num elements is %d\n", obj->type_, 
	   obj->numElements_);
#endif
    // cache string values. CDEV return string instead of short integer
    // for DBR_ENUM
    if (obj->type_ == DBR_ENUM){
      obj->accessBuffer_ = new db_access_val;
      obj->accessBuffer_->cenmval.no_str = 0;
      ::ca_array_get_callback (DBR_CTRL_ENUM, 1, conn.chid, 
			       &(caChannel::enumGetCallback),
			       (void *)obj);
    }
    else
      // flush out all buffered exections
      obj->egrp_->flush ();
  }
}

void
caChannel::discCallback (struct connection_handler_args conn)
{
  int status;
  caChannel *obj = (caChannel *)ca_puser (conn.chid);

  if (obj->connected_){
#ifdef _CDEV_DEBUG
    printf("disconnected for channel %s\n", obj->channelName_);
#endif
    obj->connected_ = 0;
    status = CDEV_DISCONNECTED;
  }
  else{
#ifdef _CDEV_DEBUG
    printf("reconnected for channel %s\n", obj->channelName_);
#endif
    obj->connected_ = 1;
    status = CDEV_RECONNECTED;
  }
  cdevData result;
  for (int i = 0; i < obj->numMonitorObjs_; i++) {
    cdevRequestObject *reqObj = obj->monitorObjs_[i]->reqObj_;
    obj->monitorObjs_[i]->callCallbacks (status, reqObj, result);
  }
}

void
caChannel::accessRightCallback (struct access_rights_handler_args args)
{
  caChannel *obj = (caChannel *)ca_puser (args.chid);

  int readaccess = ca_read_access (args.chid);
  int writeaccess = ca_write_access (args.chid);
  int acflag = 0;

  if (!readaccess)
    acflag  = CDEV_ACCESS_NONE;
  else if (readaccess && !writeaccess)
    acflag = CDEV_ACCESS_READONLY;
  else if (writeaccess)
    acflag = CDEV_ACCESS_WRITE;


  if (acflag != obj->readonly_) {
    obj->readonly_ = acflag;
    // call all monitor callbacks to notify access right is changed
    cdevData result;
    for (int i = 0; i < obj->numMonitorObjs_; i++) {
      cdevRequestObject *reqObj = obj->monitorObjs_[i]->reqObj_;
      obj->monitorObjs_[i]->callCallbacks (CDEV_ACCESSCHANGED, reqObj, result);
    }
  }
}

void
caChannel::enumGetCallback (struct event_handler_args args)
{
  caChannel *obj = (caChannel *)ca_puser (args.chid);  
  
  if (args.count > 0 && args.dbr != 0) {
    struct dbr_ctrl_enum *retValue = 0;
    retValue = (struct dbr_ctrl_enum *)args.dbr;
    
    memcpy (&(obj->accessBuffer_->cenmval), 
	    retValue, sizeof (struct dbr_ctrl_enum));
#ifdef _CDEV_DEBUG
    printf ("Enum has %d number of strings\n", (obj->accessBuffer_)->cenmval.no_str);
    for (int i = 0; i < (obj->accessBuffer_)->cenmval.no_str; i++) 
      printf ("String[%d] is %s\n", i, (obj->accessBuffer_)->cenmval.strs[i]);
#endif
  }
  else  {
#ifdef _CDEV_DEBUG
    printf ("enum get callback failed\n");
#endif
    (obj->accessBuffer_)->cenmval.no_str = 0;    
  }
  // now flush out callbacks
  obj->egrp_->flush ();
}
