//-----------------------------------------------------------------------------
// 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:
//       CA service class 
//
// Author:  Jie Chen
//
// Revision History:
//   caService.cc,v
// Revision 1.10  1998/09/02  19:08:49  chen
// change ca_flush_io to ca_pend_io (0.00000001)
//
// Revision 1.9  1998/03/05  18:49:14  chen
// fix a bug on context == 2 on property channels
//
// Revision 1.8  1998/02/18  19:48:26  chen
// fix ca set access error
//
// Revision 1.7  1997/03/03  17:36:32  chen
// add buffering to channel access connection
//
// Revision 1.6  1995/10/26  14:33:15  akers
// Removal of Warnings
//
// Revision 1.5  1995/10/03  19:57:12  chen
// Add lookup table for attributes from tags
//
// Revision 1.4  1995/08/28  18:07:09  chen
// Add some error check on removeReadFd
//
// Revision 1.3  1995/07/05  18:34:43  chen
// use default hash function
//
// Revision 1.2  1995/06/30  15:55:14  chen
// remove unnecessary files
//
// Revision 1.1.1.1  1995/06/16  17:14:02  epics
// initial import of cdev
//
// 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifndef _WIN32
#include <unistd.h>
#endif

#include <cdevErrCode.h>
#include "caService.h"
#include "caRequestObject.h"
#include "caNameSvc.h"
#include "caChannel.h"

// hash table size for channle ID pointers
const int CID_TABLE_SIZE = 997;

int caService::CA_TAG_PV = 0x1001;
int caService::CA_TAG_DFV = 0x1002;
int caService::CA_TAG_RO  = 0x1003;
int caService::CA_TAG_VALUE = 0;
int caService::CA_TAG_STATUS = 0;
int caService::CA_TAG_SEVERITY = 0;
int caService::CA_TAG_TIME = 0;
int caService::CA_TAG_UNITS = 0;
int caService::CA_TAG_DISPHI = 0;
int caService::CA_TAG_DISPLO = 0;
int caService::CA_TAG_ALRMHI = 0;
int caService::CA_TAG_ALRMLO = 0;
int caService::CA_TAG_WRNHI = 0;
int caService::CA_TAG_WRNLO = 0;
int caService::CA_TAG_CTRLHI = 0;
int caService::CA_TAG_CTRLLO = 0;
int caService::CA_TAG_RESULT_CODE = 0;
int caService::CA_TAG_PRECISION = 0;

//===========================================================================
//       Implementation of class caService
//===========================================================================
caService::caService(char *name, cdevSystem& system)
:cdevService(name, system), numFds_(0), fds_(0), 
 channelTable_ (CID_TABLE_SIZE, cdevStrHashFunc)
{
#ifdef _TRACE_OBJECTS
  printf("                Create caService class\n");
#endif
  // create new name service just for channel access use only
  nameSvc_ = new caNameSvc (system);
  // add new tag to cdevData
  if (caService::CA_TAG_CTRLLO == 0){
    cdevData::insertTag (caService::CA_TAG_PV, "PV");
    cdevData::insertTag (caService::CA_TAG_DFV, "DEFAULT");
    cdevData::insertTag (caService::CA_TAG_RO, "readonly");
    caService::mapCtagToItag ();
  }
  // initialize tag to attribute table
  initTagToAttrTable ();
  // channel access initialize stuff
  int status = ::ca_task_initialize();
  // here one could do ca_add_fd_registration
  ::ca_add_fd_registration (&(caService::fdRegCallback), (void *)this);
  ::ca_add_exception_event (&(caService::exceptionCallback), (void *)this);
}

caService::~caService(void)
{
#ifdef _TRACE_OBJECTS
  printf("                Delete caService class\n");
#endif
  // system_.removeService (this);
  ::ca_task_exit ();
}

int
caService::getFd (int * &fd, int &numFds)
{
  if (numFds_ == 0){
    numFds = numFds_;
    fd = 0;
  }
  else{
    fd = fds_;
    numFds = numFds_;
  }
  return CDEV_SUCCESS;
}

int
caService::handlePending (int /* fd */)
{
  if (::ca_pend_event (0.0001) == ECA_NORMAL) {
    return CDEV_SUCCESS;
  }
  return CDEV_IOFAILED;
}

int 
caService::flush (void)
{
#ifdef _CDEV_DEBUG
  printf("Flush by caService\n");
#endif
  if (::ca_pend_event (0.00000001) == ECA_NORMAL) 
    return CDEV_SUCCESS;
  else 
    return CDEV_IOFAILED;
}

int 
caService::poll (void)
{
#ifdef _CDEV_DEBUG
  printf("Poll by caService\n");
#endif  
  if (::ca_pend_event (0.0001) == ECA_NORMAL) {
    return CDEV_SUCCESS;
  }
  return CDEV_IOFAILED;
}

int 
caService::pend (int fd)
{
#ifdef _CDEV_DEBUG
  printf ("caService Pend\n");
#endif
  return handlePending (fd);
}

int 
caService::pend (double seconds, int)
{
  // for now I have to do this way since ca_array_get_callback has problem
#ifdef _CDEV_DEBUG
  printf ("caService Pend %f\n",seconds);
#endif
  if (::ca_pend_event (seconds) == ECA_NORMAL) {
    return CDEV_SUCCESS;
  }
  return CDEV_IOFAILED;
}

int 
caService::getRequestObject (char* deviceName, char* msg,
			     cdevRequestObject * &req)
{
  req = new caRequestObject (deviceName, msg, this, system_);
  if (req)
    return CDEV_SUCCESS;
  else
    return CDEV_ERROR;
}

int
caService::getNameServer (cdevDevice* &server)
{
  server = 0;
  return CDEV_SUCCESS;
}

void
caService::addReadFd (int fd)
{
  if (numFds_){
    int *newfds = new int[numFds_ + 1];
    for (int i =0; i < numFds_; i++)
      newfds[i] = fds_[i];
    delete []fds_;
    newfds[i] = fd;
    fds_ = newfds;
    numFds_ += 1;
  }
  else{
    fds_ = new int[1];
    fds_[0] = fd;
    numFds_ = 1;
  }
}

void
caService::removeReadFd (int fd)
{
  // check whether this fd is inside the list
  int found = 0;
  for (int i = 0; i < numFds_; i++){
    if (fds_[i] == fd) {
      found = 1;
      break;
    }
  }
  if (!found)
    return;
  if (numFds_ > 1){
    int *newfds = new int[numFds_ - 1];
    int j = 0;
    for (i = 0; i < numFds_; i++){
      if (fds_[i] != fd)
	newfds[j++] = fds_[i];
    }
    delete []fds_;
    fds_ = newfds;
    numFds_ = numFds_ - 1;
  }
  else{
    delete []fds_;
    numFds_ = 0;
  }
}

void
caService::fdRegCallback (void *arg, int fd, int opened)
{
  caService *obj = (caService *)arg;
  if (opened){
    obj->addReadFd (fd);
#ifdef _CDEV_DEBUG
    printf("fd %d opened\n",fd);
#endif
  }
  else{
    obj->removeReadFd (fd);
#ifdef _CDEV_DEBUG
    printf("fd %d closed\n",fd);
#endif
  }

  obj->registerFd (fd, opened);
}

void
caService::exceptionCallback (struct exception_handler_args /* args */)
{
#ifdef _CDEV_DEBUG
  printf("CA: exception handler called\n");
#endif
}

int
caService::channelPtr (char *channelName, caChannel* &ch)
{
  cdevSlist& list = channelTable_.bucketRef (channelName);
  cdevSlistIterator lit (list);
  caChannel *cal;

  for (lit.init(); !lit; ++lit){
    cal = (caChannel *)lit ();
    if (::strcmp (cal->channelName(), channelName) == 0){
      ch = (caChannel *)lit();
      return CDEV_SUCCESS;
    }
  }
  ch = 0;
  return CDEV_ERROR;
}

int
caService::addChannel (char *channelName, caChannel* chPtr)
{
  channelTable_.add (channelName, (void *)chPtr);
  return CDEV_SUCCESS;
}

int
caService::removeChannel (char *channelName, caChannel* chPtr)
{
  channelTable_.remove (channelName, (void *)chPtr);
  return CDEV_SUCCESS;
}

void
caService::mapCtagToItag (void)
{
  cdevData::tagC2I ("value", &caService::CA_TAG_VALUE);
  cdevData::tagC2I ("status", &caService::CA_TAG_STATUS);
  cdevData::tagC2I ("severity", &caService::CA_TAG_SEVERITY);
  cdevData::tagC2I ("time", &caService::CA_TAG_TIME);
  cdevData::tagC2I ("units", &caService::CA_TAG_UNITS);
  cdevData::tagC2I ("displayHigh", &caService::CA_TAG_DISPHI);
  cdevData::tagC2I ("displayLow", &caService::CA_TAG_DISPLO);
  cdevData::tagC2I ("alarmHigh", &caService::CA_TAG_ALRMHI);
  cdevData::tagC2I ("alarmLow", &caService::CA_TAG_ALRMLO);
  cdevData::tagC2I ("warningHigh", &caService::CA_TAG_WRNHI);
  cdevData::tagC2I ("warningLow", &caService::CA_TAG_WRNLO);
  cdevData::tagC2I ("controlHigh", &caService::CA_TAG_CTRLHI);
  cdevData::tagC2I ("controlLow", &caService::CA_TAG_CTRLLO);
  cdevData::tagC2I ("resultCode", &caService::CA_TAG_RESULT_CODE);
  cdevData::tagC2I ("precision", &caService::CA_TAG_PRECISION);
  // Initialize tag to attribute table
#ifdef _CDEV_DEBUG
  printf("value tag value is %d\n", caService::CA_TAG_VALUE);
  printf("status tag value is %d\n", caService::CA_TAG_STATUS);
  printf("severity tag value is %d\n", caService::CA_TAG_SEVERITY);
  printf("time tag value is %d\n", caService::CA_TAG_TIME);
  printf("units tag value is %d\n", caService::CA_TAG_UNITS);
  printf("displayHigh tag value is %d\n", caService::CA_TAG_DISPHI);
  printf("displayLow tag value is %d\n", caService::CA_TAG_DISPLO);
  printf("alarmHigh tag value is %d\n", caService::CA_TAG_ALRMHI);
  printf("alarmLow tag value is %d\n", caService::CA_TAG_ALRMLO);
  printf("warningHigh tag value is %d\n", caService::CA_TAG_WRNHI);
  printf("warningLow tag value is %d\n", caService::CA_TAG_WRNLO);
  printf("controlHigh tag value is %d\n", caService::CA_TAG_CTRLHI);
  printf("controlLow tag value is %d\n", caService::CA_TAG_CTRLLO);
  printf("result code tag value is %d\n", caService::CA_TAG_RESULT_CODE);
  printf("precision tag value is %d\n", caService::CA_TAG_PRECISION);
#endif
}

void
caService::initTagToAttrTable (void)
{
  int i = 0;
  tagToAttrTb_[i].tag = caService::CA_TAG_VALUE;
  tagToAttrTb_[i++].attr = "VAL";
  tagToAttrTb_[i].tag = caService::CA_TAG_STATUS;
  tagToAttrTb_[i++].attr = "STAT";
  tagToAttrTb_[i].tag = caService::CA_TAG_SEVERITY;
  tagToAttrTb_[i++].attr = "SEVR";
  tagToAttrTb_[i].tag = caService::CA_TAG_TIME;
  tagToAttrTb_[i++].attr = "TIME";
  tagToAttrTb_[i].tag = caService::CA_TAG_UNITS;
  tagToAttrTb_[i++].attr = "EGU";
  tagToAttrTb_[i].tag = caService::CA_TAG_PRECISION;
  tagToAttrTb_[i++].attr = "PREC";
  tagToAttrTb_[i].tag = caService::CA_TAG_DISPHI;
  tagToAttrTb_[i++].attr = "HOPR";
  tagToAttrTb_[i].tag = caService::CA_TAG_DISPLO;
  tagToAttrTb_[i++].attr = "LOPR";
  tagToAttrTb_[i].tag = caService::CA_TAG_ALRMHI;
  tagToAttrTb_[i++].attr = "HIHI";
  tagToAttrTb_[i].tag = caService::CA_TAG_ALRMLO;
  tagToAttrTb_[i++].attr = "LOLO";
  tagToAttrTb_[i].tag = caService::CA_TAG_WRNHI;
  tagToAttrTb_[i++].attr = "HIGH";
  tagToAttrTb_[i].tag = caService::CA_TAG_WRNLO;
  tagToAttrTb_[i++].attr = "LOW";  
  tagToAttrTb_[i].tag = caService::CA_TAG_CTRLHI;
  tagToAttrTb_[i++].attr = "HOPR";
  tagToAttrTb_[i].tag = caService::CA_TAG_CTRLLO;
  tagToAttrTb_[i++].attr = "LOPR";
}

const char*
caService::attr (int tag) const
{
  int found = 0;
  for (int i = 0; i < 14; i++) {
    if (tagToAttrTb_[i].tag == tag) 
      return tagToAttrTb_[i].attr;
  }
  return 0;
}

// C interface routine to initialize an caService object
cdevService *
newCaService (char *name, cdevSystem *system)
{
  return new caService (name, *system);
}
