/*-----------------------------------------------------------------------
  This is another driver for the EL737 counter box used at SINQ. The hp in
  the name is for high performance, though this only a promise and not yet
  proven. This version connects directly to the RS232 port of the counter
  box on the terminal server rather then working with David Maden's 
  SerPortServer program. Other tweaks will be implemented as well, such as
  reading the monitors only now and then and not on all calls. 

  Mark Koennecke, July 2003
  -------------------------------------------------------------------------*/
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <time.h>
#include <fortify.h>
#include <string.h>
#include <sics.h>
#include <rs232controller.h>
#include <countdriv.h>
#include <splitter.h>

#define MONTHRESH 20

#define STATSEND 0
#define STATRECEIVE 2
#define BADTRANGE -117766
/*-----------------------------------------------------------------------
  our own data struture
  ------------------------------------------------------------------------*/
typedef struct {
  prs232 controller;
  int monitorCount;             /* read monitors if this is above MONTHRESH */
  float cachedControl;
  int errorCode;
  int finishCount;              /* need RS = 0 2 times before really sure finished */
  int statusMode;
  time_t startRequest;
  int lastStatus;               /* need to remember last status, otherwise I get oscillating
                                   NoBeam and Counting status if the beam goes off
                                 */
  char *badReply;
  int readErrorCount;           /* need to remember failed reads: on RDOE, upper level
                                   code will see busy's and thus not catch the case of
                                   multiple failures
                                 */
} EL737hp, *pEL737hp;
/*--------------------- ERROR CODES -------------------------------------*/
#define OFFLINE -1
#define BADRANGE -3
#define BADCOMMAND -4
#define BADPARAM -5
#define NOPARAM  -7
#define TOMANYCOUNTS -8
#define SYSERROR  -9
#define BADREPLY  -10
#define SELECTFAIL -11
#define TIMEOUT737    -12
#define TOMANYREADERRORS -23
/*---------------------------------------------------------------------*/
static void setBadReply(pEL737hp self, char *reply)
{
  if (self->badReply != NULL) {
    free(self->badReply);
  }
  self->badReply = strdup(reply);
}

/*-----------------------------------------------------------------------
  search errors in a reply from the EL737. Returns 1 on success or a
  negative error code in case of trouble.
  ------------------------------------------------------------------------*/
static int checkEL737Error(char *pReply)
{

  /*
     all error start with a ?, if no ? in reply, answer is OK! 
   */
  if (strstr(pReply, "?") == NULL) {
    return 1;
  }

  /*
     Now there is an error and we have to identify it
   */
  if (strstr(pReply, "?OF") != NULL) {
    return OFFLINE;
  } else if (strstr(pReply, "?OV") != NULL) {
    return OVERFLOW;
  } else if (strstr(pReply, "?1") != NULL) {
    return BADRANGE;
  } else if (strstr(pReply, "?2") != NULL) {
    return BADCOMMAND;
  } else if (strstr(pReply, "?3") != NULL) {
    return BADPARAM;
  } else if (strstr(pReply, "?4") != NULL) {
    return BADCOUNTER;
  } else if (strstr(pReply, "?5") != NULL) {
    return NOPARAM;
  } else if (strstr(pReply, "?6") != NULL) {
    return TOMANYCOUNTS;
  } else {
    return SYSERROR;
  }
}

/*---------------------------------------------------------------
 fixMode checks if we are in STATRECEIVE mode, reads any pending
 data and sets the mode to STATSEND. This would fix the problem 
 if another client wishes to give a command while we are expecting
 a status response
 ---------------------------------------------------------------*/
static void fixMode(pEL737hp pPriv)
{
  char pBuffer[256];
  int len = 255;

  if (pPriv->statusMode == STATRECEIVE) {
    readRS232TillTerm(pPriv->controller, pBuffer, &len);
    pPriv->statusMode = STATSEND;
  }
}

/*---------------------------------------------------------------*/
static int EL737Command(pEL737hp pPriv, char *pCommand,
                        char *pReply, int replylen)
{
  int status;

  status = transactRS232(pPriv->controller, pCommand, strlen(pCommand),
                         pReply, replylen);
  if (status < 0) {
    pPriv->errorCode = status;
    return 0;
  }
  status = checkEL737Error(pReply);
  if (status < 0) {
    pPriv->errorCode = status;
    return 0;
  }
  return 1;
}

/*----------------------------------------------------------------*/
static int readRS(pEL737hp pPriv, int *RS)
{
  int status, len = 131;
  char reply[132];

  status = readRS232TillTerm(pPriv->controller, reply, &len);
  if (status < 0) {
    pPriv->readErrorCount++;
    pPriv->errorCode = status;
    return 0;
  }
  status = checkEL737Error(reply);
  if (status < 0) {
    pPriv->readErrorCount++;
    pPriv->errorCode = status;
    return 0;
  }
  status = sscanf(reply, "%d", RS);
  if (status < 1) {
    pPriv->readErrorCount++;
    pPriv->errorCode = BADREPLY;
    setBadReply(pPriv, reply);
    printf("Bad reply to EL737 RS command: %s\n", reply);
    return 0;
  }
  pPriv->readErrorCount = 0;
  return 1;
}

/*-----------------------------------------------------------------*/
static int decodeRS(pEL737hp pPriv, int RS)
{
  int returnValue;

  switch (RS) {
  case 0:
    pPriv->finishCount++;
    if (pPriv->finishCount > 2) {
      returnValue = HWIdle;
    } else {
      returnValue = HWBusy;
    }
    break;
  case 1:
  case 2:
    pPriv->finishCount = 0;
    returnValue = HWBusy;
    break;
  case 5:
  case 6:
    pPriv->finishCount = 0;
    returnValue = HWNoBeam;
    break;
  default:
    pPriv->finishCount = 0;
    returnValue = HWPause;
    break;
  }
  return returnValue;
}

/*-----------------------------------------------------------------*/
static int updateMonitors(struct __COUNTER *self)
{
  int status;
  long m1, m2, m3, m4, m5, m6, m7, m8;
  float fTime;
  pEL737hp pPriv = NULL;
  char reply[132];

  pPriv = (pEL737hp) self->pData;

  if (!EL737Command(pPriv, "RA\r", reply, 131)) {
    return 0;
  }

  /*
     There are two forms of RA replys: new form with 8 monitors
   */
  status = sscanf(reply, "%f %ld %ld %ld %ld %ld %ld %ld %ld",
                  &fTime, &m1, &m2, &m3, &m4, &m5, &m6, &m7, &m8);
  if (status != 9) {
    /*
       old form with 4 monitors
     */
    status =
        sscanf(reply, "%ld %ld %ld %ld %f", &m1, &m2, &m3, &m4, &fTime);
    if (status != 5) {
      pPriv->errorCode = BADREPLY;
      setBadReply(pPriv, reply);
      printf("Bad reply to EL737 RA command: %s\n", reply);
      return 0;
    }
  }
  self->lCounts[0] = m2;
  self->lCounts[1] = m1;
  self->lCounts[2] = m3;
  self->lCounts[3] = m4;
  self->lCounts[4] = m5;
  self->lCounts[5] = m6;
  self->lCounts[6] = m7;
  self->lCounts[7] = m8;
  self->fTime = fTime;

  if (self->eMode == eTimer) {
    pPriv->cachedControl = fTime;
  } else {
    pPriv->cachedControl = m1;
  }
  return 1;
}

/*------------------------------------------------------------------*/
static int EL737Status(struct __COUNTER *self, float *fControl)
{
  int status, RS, returnValue;
  pEL737hp pPriv = NULL;

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

  /*
     handle STATSEND mode
   */
  if (pPriv->statusMode == STATSEND) {
    status = writeRS232(pPriv->controller, "RS\r", 3);
    if (status < 0) {
      pPriv->errorCode = status;
      return HWFault;
    }
    pPriv->statusMode = STATRECEIVE;
    pPriv->startRequest = time(NULL);
    *fControl = pPriv->cachedControl;
    return pPriv->lastStatus;
  }

  /*
     now we are dealing with STATRECEIVE mode. 
     Check for  timeout first. 
   */
  if (time(NULL) > pPriv->startRequest + 10) {
    pPriv->statusMode = STATSEND;
    pPriv->errorCode = TIMEOUT737;
    pPriv->readErrorCount++;
    return HWFault;
  }

  /*
     check availability of data
   */
  status = availableNetRS232(pPriv->controller);
  if (status == 0) {
    *fControl = pPriv->cachedControl;
    return pPriv->lastStatus;
  } else if (status < 0) {
    *fControl = pPriv->cachedControl;
    pPriv->statusMode = STATSEND;
    pPriv->errorCode = SELECTFAIL;
    return HWFault;
  }

  /*
     The snail in the counter box has replied. Read and process
     the data
   */
  pPriv->statusMode = STATSEND;
  if (!readRS(pPriv, &RS)) {
    return HWFault;
  }

  /*
     decode it
   */
  returnValue = decodeRS(pPriv, RS);
  pPriv->lastStatus = returnValue;

  /*
     check for excessive failed reads
   */
  if (pPriv->readErrorCount > 3) {
    pPriv->errorCode = TOMANYREADERRORS;
    return HWFault;
  }

  /*
     check if we update the monitors and do it 
   */
  pPriv->monitorCount++;
  if (pPriv->monitorCount > MONTHRESH) {
    status = updateMonitors(self);
    pPriv->monitorCount = 0;
    if (!status) {
      return HWFault;
    }
  }

  *fControl = pPriv->cachedControl;
  return returnValue;
}

/*-------------------------------------------------------------------*/
static int EL737Start(struct __COUNTER *self)
{
  pEL737hp pPriv = NULL;
  int status;
  char pCommand[50], pReply[30];

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

  fixMode(pPriv);
  pPriv->readErrorCount = 0;
  if (self->eMode == ePreset) {
    if (self->fPreset < 1. || self->fPreset >= 2147483647.) {
      self->iErrorCode = BADTRANGE;
      return HWFault;
    } 
    snprintf(pCommand, 49, "MP %d\r", (int) self->fPreset);
  } else {
    if (self->fPreset < .1 || self->fPreset > 200000) {
      self->iErrorCode = BADTRANGE;
      return HWFault;
    }
    snprintf(pCommand, 49, "TP %.2f\r", self->fPreset);
  }
  if (EL737Command(pPriv, pCommand, pReply, 29) != 1) {
    return 0;
  }
  pPriv->finishCount = 0;
  pPriv->lastStatus = HWBusy;
  return 1;
}

/* --------------------------------------------------------------------*/
static int EL737Pause(struct __COUNTER *self)
{
  pEL737hp pPriv = NULL;
  int status;
  char pCommand[50], pReply[30];

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

  fixMode(pPriv);
  pPriv->lastStatus = HWPause;
  return EL737Command(pPriv, "PS\r", pReply, 29);
}

/*----------------------------------------------------------------------*/
static int EL737Continue(struct __COUNTER *self)
{
  pEL737hp pPriv = NULL;
  int status;
  char pCommand[50], pReply[30];

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

  fixMode(pPriv);
  pPriv->lastStatus = HWBusy;
  return EL737Command(pPriv, "CO\r", pReply, 29);
}

/*---------------------------------------------------------------------*/
static int EL737Halt(struct __COUNTER *self)
{
  pEL737hp pPriv = NULL;
  int status;
  char pCommand[50], pReply[30];

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

  fixMode(pPriv);
  pPriv->lastStatus = HWBusy;
  return EL737Command(pPriv, "S\r", pReply, 29);
}

/*-------------------------------------------------------------------*/
static int EL737Transfer(struct __COUNTER *self)
{
  pEL737hp pPriv = NULL;
  assert(self);
  pPriv = (pEL737hp) self->pData;

  fixMode(pPriv);
  return updateMonitors(self);
}

/*--------------------------------------------------------------------*/
static int EL737GetError(struct __COUNTER *self, int *iCode,
                         char *pError, int errLen)
{
  pEL737hp pPriv = NULL;
  assert(self);
  pPriv = (pEL737hp) self->pData;

  *iCode = pPriv->errorCode;
  switch (pPriv->errorCode) {
  case OFFLINE:
    strlcpy(pError, "EL737 is offline", errLen);
    break;
  case OVERFLOW:
    strlcpy(pError, "EL737 reported overflow, communication problem",
            errLen);
    break;
  case BADRANGE:
    strlcpy(pError, "EL737 parameter is out of range", errLen);
    break;
  case BADTRANGE:
    strlcpy(pError, "preset timer out of range", errLen);
    break;
  case BADCOMMAND:
    strlcpy(pError, "EL737 received unknown command or is busy", errLen);
    break;
  case BADPARAM:
    strlcpy(pError, "EL737 parameter is awful", errLen);
    break;
  case NOPARAM:
    strlcpy(pError, "EL737 parameter missing", errLen);
    break;
  case TOMANYCOUNTS:
    strlcpy(pError, "EL737 counters overflowed", errLen);
    break;
  case SYSERROR:
    strlcpy(pError, "EL737 has an internal system error", errLen);
    break;
  case BADREPLY:
    snprintf(pError, errLen, "EL737 sent an unexpected reply: %s",
             pPriv->badReply);
    break;
  case SELECTFAIL:
    strlcpy(pError, "select system call failed, network trouble", errLen);
    break;
  case TIMEOUT737:
    strlcpy(pError,
            "timeout or network problem while waiting for status repsonse",
            errLen);
    break;
  case TOMANYREADERRORS:
    strlcpy(pError, "Failed more then three times to read counter box",
            errLen);
    break;
  default:
    getRS232Error(pPriv->errorCode, pError, errLen);
  }
  return 1;
}

/*--------------------------------------------------------------------*/
static int EL737FixIt(struct __COUNTER *self, int iCode)
{
  pEL737hp pPriv = NULL;
  int status;
  char pReply[50];
  char buffer[256];
  int dataLen = 255;
  int i;

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

  switch (iCode) {
  case BADPARAM:
  case NOPARAM:
  case BADRANGE:
  case BADTRANGE:
  case OVERFLOW:
    return COREDO;
    break;
  case BADREPLY:
  case TIMEOUT737:
  case TIMEOUT:
    for (i = 0; i < 3; i++) {
      status = readRS232TillTerm(pPriv->controller, buffer, &dataLen);
      if (status == 1) {
        return COREDO;
      }
    }
    /*
       If nothing can be read, the only fixable cause is a network breakdown
       Try to fix this. If this does not work: give up
     */
    closeRS232(pPriv->controller);
    SicsWait(60);
    status = initRS232(pPriv->controller);
    if (status != 1) {
      return COTERM;
    } else {
      return COREDO;
    }
    break;
  case OFFLINE:
    EL737Command(pPriv, "RMT 1\r", pReply, 49);
    EL737Command(pPriv, "echo 2\r", pReply, 49);
    return COREDO;
    break;
  case BADCOMMAND:             /* can be busy, stop it and try again */
    EL737Command(pPriv, "S\r", pReply, 49);
    return COREDO;
    break;
  case TOMANYCOUNTS:
	  return COREDO;
	  break;
  case SYSERROR:
    return COTERM;
    break;
  default:
    /*
       network problem; try to reopen
     */
    closeRS232(pPriv->controller);
    SicsWait(60);
    status = initRS232(pPriv->controller);
    if (status != 1) {
      return COTERM;
    } else {
      return COREDO;
    }
  }
}

/*------------------------------------------------------------------------*/
static int EL737Set(struct __COUNTER *self, char *name, int iCter,
                    float fVal)
{
  pEL737hp pPriv = NULL;
  int status;
  char pCommand[80], pReply[50];

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

  fixMode(pPriv);
  if (strcmp(name, "threshold") == 0) {
    sprintf(pCommand, "DL %1.1d %f\r", iCter, fVal);
    if (!EL737Command(pPriv, pCommand, pReply, 49)) {
      return 0;
    }
    sprintf(pCommand, "DR %1.1d\r", iCter);
    if (!EL737Command(pPriv, pCommand, pReply, 49)) {
      return 0;
    }
    return 1;
  } else if (strcmp(name, "debug") == 0) {
    setRS232Debug(pPriv->controller, (int) fVal);
    return 1;
  } else {
    self->iErrorCode = UNKNOWNPAR;
    return 0;
  }
}

/*----------------------------------------------------------------------*/
static int EL737Get(struct __COUNTER *self, char *name, int iCter,
                    float *fVal)
{
  pEL737hp pPriv = NULL;
  int status;
  char pCommand[80], pReply[50];

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

  fixMode(pPriv);
  if (strcmp(name, "threshold") == 0) {
    sprintf(pCommand, "DL %1.1d\r", iCter);
    if (!EL737Command(pPriv, pCommand, pReply, 49)) {
      return 0;
    }
    sscanf(pReply, "%f", fVal);
    return 1;
  } else if (strcmp(name, "debug") == 0) {
    *fVal = pPriv->controller->debug;
    return 1;
  } else {
    self->iErrorCode = UNKNOWNPAR;
    return 0;
  }
}

/*--------------------------------------------------------------------*/
static int EL737Send(struct __COUNTER *self, char *pText, char *pReply,
                     int iReplyLen)
{
  pEL737hp pPriv = NULL;

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

  fixMode(pPriv);
  return EL737Command(pPriv, pText, pReply, iReplyLen);
}

/*---------------------------------------------------------------------*/
static void KillHP(pCounterDriver self)
{
  pEL737hp pPriv = NULL;

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

  if (!pPriv) {
    return;
  }
  if (pPriv->controller != NULL) {
    KillRS232(pPriv->controller);
  }
  if (pPriv->badReply != NULL) {
    free(pPriv->badReply);
  }
  free(pPriv);
}

/*-------------------------------------------------------------------*/
pCounterDriver MakeEL737HP(SConnection * pCon, char *name,
                           int argc, char *argv[])
{
  pCounterDriver pNew = NULL;
  pEL737hp pPriv = NULL;
  char pHost[132];
  int port, status;

  /*
     check arguments
   */
  if (argc < 2) {
    SCWrite(pCon, "ERROR: insufficient no af arguments to create EL737HP",
            eError);
    return NULL;
  }
  if (!isNumeric(argv[1])) {
    SCWrite(pCon, "ERROR: expected numeric argument for port number",
            eError);
    return NULL;
  }
  port = atoi(argv[1]);
  strlcpy(pHost, argv[0], 131);

  /*
     allocate a bank worth of memory ...........
   */
  pNew = CreateCounterDriver(name, "EL737HP");
  pPriv = (pEL737hp) malloc(sizeof(EL737hp));
  if (!pNew || !pPriv) {
    return NULL;
  }
  memset(pPriv, 0, sizeof(EL737hp));
  pPriv->controller = createRS232(pHost, port);
  if (!pPriv->controller) {
    DeleteCounterDriver(pNew);
    return NULL;
  }

  /* assign functions */
  pNew->GetStatus = EL737Status;
  pNew->Start = EL737Start;
  pNew->Halt = EL737Halt;
  pNew->ReadValues = EL737Transfer;
  pNew->GetError = EL737GetError;
  pNew->TryAndFixIt = EL737FixIt;
  pNew->Pause = EL737Pause;
  pNew->Continue = EL737Continue;
  pNew->Set = EL737Set;
  pNew->Get = EL737Get;
  pNew->Send = EL737Send;
  pNew->KillPrivate = KillHP;
  pNew->iNoOfMonitors = 7;
  pNew->fTime = 0.;
  pNew->pData = pPriv;

  /*
     initialize connection
   */
  setRS232Debug(pPriv->controller, 0);
  setRS232ReplyTerminator(pPriv->controller, "\r");
  setRS232Timeout(pPriv->controller, 4000);
  status = initRS232(pPriv->controller);
  status = EL737Command(pPriv, "RMT 1\r", pHost, 131);
  status = EL737Command(pPriv, "RMT 1\r", pHost, 131);
  status = EL737Command(pPriv, "ECHO 2\r", pHost, 131);

  return pNew;
}
