/**
 * This is an asynchronous protocol driver for the Delta-Tau PMAC
 * series of controllers, connected via TCP/IP. The PMAC needs its
 * commands in a special purpose data structure, describe below. 
 * As responses, it can send any of the following formats:
 *     data<CR>data<CR>data>CR><ACK>
 *     <BELL>ERRxxx<CR>
 *     <STX>data<CR>
 * There can be multiple data and errors in a string. However, I wish to 
 * restrict this to processing one command at any time. This driver owes 
 * some insight and little code to the EPICS driver by 
 * Pete Leicester, Diamond. 
 * 
 * ** Before this can be used, I3=2 and I6=1 must be set on the PMAC **
 * 
 * copyright: see file COPYRIGHT
 * 
 * Mark Koennecke, December 2008
 *
 * Introduced a delay on read in order to throttle requests
 *
 * Mark Koennecke, June 2011
 */
#include <errno.h>
#include <ascon.h>
#include <ascon.i>
#include <dynstring.h>

#define ETHERNET_DATA_SIZE 1492
#define INPUT_SIZE        (ETHERNET_DATA_SIZE+1)        /* +1 to allow space to add terminating ACK */
#define STX   '\2'
#define CTRLB '\2'
#define CTRLC '\3'
#define ACK   '\6'
#define CTRLF '\6'
#define BELL  '\7'
#define CTRLG '\7'
#define CTRLP '\16'
#define CTRLV '\22'
#define CTRLX '\24'

/* PMAC ethernet command structure */
#pragma pack(1)
typedef struct tagEthernetCmd {
  unsigned char RequestType;
  unsigned char Request;
  unsigned short wValue;
  unsigned short wIndex;
  unsigned short wLength;       /* length of bData */
  unsigned char bData[ETHERNET_DATA_SIZE];
} ethernetCmd;
#pragma pack()

#define ETHERNET_CMD_HEADER ( sizeof(ethernetCmd) - ETHERNET_DATA_SIZE )

/* PMAC ethernet commands - RequestType field */
#define VR_UPLOAD   0xC0
#define VR_DOWNLOAD 0x40

/* PMAC ethernet commands - Request field */
#define VR_PMAC_SENDLINE    0xB0
#define VR_PMAC_GETLINE     0xB1
#define VR_PMAC_FLUSH       0xB3
#define VR_PMAC_GETMEM      0xB4
#define VR_PMAC_SETMEN      0xB5
#define VR_PMAC_SETBIT      0xBA
#define VR_PMAC_SETBITS     0xBB
#define VR_PMAC_PORT        0xBE
#define VR_PMAC_GETRESPONSE 0xBF
#define VR_PMAC_READREADY   0xC2
#define VR_CTRL_RESPONSE    0xC4
#define VR_PMAC_GETBUFFER   0xC5
#define VR_PMAC_WRITEBUFFER 0xC6
#define VR_PMAC_WRITEERROR  0xC7
#define VR_FWDOWNLOAD       0xCB
#define VR_IPADDRESS        0xE0

/*---------------------------------------------------------------------------
 * a private data structurli to keep track of the PMAC
 *---------------------------------------------------------------------------*/
typedef struct {
  ethernetCmd cmd;
  char *ptr;
  int bytesToWrite;
  int expectACK;
  double startTime;
} PMACPrivate, *pPMACPrivate;
/*---------------------------------------------------------------------------*
* wait period before read
 ----------------------------------------------------------------------------*/
#define READDELAY .05
/*---------------------------------------------------------------------------*/
static int PMACHandler(Ascon * a)
{
  char *data = NULL;
  int ret, l;
  char chr;
  pPMACPrivate priv = NULL;

  priv = a->private;

  switch (a->state) {
  case AsconWriteStart:
    data = GetCharArray(a->wrBuffer);
    memset(priv, 0, sizeof(PMACPrivate));
    priv->cmd.RequestType = VR_DOWNLOAD;
    priv->cmd.Request = VR_PMAC_GETRESPONSE;
    priv->cmd.wValue = 0;
    priv->cmd.wIndex = 0;
    priv->cmd.wLength = htons(strlen(data));    /* may be one more */
    priv->bytesToWrite = strlen(data) + 1 + ETHERNET_CMD_HEADER;
    strcpy((char *) priv->cmd.bData, data);
    priv->expectACK = 1;
    priv->ptr = (char *) &priv->cmd;
    a->state = AsconWriting;
    a->wrPos = 0;
    break;
  case AsconWriting:
    AsconReadGarbage(a->fd);
    l = priv->bytesToWrite - a->wrPos;
    ret = AsconWriteChars(a->fd, priv->ptr, l);
    if (ret < 0) {
      if (errno != EINTR && errno != EAGAIN) {
        AsconError(a, "send failed:", errno);
      }
      /*
       * Ooops: which state shall we go to after a write fail?
       * This seems to retry.
       */
    } else {
      a->wrPos += ret;
      if (a->wrPos >= priv->bytesToWrite) {
        a->state = AsconWriteDone;
        priv->startTime = DoubleTime();
      } else {
        priv->ptr += ret;
      }
    }
    break;
  case AsconReadStart:
    DynStringClear(a->rdBuffer);
    if(DoubleTime() > priv->startTime + READDELAY){
    	a->start = DoubleTime();
    	a->state = AsconReading;
    }
    break;
  case AsconReading:
    ret = AsconReadChar(a->fd, &chr);
    if (ret < 0) {
      /* EINTR means we must retry */
      if (errno != EINTR && errno != EAGAIN) {
        AsconError(a, "AsconReadChar failed:", errno);
      }
      return 1;
    } else if (ret > 0) {
      a->start = DoubleTime();
      if (chr == STX || chr == BELL) {
        priv->expectACK = 0;
        return 1;
      }
      if (priv->expectACK && chr == ACK) {
        if (GetDynStringLength(a->rdBuffer) == 0) {
          DynStringConcat(a->rdBuffer, "ACK");
        }
        DynStringConcatChar(a->rdBuffer, '\0');
        a->state = AsconReadDone;
        break;
      }
      if (priv->expectACK == 0 && chr == '\r') {
        DynStringConcatChar(a->rdBuffer, '\0');
        a->state = AsconReadDone;
        break;
      }
      if (DynStringConcatChar(a->rdBuffer, chr) == 0) {
        AsconError(a, "DynStringConcatChar failed:", ENOMEM);
        break;
      }
    } else if (ret == 0) {
      if (a->timeout > 0) {
        if (DoubleTime() - a->start > a->timeout) {
          AsconError(a, "read timeout", 0);
          a->state = AsconTimeout;
        }
      }
      return 0;
    }
    break;
  default:
    return AsconStdHandler(a);
  }
  return 1;
}

/*------------------------------------------------------------------------*/
static int PMACInit(Ascon * a, SConnection * con, int argc, char *argv[])
{
  pPMACPrivate priv = NULL;

  priv = calloc(sizeof(PMACPrivate), 1);
  a->fd = -1;
  a->state = AsconConnectStart;
  a->reconnectInterval = 10;
  a->hostport = strdup(argv[1]);
  if (argc > 2) {
    a->timeout = atof(argv[2]);
  } else {
    a->timeout = 2.0;           /* sec */
  }
  a->private = priv;
  a->killPrivate = free;
  return 1;
}

/*------------------------------------------------------------------------*/
void AddPMACProtocoll()
{
  AsconProtocol *prot = NULL;

  prot = calloc(sizeof(AsconProtocol), 1);
  prot->name = strdup("pmac");
  prot->init = PMACInit;
  prot->handler = PMACHandler;
  AsconInsertProtocol(prot);
}
