/*----------------------------------------------------------------------------
  This is a single counter implemented on top of EPICS slaer record.
  This is not general: In order to support the special features of a 
  neutron counter, we use the scaler record in a special way.

  copyright: see file COPYRIGHT

  Mark Koennecke, February 2013
  ---------------------------------------------------------------------------*/
#include <stdlib.h>
#include <assert.h>
#include <sics.h>
#include <status.h>
#include <countdriv.h>
#include <counter.h>

/* EPICS stuff */
#include <tsDefs.h> 
#include <cadef.h> 
#include <ezca.h>
/* #include <alarmString.h>*/
#include <menuAlarmStat.h>
#include <db_access.h>
#include <epicsThread.h>

/*------------------ our private data structure ------------------------*/
typedef struct {
  char *rootName;
  int thresholdCounter;
  int thresholdValue;
  int thresholdChanged;
  char *ezcaError;
  int ezcaCode;
  int connectCount;
} EPICSCounter, *pEPICSCounter;


/*========================================================================
  These two functions currently rely on the idea that the EPICS stops 
  and starts without clearing counters in between. The sequence of
  things necessary to start it, suggests this. If this is not the case then
  this will not work.
===========================================================================*/
static int EPICSPause(struct __COUNTER *self)
{
  int status;
  pEPICSCounter pPriv = NULL;

  /*
    not implemented
  */

  return OKOK;
}

/*=======================================================================*/
static int EPICSContinue(struct __COUNTER *self)
{
  int status;
  pEPICSCounter pPriv = NULL;

  /*
    not implemented
  */
  return OKOK;
}

/*=======================================================================*/
static void CountHaltThreadFunc(void *param)
{
  char *pvName = (char *)param;
  short stop = 0;
  chid cid;

  ca_context_create(ca_disable_preemptive_callback);
  ca_create_channel(pvName,NULL,NULL,10,&cid);
  ca_pend_io(5.);
  ca_put(DBR_SHORT,cid,&stop);
  ca_pend_io(5.0);
  free(pvName);
  printf("HaltThread ends\n");
  
}
/*----------------------------------------------------------------------------*/
static int EPICSHalt(struct __COUNTER *self)
{
  int status;
  pEPICSCounter pPriv = NULL;
  char pvName[132];
  short stop = 0;

  assert(self);
  pPriv = (pEPICSCounter) self->pData;
  assert(pPriv);

  snprintf(pvName,sizeof(pvName),"%s.CNT", pPriv->rootName);
  /* ezcaPut(pvName,ezcaShort,1,&stop); */
  epicsThreadCreate("Lieselotte", 
		    epicsThreadPriorityHigh,
		    epicsThreadStackMedium,
		    CountHaltThreadFunc,
		    (void *)strdup(pvName));

  return OKOK;
}

/*=======================================================================*/
static int EPICSTransfer(struct __COUNTER *self)
{
  int status, i;
  pEPICSCounter priv = NULL;
  char pvName[132];
  long m;

  assert(self);
  priv = (pEPICSCounter) self->pData;
  assert(priv);

  /*
    load monitors
  */
  for(i = 2; i < 10; i++){
    snprintf(pvName,sizeof(pvName),"%s.S%d",priv->rootName,i);
    status = ezcaGet(pvName,ezcaLong,1,&m);
    if(status == EZCA_OK) {
      self->lCounts[i-2] = m;
    }  else {
      ezcaGetErrorString("ERROR: EPICS: ", &priv->ezcaError);
      priv->ezcaCode = status;
      return HWFault;
    }
  }

  /*
    read time
  */
  snprintf(pvName,sizeof(pvName),"%s.S1",priv->rootName);
  status = ezcaGet(pvName,ezcaLong,1,&m);
  if(status == EZCA_OK) {
    self->fTime =  (float)m/1000.;
  }  else {
    ezcaGetErrorString("ERROR: EPICS: ", &priv->ezcaError);
    priv->ezcaCode = status;
    return HWFault;
  }

  return OKOK;
}

/*-----------------------------------------------------------------------*/
static int EPICSGetStatus(struct __COUNTER *self, float *fControl)
{
  pEPICSCounter priv = (pEPICSCounter) self->pData;
  char pvName[132];
  short cnt;
  long m;
  int i, valChange = 0, status;


  assert(priv);

  if((status = EPICSTransfer(self)) != OKOK){
    return status;
  }
  priv->connectCount = 0;
  if(self->eMode == eTimer){
    *fControl = self->fTime;
  } else {
    *fControl = (float)self->lCounts[1];
  }

  /*
    read extended status
  */
  snprintf(pvName,sizeof(pvName),"%s.S10",priv->rootName);
  status = ezcaGet(pvName,ezcaLong,1,&m);
  if(status != EZCA_OK) {
    ezcaGetErrorString("ERROR: EPICS: ", &priv->ezcaError);
    priv->ezcaCode = status;
    return HWFault;
  }

  /*
    read cnt
  */
  snprintf(pvName,sizeof(pvName),"%s.CNT",priv->rootName);
  status = ezcaGet(pvName,ezcaShort,1,&cnt);
  if(status != EZCA_OK) {
    ezcaGetErrorString("ERROR: EPICS: ", &priv->ezcaError);
    priv->ezcaCode = status;
    return HWFault;
  }
  
  /*
    analyse status
  */
  switch(m){
  case 0:
    if(cnt == 0){
      return HWIdle;
    } else {
      return HWBusy;
    }
    break;
  case 1: 
    return HWBusy;
  case 2:
    return HWNoBeam;
  case 3:
    return HWPause;
  default:
    /*
      bad status code form EPICS
    */
    assert(1);
  }
  return HWFault;
}
/*-------------- dummy callback for Start -------------------------*/
static void StartDummy(struct event_handler_args d)
{
}
/*-----------------------------------------------------------------------*/
static int EPICSStart(struct __COUNTER *self)
{
  pEPICSCounter priv = NULL;
  char pvName[132];
  int status, pr2;
  double dTime;
  long lPreset;
  short cnt = 1;
  chid *cid;

  assert(self);
  priv = (pEPICSCounter) self->pData;
  assert(priv);

  if(priv->thresholdChanged){
    snprintf(pvName,sizeof(pvName),"%s.PR3",priv->rootName);
    status = ezcaPut(pvName,ezcaLong,1,&priv->thresholdCounter);
    if(status != EZCA_OK) {
      ezcaGetErrorString("ERROR: EPICS: ", &priv->ezcaError);
      priv->ezcaCode = status;
      return HWFault;
    }
    snprintf(pvName,sizeof(pvName),"%s.PR4",priv->rootName);
    status = ezcaPut(pvName,ezcaLong,1,&priv->thresholdValue);
    if(status != EZCA_OK) {
      ezcaGetErrorString("ERROR: EPICS: ", &priv->ezcaError);
      priv->ezcaCode = status;
      return HWFault;
    } else {
      priv->thresholdChanged = 0;
    }
  }
    
  if(self->eMode == eTimer){
    dTime = self->fPreset;
    snprintf(pvName,sizeof(pvName),"%s.TP",priv->rootName);
    status = ezcaPut(pvName,ezcaDouble,1,&dTime);
    if(status != EZCA_OK) {
      ezcaGetErrorString("ERROR: EPICS: ", &priv->ezcaError);
      priv->ezcaCode = status;
      return HWFault;
    }
    pr2 = 0;
  } else {
    snprintf(pvName,sizeof(pvName),"%s.PR1",priv->rootName);
    lPreset = (long)self->fPreset;
    status = ezcaPut(pvName,ezcaLong,1,&lPreset);
    if(status != EZCA_OK) {
      ezcaGetErrorString("ERROR: EPICS: ", &priv->ezcaError);
      priv->ezcaCode = status;
      return HWFault;
    }
    pr2 = 10;
  }

  snprintf(pvName,sizeof(pvName),"%s.PR2",priv->rootName);
  status = ezcaPut(pvName,ezcaLong,1,&pr2);
  if(status != EZCA_OK) {
    ezcaGetErrorString("ERROR: EPICS: ", &priv->ezcaError);
    priv->ezcaCode = status;
    return HWFault;
  }

  snprintf(pvName,sizeof(pvName),"%s.CNT",priv->rootName);
  ezcaPvToChid(pvName,&cid);
  status = ca_put_callback(DBR_SHORT,*cid,&cnt, StartDummy,NULL);
  if(status != ECA_NORMAL){
    snprintf(pvName, sizeof(pvName),"Bad CA status %d", status);
    priv->ezcaError = strdup(pvName);
    priv->ezcaCode = status;
    return HWFault;
  } else {
    ca_pend_event(.05);
    priv->connectCount = 0;
    return OKOK;
  }

  return OKOK;
}

/*======================================================================*/
static int EPICSGetError(struct __COUNTER *self, int *iCode,
                       char *errorText, int errlen)
{
  char pBueffel[132];
  pEPICSCounter priv = NULL;
 
  priv = (pEPICSCounter) self->pData;
  if(priv->ezcaError != NULL){
    strncpy(errorText,priv->ezcaError,errlen);
    ezcaFree(priv->ezcaError);
    priv->ezcaError = NULL;
  } else {
    strncpy(errorText,"Unknown error",errlen);
  }

  return 1;
}

/*=======================================================================*/
static int EPICSFixIt(struct __COUNTER *self, int iCode)
{
  pEPICSCounter priv = NULL;

  priv = (pEPICSCounter) self->pData;
  if(priv->ezcaCode == EZCA_NOTCONNECTED && priv->connectCount < 2) {
    priv->connectCount++;
    return COREDO;
  }
  return COTERM;
}

/*-----------------------------------------------------------------------*/
static int EPICSSet(struct __COUNTER *self, char *name,
                  int iCter, float fVal)
{
  pEPICSCounter pPriv = NULL;
  int iVal;

  assert(self);
  pPriv = (pEPICSCounter) self->pData;
  assert(pPriv);

  if(strcmp(name,"threshold") == 0){
    pPriv->thresholdCounter = iCter;
    pPriv->thresholdValue = (int)fVal;
    pPriv->thresholdChanged = 1;
    return OKOK;
  } else {
    return HWFault;
  } 

}

/*===================================================================*/
static int EPICSGet(struct __COUNTER *self, char *name,
                  int iCter, float *fVal)
{
  pEPICSCounter pPriv = NULL;

  assert(self);
  pPriv = (pEPICSCounter) self->pData;
  assert(pPriv);

  if(strcmp(name,"threshold") == 0){
    *fVal = pPriv->thresholdValue;
    return OKOK;
  } else {
    return HWFault;
  }
}

/*=====================================================================*/
static int EPICSSend(struct __COUNTER *self, char *text,
                   char *reply, int replylen)
{
  strlcpy(reply, "EPICS does not feast on ASCII strings, refused!",
          replylen);
  return OKOK;
}

/*====================================================================*/
pCounterDriver MakeEPICSCounter(char *rootname)
{
  pEPICSCounter pPriv = NULL;
  pCounterDriver self = NULL;
  int i;
  char pvName[132];
  

  /*
     memory for everybody
   */
  self = CreateCounterDriver("epics", "epics");
  pPriv = (pEPICSCounter) malloc(sizeof(EPICSCounter));
  if (self == NULL || pPriv == NULL) {
    return NULL;
  }
  memset(pPriv, 0, sizeof(EPICSCounter));
  pPriv->thresholdChanged = 1;
  pPriv->rootName = strdup(rootname);

  /*
    install monitors
  */
  for(i = 1; i < 11; i++){
    snprintf(pvName,sizeof(pvName),"%s.S%d", rootname,i);
    ezcaSetMonitor(pvName,ezcaLong,1);
  }
  snprintf(pvName,sizeof(pvName),"%s.CNT", rootname);
  ezcaSetMonitor(pvName,ezcaShort,1);

  /*
     assign function pointers
   */
  self->GetStatus = EPICSGetStatus;
  self->Start = EPICSStart;
  self->Pause = EPICSPause;
  self->Continue = EPICSContinue;
  self->Halt = EPICSHalt;
  self->ReadValues = EPICSTransfer;
  self->GetError = EPICSGetError;
  self->TryAndFixIt = EPICSFixIt;
  self->Set = EPICSSet;
  self->Get = EPICSGet;
  self->Send = EPICSSend;
  self->KillPrivate = NULL;
  self->iNoOfMonitors = 8;

  self->pData = pPriv;
  return self;
}
