/*---------------------------------------------------------------------
                 R S 2 3 2 C o n t r o l l e r
  
  A general object which represents a controller connected to the network
  via a terminal server. This bypasses David Maden's SerPortServer software.
  Basic facilities are provided for writinga nd reading to and from the 
  device. For more information see the rs232controller.tex file.

  copyright: see copyright.h

  Mark Koennecke, October 2001
-----------------------------------------------------------------------*/
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include "fortify.h"
#include "sics.h"
#include "splitter.h"
#include "rs232controller.h"

/*
  own error codes
*/
#define NOTCONNECTED  -2700
#define BADMEMORY     -2701
#define TIMEOUT       -2702
#define FAILEDCONNECT -2703

/*--------------------------------------------------------------------*/
void setRS232SendTerminator(prs232 self, char *term)
{
  assert(self);
  
  if(self->sendTerminator != NULL)
  {
    free(self->sendTerminator);
  }

  if(term != NULL)
  {
    self->sendTerminator = strdup(term);
  }
  else
  {
    self->sendTerminator = NULL;
  }
}
/*--------------------------------------------------------------------*/
void setRS232ReplyTerminator(prs232 self, char *term)
{
  assert(self);
  if(self->replyTerminator != NULL)
  {
    free(self->replyTerminator);
  }

  if(term != NULL)
  {
    self->replyTerminator = strdup(term);
  }
  else
  {
    self->replyTerminator = NULL;
  }
  
}
/*---------------------------------------------------------------------*/
void setRS232Timeout(prs232 self, int timeout)
{
  assert(self);
  self->timeout = timeout;
}
/*--------------------------------------------------------------------*/
int writeRS232(prs232 self, void *data, int dataLen)
{
  char *pPtr = NULL;
  int iRet;


  assert(self);

  /*
    catch an unconnected socket
  */
  if(!self->pSock)
  {
    return NOTCONNECTED;
  }
  
  /* 
     do the terminator processing, if required.
     If new data had to be allocated in order to add the terminator,
     pPtr is not NULL. I any other case it is.
  */
  if(self->sendTerminator != NULL)
  {
    pPtr = (char *)data;
    if(strstr(pPtr,self->sendTerminator) == NULL) 
    {
      /*
	the terminator is missing. add it.
      */
      pPtr = (char *)malloc((dataLen + strlen(self->sendTerminator) +2)
			    *sizeof(char));
      if(!pPtr)
      {
	return BADMEMORY;
      }
      strcpy(pPtr,(char *) data);
      strcat(pPtr,self->sendTerminator);
      dataLen += strlen(self->sendTerminator);
      data = pPtr;
    }
    else
    {
      pPtr = NULL;
    }
  }

  /*
    send 
  */
  iRet = NETWrite(self->pSock,data,dataLen);

  if(pPtr != NULL)
    free(pPtr);

  return iRet;
}
/*----------------------------------------------------------------------*/
int readRS232(prs232 self, void *data, int *dataLen)
{
  long lRead;
  int iRet;
  size_t rLength;
  
  assert(self);
  /*
    catch an unconnected socket
  */
  if(!self->pSock)
  {
    return NOTCONNECTED;
  }

  iRet = NETAvailable(self->pSock,self->timeout);
  if(iRet < 0)
  {
    return iRet;
  }  
  else if(iRet == 0)
  {
    *dataLen = 0;
    return TIMEOUT;
  }
  else
  {
    rLength = *dataLen; 
    lRead = recv(self->pSock->sockid, data,rLength,0);
    if(lRead >= 0)
    {
      *dataLen = lRead;
      return 1;
    }
    else
    {
      return (int)lRead;
    }
  }
  /*
    not reached 
  */
  return 0;
}
/*-----------------------------------------------------------------------*/
int availableRS232(prs232 self)
{
  assert(self);

  /*
    catch an unconnected socket
  */
  if(!self->pSock)
  {
    return NOTCONNECTED;
  }

  return NETAvailable(self->pSock,self->timeout);
}
/*------------------------------------------------------------------------*/
int transactRS232(prs232 self, void *send, int sendLen,
		  void *reply, int replyLen)
{
  int iRet;

  assert(self);

  /*
    catch an unconnected socket
  */
  if(!self->pSock)
  {
    return NOTCONNECTED;
  }

  /* 
   write data 
  */
  iRet = writeRS232(self,send,sendLen);
  if(iRet < 0)
  {
    return iRet;
  }

  /*
    read 
  */
  memset(reply,0,replyLen);
  iRet = NETReadTillTerm(self->pSock,self->timeout,self->replyTerminator,
			  reply, replyLen);
  if(iRet == 0)
  {
    return TIMEOUT;
  }
  else
  {
    return iRet;
  }
}
/*------------------------------------------------------------------------*/
void getRS232Error(int iCode, char *errorBuffer,
		   int errorBufferLen)
{
  /*
    the error code is either one of our errors, or an errno code from
    the system
  */
  switch(iCode)
  {
    case BADMEMORY:
      strncpy(errorBuffer,
               "Out of memory for appending terminators",
	      errorBufferLen);
      break;
    case NOTCONNECTED:
      strncpy(errorBuffer,
               "Not connected!",
	      errorBufferLen);
      break;
    case TIMEOUT:
      strncpy(errorBuffer,
               "Timeout reading data",
	      errorBufferLen);
      break;
    case FAILEDCONNECT:
      strncpy(errorBuffer,
               "Failed to connect to terminal server",
	      errorBufferLen);
      break;
    default:
      strncpy(errorBuffer,strerror(errno),
              errorBufferLen);
      break;
  }
}
/*--------------------------------------------------------------------*/
int initRS232(prs232 self)
{
  int iRet;

  assert(self);

  if(self->pSock != NULL)
  {
    NETClosePort(self->pSock);
    self->pSock = NULL;
  }
  self->pSock = NETConnect(self->pHost, self->iPort);
  if(!self->pSock)
    return FAILEDCONNECT;
  else
    return 1;
}
/*-------------------------------------------------------------------*/
static void KillRS232(void *pData)
{
  prs232 self = (prs232)pData;
  if(!self)
  {
     return;
  }

  if(self->pDes)
  {
    DeleteDescriptor(self->pDes);
  }
  if(self->sendTerminator != NULL)
  {
    free(self->sendTerminator);
  }
  if(self->replyTerminator != NULL)
  {
    free(self->replyTerminator);
  }
  if(self->pSock)
  {
    NETClosePort(self->pSock);
  }
  if(self->pHost)
  {
    free(self->pHost);
  }
}
/*-------------------------------------------------------------------*/
static int checkSet(SConnection *pCon, int argc, int rights)
{
  if(argc < 3)
  {
      return 0;
  }
  else
  {
    if(SCMatchRights(pCon,rights))
    {
      return 1;
    }
    else
    {
      return 0;
    }
  } 
  /*
   not reached 
  */
  return 0;
}
/*--------------------------------------------------------------------*/
static void encodeTerminator(char *result, char *terminator)
{
  int i, len;
  char pBuffer[10];
  
  if(terminator == NULL)
  {
    result[0] = '\0';
  }
  len = strlen(terminator);
  sprintf(pBuffer,"0x%x",(int)terminator[0]);
  strcpy(result,pBuffer);
  for(i = 1; i < len; i++)
  {
    sprintf(pBuffer,"0x%x",(int)terminator[i]);
    strcat(result,pBuffer);
  }
}
extern char *stptok(char *pPtr, char *pToken, int tokenLen, char *term);
/*--------------------------------------------------------------------*/
char *decodeTerminator(char *code)
{
  int count = 0, icode;
  char *pResult;
  char *pPtr, pToken[10];

  /* 
    max 10 terminators!
  */
  pResult = (char *)malloc(10*sizeof(char));
  if(!pResult)
  {
    return NULL;
  }
  memset(pResult,0,10);

  pToken[0] = '0';
  /*
    I seem to get an empty token on the first call to stptok, this is why
    I do 2 stptoks. Strange and wonderful.
  */
  pPtr = stptok(code,pToken+1,9,"0");
  pPtr = stptok(pPtr,pToken+1,9,"0");
  while(pPtr != NULL)
  {
    sscanf(pToken,"%x",&icode);
    pResult[count] = (char)icode;
    count++;
    pPtr = stptok(pPtr,pToken+1,9,"0");
  }
  return pResult;
}
/*--------------------------------------------------------------------*/
int RS232Action(SConnection *pCon, SicsInterp *pSics,
                        void *pData, int argc, char *argv[])
{
  prs232 self = NULL;
  char pError[256];
  char pBuffer[8192], pReply[8192];
  char *pPtr = NULL;
  int iRet, iRead = 8191;

  self = (prs232)pData;
  assert(self);
  assert(pCon);

  /*
    check for arguments
  */
  if(argc < 2)
  {
    sprintf(pError,"ERROR: insufficient no of arguments to %s",argv[0]);
    SCWrite(pCon,pError,eError);
    return 0;
  }

  strtolower(argv[1]);
  if(strcmp(argv[1],"sendterminator") == 0)
  {
    if(checkSet(pCon,argc,usMugger))
    {
      pPtr = decodeTerminator(argv[2]);
      setRS232SendTerminator(self,pPtr);
      if(pPtr)
	free(pPtr);
      SCSendOK(pCon);
      return 1;
    }
    else
    {
      encodeTerminator(pBuffer,self->sendTerminator);
      sprintf(pError,"%s.sendTerminator = \"%s\"",argv[0],
             pBuffer);
      SCWrite(pCon,pError,eValue);
      return 1; 
    }
  }
  else if(strcmp(argv[1],"timeout") == 0)
  {
    if(checkSet(pCon,argc,usMugger))
    {
      setRS232Timeout(self,atoi(argv[2]));
      SCSendOK(pCon);
      return 1;
    }
    else
    {
      sprintf(pError,"%s.Timeout = %d",argv[0],self->timeout);
      SCWrite(pCon,pError,eValue);
      return 1; 
    }
  }
  else if(strcmp(argv[1],"replyterminator") == 0)
  {
    if(checkSet(pCon,argc,usMugger))
    {
      pPtr = decodeTerminator(argv[2]);
      setRS232ReplyTerminator(self,pPtr);
      if(pPtr)
	free(pPtr);
      SCSendOK(pCon);
      return 1;
    }
    else
    {
      encodeTerminator(pBuffer,self->replyTerminator);
      sprintf(pError,"%s.replyTerminator = \"%s\"",argv[0],
               pBuffer);
      SCWrite(pCon,pError,eValue);
      return 1; 
    }
  }
  else if(strcmp(argv[1],"write") == 0)
  {
    Arg2Text(argc-2,argv+2,pBuffer,8191);
    iRet = writeRS232(self,pBuffer,strlen(pBuffer));
    if(iRet < 0)
    {
      getRS232Error(iRet,pError,255);
      SCWrite(pCon,pError,eError);
      return 0;
    }
    SCSendOK(pCon);
    return 1;
  }
  else if(strcmp(argv[1],"read") == 0)
  {
    if(!availableRS232(self))
    {
      SCWrite(pCon,"Nothing to read!",eError);
      return 1;
    }
    iRet = readRS232(self,pBuffer,&iRead);
    if(iRet < 0)
    {
      getRS232Error(iRet,pError,255);
      SCWrite(pCon,pError,eError);
      return 0;
    }
    SCWrite(pCon,pBuffer,eValue);
    return 1;
  }
  else if(strcmp(argv[1],"available") == 0)
  {
    iRet = availableRS232(self);
    if(iRet < 0)
    {
      getRS232Error(iRet,pError,255);
      SCWrite(pCon,pError,eError);
      return 0;
    }
    else if(iRet == 0)
    {
      SCWrite(pCon,"No data pending",eValue);
      return 1;
    }
    else 
    {
      SCWrite(pCon,"Data available",eValue);
      return 1;
    }
  }
  else if(strcmp(argv[1],"send") == 0)
  {
    Arg2Text(argc-2,argv+2,pBuffer,8191);
    iRet = transactRS232(self,pBuffer,strlen(pBuffer),
                              pReply,iRead);
    if(iRet < 0)
    {
      getRS232Error(iRet,pError,255);
      SCWrite(pCon,pError,eError);
      return 0;
    }
    SCWrite(pCon,pReply,eValue);
    return 1;
  }
  else if(strcmp(argv[1],"init") == 0)
  {
    iRet = initRS232(self);
    if(iRet != 1)
    {
      sprintf(pError,"ERROR: reinitializing connection to %s at %d failed",
	      self->pHost, self->iPort);
      SCWrite(pCon,pError,eError);
      return 0;
    }
    else
    {
      SCSendOK(pCon);
      return 1;
    }
  }
  else
  {
    sprintf(pError,"ERROR: %s does not understand %s",argv[0], argv[1]);
    SCWrite(pCon,pError,eError);
    return 0;
  }

  return 1;
}
/*-------------------------------------------------------------------*/
int RS232Factory(SConnection *pCon, SicsInterp *pSics,
                        void *pData, int argc, char *argv[])
{
  prs232 pNew = NULL;
  int iRet;
  char pError[256];

  if(argc < 4)
  {
    SCWrite(pCon,"ERROR: insufficient no of arguments to RS232Factory",
            eError);
    return 0;
  }

  /*
   create data structure and open port 
  */
  pNew = (prs232)malloc(sizeof(rs232));
  if(!pNew)
  {
    SCWrite(pCon,"ERROR: out of memory in RS232Factory",eError);
    return 0;
  }
  memset(pNew, 0, sizeof(rs232));

  pNew->pHost = strdup(argv[2]);
  pNew->iPort = atoi(argv[3]);
  pNew->sendTerminator = strdup("\r");
  pNew->replyTerminator = strdup("\n");
  pNew->timeout = 1000;
  pNew->pDes = CreateDescriptor("RS232 Controller");
  if(!pNew->pDes || !pNew->pHost || 
     !pNew->replyTerminator || !pNew->sendTerminator)
  {
    SCWrite(pCon,"ERROR: out of memory in RS232Factory",eError);
    return 0;
  }
  pNew->pSock = NETConnect(pNew->pHost, pNew->iPort);
  if(!pNew->pSock)
  {
     sprintf(pError,"ERROR: failed to connect to %s at port %d",
                    pNew->pHost, pNew->iPort);
     SCWrite(pCon,pError,eError);
  }
  
  /*
    create the command
  */
  iRet = AddCommand(pSics,argv[1],RS232Action, KillRS232, pNew);
  if(!iRet)
  {
        sprintf(pError,"ERROR: duplicate command %s not created", argv[1]);
        SCWrite(pCon,pError,eError);
        KillRS232(pNew);
        return 0;
 }
 return 1;
}


