/*-----------------------------------------------------------------------
remob.c

implements remote driveable objects living on an other sics server

M. Zolliker July 04

------------------------------------------------------------------------*/
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include <tcl.h>
#include "fortify.h"
#include "sics.h"
#include "devexec.h"
#include "remob.h"
#include "splitter.h"
#include "status.h"
#include "servlog.h"
#include "site.h"
/*-------------------------------------------------------------------------*/
#define INTERRUPTMODE 0
#define ACCESSCODE 1

/*------------------------------------------------------------------------ */
typedef struct Remob Remob;

typedef struct RemServer {
  pObjectDescriptor desc;                          
  char *name;
  char *host;
  int port;
  char *user;
  char *pass;
  mkChannel *chan;
  int incomplete;
  char line[256];
  Remob *objList;
  int matchMap;
  int timeout;
  int taskActive;
  SCStore conn;
} RemServer;

struct Remob {
  pObjectDescriptor desc;                          
  char *name;
  ObPar *ParArray;
  pIDrivable pDrivInt;
  pICallBack pCall;
  RemServer *server;
  int status;
  Remob *next;
};

typedef struct {
  float fVal;
  char *pName;
} RemobCallback;

typedef struct {
  char *pName;
  SConnection *pCon;
} RemobInfo;

/*-------------------------------------------------------------------------*/
static char *StartsWith(char *line, char *name) {
  /* if line does not start with name, return NULL
     else return a pointer to the next non-white space character
  */
  char *str;
  int l;
  
  l = strlen(name);
  if (0 != strncmp(line, name, l)) return NULL;
  str = line + l;
  while (*str == ' ') {
    str++;
  }
  return str;
}
/*-------------------------------------------------------------------------*/
static int RemWrite(RemServer *remserver, char *line) {  
  if (remserver->chan) {
    /* printf("> %s\n", line); */
    return NETWrite(remserver->chan, line, strlen(line));
  } else {
    return -1;
  }  
}
/*-------------------------------------------------------------------------*/
static int RemRead(RemServer *remserver, long tmo) {
  int iRet;
  
  if (remserver->chan == NULL) return 0; /* no data */
  iRet = NETReadTillTermNew(remserver->chan, tmo, "\n",
    remserver->line+remserver->incomplete, sizeof(remserver->line)-remserver->incomplete);
  if (iRet == 0) {
    remserver->incomplete = strlen(remserver->line); /* number of chars already received */
    return 0; /* timeout */
  } else {
    remserver->incomplete=0;
  }
  return iRet;
}
/*-------------------------------------------------------------------------*/
static int RemHandle(RemServer *remserver) {
  char *line, *par, *str;
  Remob *remob;
  
  /* skip whitespace at the beginning */
  line=remserver->line;
  while (*line < ' ' && *line != '\0') {
    line++;
  }
  memmove(remserver->line, line, strlen(line));
  
  /* handle drivstat messages */
  line = remserver->line;
  for (remob = remserver->objList; remob != NULL; remob = remob->next) {
    par=StartsWith(line, remob->name);
    if (par != NULL) {
      if ((str = StartsWith(par, "finished"))) {
        if (*str == '\0') {
          remob->status = HWIdle;
        } else {
          remob->status = HWFault;
        }
        line[0]='\0';
        return 1;
      }
      if ((str = StartsWith(par, "started"))) {
        remob->status = HWBusy;
        line[0]='\0';
        return 1;
      }
    }
  }
  return 0;
}
/*-------------------------------------------------------------------------*/
static void RemCopy(RemServer *remserver, SConnection *pCon) {
  char buf[256];
  if (pCon != NULL && remserver->line[0] != '\0') {
    snprintf(buf, sizeof(buf), "  %s", remserver->line);
    /* snprintf(buf, sizeof(buf), "%s (%s)", remserver->line, remserver->name); */
    SCWrite(pCon, buf, eStatus);
  }
}
/*-------------------------------------------------------------------------*/
static int RemServerTask(void *data) {
  RemServer *remserver=data;
  int iRet;
  SConnection *pCon;
  
  if (!remserver->taskActive) return 0; /* remove task */
  if (RemRead(remserver, 0) <= 0) return 1; /* continue */

  /* printf("< %s\n", buf); */

  if (RemHandle(remserver)) { /* handle drivstat messages */
    return 1;
  }

  /* forward oll other messages */
  pCon = SCLoad(&remserver->conn);
  if (pCon) {
    RemCopy(remserver, pCon);
  }
  return 1;
}
/*-------------------------------------------------------------------------*/
static void RemDisconnect(RemServer *remserver) {
  if (remserver->chan != NULL) {
    NETClosePort(remserver->chan);
    free(remserver->chan);
    remserver->chan=NULL;
    /* printf("disconnected\n"); */
  }
}
/*-------------------------------------------------------------------------*/
static void RemConnect(RemServer *remserver) {
  int iRet;
  char buf[256];
  
  if (!remserver->chan) {
    remserver->timeout = 0;
    remserver->chan = NETConnect(remserver->host, remserver->port);
    if (!remserver->chan) {
      return;
    }
    snprintf(buf, sizeof(buf), "%s %s\ntransact listexe interest\n"
      , remserver->user, remserver->pass);
    iRet = RemWrite(remserver, buf);
    if (iRet < 0) goto close;
    iRet = RemRead(remserver, 1000);
    while (iRet > 0) { /* eat login response */
      if (StartsWith(remserver->line, "TRANSACTIONFINISHED")) {
        /* printf("connected\n"); */
        return;
      }
      iRet = RemRead(remserver, 1000);
    }
    goto close;
  }
  return;
close:
  RemDisconnect(remserver);
  return;
}
/*-------------------------------------------------------------------------*/
static int RemTransact(RemServer *remserver, SConnection *pCon, char *cmd, ...) {
  /* the variable arguments are for filtering:
  
     "<name", &val  get float value named m
     "!blabla"      skip lines starting with blabla
     ">"            write untreated lines to pCon
  */
  char buf[256];
  int iRet;
  int i, typ;
  char *arg, *val, *endp;
  float *f;
  va_list ap;
  int try;
  int argMask;
  
  try=2;
  if (remserver->timeout) { /* eat old responses */
    while (RemRead(remserver, 0) > 0) {
      RemHandle(remserver);
    }
  }
tryagain:
  strcpy(buf, "transact ");
  strcat(buf, cmd);
  strcat(buf,"\n");
  RemConnect(remserver);
  iRet = RemWrite(remserver, buf);
  if (iRet < 0) goto close;
  
  iRet = RemRead(remserver, 2000);
  if (iRet <= 0) goto close;
  while (!StartsWith(remserver->line, "TRANSACTIONFINISHED")) {
    RemHandle(remserver);
    va_start(ap, cmd);
    arg = va_arg(ap, char *);
    argMask=1;
    remserver->matchMap = 0;
    while (arg != NULL) {
      if (*arg == '>') {
        RemCopy(remserver, pCon);
      } else if (*arg == '<') {
        f = va_arg(ap, float *);
        val = StartsWith(remserver->line, arg+1);
        if (val != NULL) {
          val = StartsWith(val, "=");
          if (val != NULL) {
            *f = strtod(val, &endp);
            break;
          }          
        }
      } else if (*arg == '!') {
        if (StartsWith(remserver->line, arg+1)) {
          remserver->matchMap |= argMask;
          argMask = argMask*2;
          break;
        }
      } else {
        printf("unknown argument in RemTransact: %s\n", arg);
        assert(0);
      }
      arg = va_arg(ap, char *);
    }
    va_end(ap);
    iRet = RemRead(remserver, 2000);
    if (iRet <= 0) goto close;
  }
  return 1;
close:
  if (iRet == 0) {
    snprintf(buf, sizeof(buf), "ERROR: timeout on %s", remserver->name);
    SCWrite(pCon,buf,eError);
    remserver->timeout = 1;
    return iRet;
  }
  snprintf(buf, sizeof(buf), "ERROR: no connection to %s", remserver->name);
  SCWrite(pCon,buf,eError);
  RemDisconnect(remserver);
  try--;
  if (try>0) goto tryagain;
  return iRet;
}
/*-------------------------------------------------------------------------*/
static void *RemobGetInterface(void *pData, int iID) {
  Remob *self = pData;
  
  assert(self);
  if(iID == DRIVEID) {
    return self->pDrivInt;
  } else if(iID == CALLBACKINTERFACE) {
    return self->pCall;
  }
  return NULL;
}
/*------------------------------------------------------------------------*/
static int RemobHalt(void *self) {
  Remob *remob=self;
  RemServer *remserver;
  char buf[64];
   
  assert(remob);
  remserver = remob->server;
  RemConnect(remserver);
  snprintf(buf, sizeof(buf), "stopexe %s\n", remob->name);
  return RemWrite(remserver, buf);
}

/*--------------------------------------------------------------------------*/
static int RemobLimits(void *self, float fVal, char *error, int iErrLen) {
  float fHard;
  Remob *remob=self;
   
  assert(remob);
  /* check is done one remote server */
  return 1;
}
/*---------------------------------------------------------------------------*/
static float RemobGetValue(void *pData, SConnection *pCon) {
  Remob *remob=pData;
  char buf[80];
  float none, value;
  int iRet;
  
  assert(remob);
  
  SCSave(&remob->server->conn, pCon);
  none = -1.25e6;
  value= none;
  snprintf(buf, sizeof(buf), "<%s", remob->name);
  iRet = RemTransact(remob->server, pCon, remob->name, buf, &value, ">", NULL);
  if (iRet <= 0) {
    return 0.0;
  }
  if (value != none) {
    return value;
  }
  snprintf(buf, sizeof(buf), "can not get %s", remob->name);
  SCWrite(pCon, buf, eWarning);
  return 0.0;
}  
/*------------------------------------------------------------------------*/
static int RemobSaveStatus(void *pData, char *name, FILE *fd) {
  Remob *self = pData;
  char buf[512];
 
  assert(self);
  assert(fd);
  
  /*
    data is stored on remote server
  */
  return 1;
}
/*------------------------------------------------------------------------*/
static int RemobStatus(void *pData, SConnection *pCon) {
  Remob *remob=pData;
 
  assert(remob);
  
  SCSave(&remob->server->conn, pCon);
  return remob->status;
}
/*---------------------------------------------------------------------------*/
static long RemobRun(void *self, SConnection *pCon, float fNew) {
  Remob *remob=self;
  float fHard;
  int i, iRet, iCode;
  char buf[512], sBuf[64];
  char pError[132];
  Remob *p;
  RemServer *remserver;
  long lTime;
  float fDelta;
  
  remserver = remob->server;
  SCSave(&remserver->conn, pCon);
  assert(remob);
  assert(pCon);
    
  /* check if I'am allowed to move this motor */
  if (!SCMatchRights(pCon,(int)ObVal(remob->ParArray,ACCESSCODE))) {
    snprintf(buf,sizeof(buf), "ERROR: You are not authorised to run %s",
               remob->name);
    SCWrite(pCon,buf,eError);
    SCSetInterrupt(pCon,eAbortBatch);
    return 0;
  }
  
  remob->status = HWIdle;
  snprintf(buf, sizeof(buf), "run %s %f", remob->name, fNew);
  iRet = RemTransact(remserver, pCon, buf, "!ERROR: somebody else", "!ERROR: cannot", ">", NULL);
  if (iRet <= 0) return 0;

  if (remserver->matchMap & 1) { /* already running, stop first */
    remob->status = HWBusy;
    snprintf(sBuf, sizeof(sBuf), "stopexe %s", remob->name);
    iRet = RemTransact(remserver, pCon, sBuf, ">", NULL);
    if (iRet <= 0) return 0;
    while (remob->status == HWBusy) {
      iRet = RemRead(remserver, 1000);
      if (iRet <= 0) break;
      RemCopy(remserver, pCon);
    }
    iRet = RemTransact(remserver, pCon, buf, ">", NULL);
    if (iRet <= 0) return 0;
  }
  if (remob->status != HWBusy) {
    return 0;
  }
  return OKOK;
}
/*-----------------------------------------------------------------------*/
static void KillInfo(void *pData) {
  RemobInfo *self = pData;
    
  assert(self);
  if (self->pName) {
    free(self->pName);
  }
  free(self);
}
/*------------------- The CallBack function for interest ------------------*/
static int InterestCallback(int iEvent, void *pEvent, void *pUser) {
  RemobCallback *psCall = pEvent;
  RemobInfo *pInfo = pUser;
  char buf[80];
     
  assert(psCall);
  assert(pInfo);
  
  snprintf(buf, sizeof(buf),"%s.position = %f ", pInfo->pName, psCall->fVal);
  SCWrite(pInfo->pCon,buf,eValue);
  return 1;
}  
/*---------------------------------------------------------------------------*/
int RemobAction(SConnection *pCon, SicsInterp *pSics, void *pData,
                int argc, char *argv[]) {
  Remob *remob = pData;
  char buf[512];
  TokenList *pList = NULL;
  TokenList *pCurrent;
  TokenList *pName; 
  int iRet;
  int i;
  int pos;
  float fValue;
  long lID;
  char *endp;
  ObPar *par;
  char acce[128], inte[128];
  
  assert(pCon);
  assert(pSics);
  assert(remob);
  
  argtolower(argc,argv);

  SCSave(&remob->server->conn, pCon);
  if (argc == 1) {
    iRet = RemTransact(remob->server, pCon, argv[0], ">", NULL);
  } else if (strcmp(argv[1],"list") == 0) {
    snprintf(buf, sizeof(buf), "%s list", remob->name);
    snprintf(acce, sizeof(acce), "!%s.accesscode", remob->name);
    snprintf(inte, sizeof(inte), "!%s.interruptmode", remob->name);
    
    RemTransact(remob->server, pCon, buf, acce, inte, ">", NULL);
    
    snprintf(buf, sizeof(buf), "%s = %.0f", acce+1, ObVal(remob->ParArray,ACCESSCODE));
    SCWrite(pCon, buf, eStatus);
    snprintf(buf, sizeof(buf), "%s = %.0f", inte+1, ObVal(remob->ParArray,INTERRUPTMODE));
    SCWrite(pCon, buf, eStatus);
    iRet=1;
  } else {
    par=ObParFind(remob->ParArray, argv[1]);
    if (par != NULL) {
      if (argc == 3) {
        fValue = strtod(argv[2], &endp);
        if (endp == argv[2]) {
          snprintf(buf, sizeof(buf), "number expected instead of %s", argv[2]);
          SCWrite(pCon, buf, eError);
          return 0;
        }
        iRet = ObParSet(remob->ParArray,argv[0],argv[1],fValue,pCon);
      }
      if (iRet) {
        snprintf(buf, sizeof(buf), "%s.%s = %.0f", argv[0], argv[1], par->fVal);
        SCWrite(pCon, buf, eStatus);
      }
    } else {
      pos=snprintf(buf, sizeof(buf), "%s ", remob->name);
      for (i=1; i<argc; i++) {
        pos+=snprintf(buf+pos, sizeof(buf)-pos, "%s ", argv[i]);
      }
      iRet = RemTransact(remob->server, pCon, buf, ">", NULL);
    }
  }
  return iRet;
}
/*---------------------------------------------------------------------------*/
int RemServerAction(SConnection *pCon, SicsInterp *pSics, void *pData,
                int argc, char *argv[]) {
  RemServer *remserver = pData;
  char buf[512];
  TokenList *pList = NULL;
  TokenList *pCurrent;
  TokenList *pName; 
  int iRet;
  int i;
  int pos;
  float fValue;
  long lID;
  char *endp;
  ObPar *par;
  char acce[128], inte[128];
  
  assert(pCon);
  assert(pSics);
  assert(remserver);
  
  argtolower(argc,argv);
  
  if (argc == 1) {
    snprintf(buf, sizeof(buf), "%s = %s:%d", argv[0], remserver->host, remserver->port);
    SCWrite(pCon, buf, eStatus);
  } else {
    pos=0;
    for (i=1; i<argc; i++) {
      pos+=snprintf(buf+pos, sizeof(buf)-pos, "%s ", argv[i]);
    }
    iRet = RemTransact(remserver, pCon, buf, ">", NULL);
    return iRet;
  }
  return 1;
}
/*--------------------------------------------------------------------------*/
static void RemobKill(void *self) {
  Remob *remob = self;
  Remob *p, **last;
  
  assert(remob);
  
  /* remove from object list */
  if (remob->server) {
    last = &remob->server->objList;
    p = *last;
    while (p != remob && p !=NULL) {
      last = &p->next;
      p = p->next;
    }
    if (p != NULL) {
      *last = p->next;
    }
    remob->next = NULL;
  }
  if (remob->name) {
    free(remob->name);
  } 
  if (remob->pDrivInt) {
    free(remob->pDrivInt);
  }     
  if (remob->pCall) {
    DeleteCallBackInterface(remob->pCall);
  }
     
  /* get rid of parameter space */
  if (remob->ParArray) {
    ObParDelete(remob->ParArray);
  }
     
  /* kill Descriptor */
  DeleteDescriptor(remob->desc);
  
  free(remob);
} 
/*--------------------------------------------------------------------------*/
static void RemServerKill(void *self) {
  RemServer *remserver = self;
  
  assert(remserver);
     
  if (remserver->taskActive) {
    remserver->taskActive=0;
    /* let the tasker kill me */
    return;
  }
  remserver = (RemServer *)self;
  if (remserver->chan) {
    RemDisconnect(remserver);
  }
  DeleteDescriptor(remserver->desc);
  if (remserver->name) free(remserver->name);
  if (remserver->host) free(remserver->host);
  if (remserver->user) free(remserver->user);
  if (remserver->pass) free(remserver->pass);
  free(remserver);
}
/*-----------------------------------------------------------------------*/
static Remob *RemobInit(char *name, RemServer *remserver) {
  Remob *remob, *p;

  assert(name);
    
  /* get memory */
  remob = (Remob *)malloc(sizeof(Remob));
  if(!remob) {
    return NULL;
  }
  
  /* create and initialize parameters */
  remob->ParArray = ObParCreate(2);
  if (!remob->ParArray) {
    free(remob);
    return NULL;
  }
  ObParInit(remob->ParArray,INTERRUPTMODE,"interruptmode",0.0,usUser);
  ObParInit(remob->ParArray,ACCESSCODE,"accesscode",(float)usUser,usMugger);

  /* copy arguments */
  remob->server = remserver;
  remob->name = strdup(name);
  
  /* initialise object descriptor */    
  remob->desc = CreateDescriptor("Remob");
  if (!remob->desc) {
    ObParDelete(remob->ParArray);
    free(remob);
    return NULL;
  }
  remob->desc->GetInterface = RemobGetInterface;
  remob->desc->SaveStatus = RemobSaveStatus;
    
  /* initialise Drivable interface */
  remob->pDrivInt = CreateDrivableInterface();
  if (!remob->pDrivInt) {
    DeleteDescriptor(remob->desc);
    ObParDelete(remob->ParArray);
    free(remob);
    return NULL;
  }
  remob->pDrivInt->SetValue = RemobRun;
  remob->pDrivInt->CheckLimits = RemobLimits;
  remob->pDrivInt->CheckStatus = RemobStatus;
  remob->pDrivInt->GetValue = RemobGetValue;
  remob->pDrivInt->Halt = RemobHalt;

  /* initialise callback interface */
  remob->pCall = CreateCallBackInterface();
  if(!remob->pCall) {
    RemobKill(remob);
    return NULL;
  }
  
  /* check if not yet in object list */
  for (p = remserver->objList; p != NULL; p=p->next) {
    if (p == remob) break;
  }
  if (p == NULL) {
    remob->next = remserver->objList;
    remserver->objList = remob;
  }

  if (!remserver->taskActive) {
    remserver->taskActive = 1;
    TaskRegister(pServ->pTasker, RemServerTask, NULL, RemServerKill, remserver, 1);
  }
  /* done */
  return remob;
}
/*-----------------------------------------------------------------------*/
static RemServer *RemServerInit(char *name, char *host, int port, char *user, char *pass) {
  RemServer *remserver = NULL;

  assert(name);
    
  remserver = malloc(sizeof(RemServer));
  if (!remserver) {
    return 0;
  }
  
  /* initialise object descriptor */    
  remserver->desc = CreateDescriptor("RemServer");
  if (!remserver->desc) {
    free(remserver);
    return NULL;
  }
  remserver->taskActive = 0;
  remserver->name = strdup(name);
  remserver->host = strdup(host);
  remserver->port = port;
  remserver->user = strdup(user);
  remserver->pass = strdup(pass);
  remserver->incomplete = 0;

  if (!remserver->name ||
      !remserver->host ||
      !remserver->port ||
      !remserver->pass) {
    /* no success, clean up */
    RemServerKill(remserver);
    return NULL;
  }
  return remserver;
}
/*--------------------------------------------------------------------------
   The Factory function for creating a remote driveable object.
   
   Usage:
      Remob server serverName host port user pass
      Remob new remobName serverName
      Remob del remobName (not yet implemented)
      Remob del serverName (not yet implemented)
*/

int RemobCreate(SConnection *pCon, SicsInterp *pSics, void *pData,
                        int argc, char *argv[]) {
  RemServer *remserver = NULL;
  Remob *remob = NULL;
  char buf[512];
  int iD, iRet;
  Tcl_Interp *pTcl = (Tcl_Interp *)pSics->pTcl;

  assert(pCon);
  assert(pSics);
  
  if (argc <= 5) {
    argtolower(argc,argv);
  } else {
    argtolower(5,argv);
  }
  if (argc == 7 && strcmp(argv[1], "server") == 0) {
    remserver = RemServerInit(argv[2], argv[3], atoi(argv[4]), argv[5], argv[6]);
    if (!remserver) {
      snprintf(buf, sizeof(buf), "Failure to create remote server connection %s", argv[2]);
      SCWrite(pCon, buf, eError);
      return 0;
    }
    /* create the interpreter command */
    iRet = AddCommand(pSics,argv[2],RemServerAction,RemServerKill,remserver);
    if (!iRet) {
      snprintf(buf, sizeof(buf),"ERROR: duplicate command %s not created",argv[1]);
      SCWrite(pCon,buf,eError);
      return 0;
    }
    
    return 1;
  } else if (argc == 4 && strcmp(argv[1], "new") == 0) {  
    remserver = FindCommandData(pServ->pSics, argv[3], "RemServer");
    if (!remserver) {
      snprintf(buf, sizeof(buf), "remote server %s not found", argv[3]);
      SCWrite(pCon, buf, eError);
      return 0;
    }
    remob = RemobInit(argv[2], remserver);
    if (!remob) {
      snprintf(buf, sizeof(buf), "Failure to create remote driveable object %s", argv[1]);
      SCWrite(pCon, buf, eError);
      return 0;
    }
    /* create the interpreter command */
    iRet = AddCommand(pSics,argv[2],RemobAction,RemobKill,remob);
    if (!iRet) {
      snprintf(buf, sizeof(buf),"ERROR: duplicate command %s not created",argv[1]);
      SCWrite(pCon,buf,eError);
      return 0;
    }
    return 1;
  }
  snprintf(buf, sizeof(buf),"ERROR: illegal arguments for command remob");
  SCWrite(pCon,buf,eError);
  return 0;
}



