/**
 * This is a protocol handler for the Munich sputter system. The protocol 
 * is described in a separate manual. Messages to send look like this:
 *
 *   2 character length of message + command=value or ? + checksum
 *
 * Responses come like this:
 *
 * 2 characters length + status = transmission code + checksum
 * 2 character length + 2 characters command status + command=value + checksum
 *
 * An additional feauture is that the Labview server closes the connection after
 * each communication.........
 *
 * I am lazy: rather then creating a private data structure I use the line count 
 * to keep track of the 2 messages expected as reply. The linecount also signals 
 * reconnect with a value of -10
 *
 * Mark Koennecke, October 2013
 */
#include <errno.h>
#include <ascon.h>
#include <ascon.i>

static int mustReadMore(Ascon *a)
{
  int len;
  char lenString[3], *cmd;

  if(GetDynStringLength(a->rdBuffer) < 2){
    return 1;
  }
  cmd = GetCharArray(a->rdBuffer);
  memset(lenString,0,sizeof(lenString));
  lenString[0] = cmd[0];
  lenString[1] = cmd[1];
  len = atoi(lenString);
  if(GetDynStringLength(a->rdBuffer) < len + 2){ /* length + checksum */
    return 1;
  }
  return 0;
}
/*-------------------------------------------------------------------------------*/
static void processStatusLine(Ascon *a)
{
  char *cmd, *pPtr;
  int status;

  cmd = GetCharArray(a->rdBuffer);
  if(strstr(cmd,"status") == NULL){
    AsconError(a,"wrong reply",0);
    return;
  }
  pPtr = strchr(cmd,'=');
  pPtr++;
  pPtr[3] = '\0';
  status = atoi(pPtr);
  if(status != 10) {
    AsconError(a,"bad transmission status",status);
  }
  DynStringClear(a->rdBuffer);
  a->lineCount++;
}
/*-------------------------------------------------------------------------------*/
static void processPayLoad(Ascon *a)
{
  char *cmd, code[3];
  int status, len;

  cmd = strdup(GetCharArray(a->rdBuffer));
  memset(code,0,sizeof(code));
  code[0] = cmd[2];
  code[1] = cmd[3];
  status = atoi(code);
  switch(status){
  case 30:
    len = strlen(cmd);
    cmd[len-2] = '\0';
    DynStringClear(a->rdBuffer);
    DynStringConcat(a->rdBuffer,cmd+4);
    break;
  case 35:
    DynStringClear(a->rdBuffer);
    DynStringConcat(a->rdBuffer,"ERROR: Command unknown");
    break;
  case 36:
    DynStringClear(a->rdBuffer);
    DynStringConcat(a->rdBuffer,"ERROR: Syntax error");
    break;
  case 37:
    DynStringClear(a->rdBuffer);
    DynStringConcat(a->rdBuffer,"ERROR: out of range");
    break;
  }
  free(cmd);
  a->state = AsconReadDone;
  a->lineCount = -10;
}
/*-------------------------------------------------------------------------------*/
static int SputterHandler(Ascon *a)
{
  char *cmd, lenString[3], chr;
  int len, checksum, i, ret;

  switch(a->state){
  case AsconWriteStart:
    /*
      reconnect when something had been done
    */
    if(a->lineCount == -10){
      AsconReconnect(a,NULL);
      a->lineCount = 0;
      return 1;
    }  
    /*
      Prepend length and append checksum
    */
    cmd = strdup(GetCharArray(a->wrBuffer));
    len = strlen(cmd) + 2;
    DynStringClear(a->wrBuffer);
    sprintf(lenString,"%02d",len);
    DynStringConcat(a->wrBuffer,lenString);
    DynStringConcat(a->wrBuffer,cmd);
    free(cmd);
    cmd = GetCharArray(a->wrBuffer);
    for(i = 0, checksum = 0; i < strlen(cmd); i++){
      checksum += (int)cmd[i];
    }
    checksum = checksum % 256;
    sprintf(lenString,"%2X",checksum);
    if(lenString[0] == ' ') {
      lenString[0] = '0';
    }
    DynStringConcat(a->wrBuffer,lenString);
    a->state = AsconWriting;
    a->lineCount = 0;
    a->wrPos = 0;
    DynStringClear(a->rdBuffer);
    break;
  case AsconReading:
    if(mustReadMore(a)){
      ret = AsconReadChar(a->fd, &chr);
      if (ret < 0) {
	/* EINTR means we must retry */
	if (errno != EINTR && errno != EAGAIN) {
	  AsconError(a, "AsconReadChar failed:", errno);
	}
      } else if (ret > 0) {
	DynStringConcatChar(a->rdBuffer,chr);
      }
    } else {
      if(a->lineCount == 0) {
	processStatusLine(a);
      } else {
	processPayLoad(a);
      }
    } 
    break;
  default:
    return AsconStdHandler(a);
  }
  return 1;
}
/*----------------------------------------------------------------------------*/
static int SputterInit(Ascon *a, SConnection *con, int argc, char *argv[])
{
  a->hostport = strdup(argv[1]);
  a->timeout = 3*60;
  return 1;
}
/*-----------------------------------------------------------------------------*/
void AddSputterProtocoll()
{
  AsconProtocol *prot = NULL;

  prot = calloc(sizeof(AsconProtocol), 1);
  prot->name = strdup("sputter");
  prot->init = SputterInit;
  prot->handler = SputterHandler;
  AsconInsertProtocol(prot);
}
