#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <time.h>
#include <sys/time.h>
#include "rs232c_def.h"
#include "asynsrv_def.h"
#include "sinq_prototypes.h"
#include "myc_err.h"
#include "tecs_serial.h"
#include "coc_logfile.h"
#include "coc_util.h"
#include "myc_str.h"
#include "myc_mem.h"
#include "uselect.h"

#define ASYNSRV_TYPE 1
#define TERMSRV_TYPE 2
#define EOT '\r'

typedef struct {
  char host[64];
  int type; /* = ASYNSRV_TYPE */
  struct AsynSrv__info    asyn_info; /* Contains skt, host, port & chan */
  struct RS__MsgStruct    to_host;
  struct RS__RespStruct   from_host;
} AsynSrvChan;

typedef struct {
  char host[64];
  int type; /* = TERMSRV_TYPE */
  char res[SER_BUF_LEN];
  int fd, tmo;
  int (*idleHdl)(int,int);
} TermSrvChan;

void SerA_error(void) {
  char *a_txt;
  int a_cod, a_my, a_vms;

  AsynSrv_ErrInfo(&a_txt, &a_cod, &a_my, &a_vms);
  ErrMsg("asynsrv error"); ErrTxt(a_txt,0);
}

int SerWait(int tmo_msec, int fd) {
  struct timeval tmo;
  fd_set mask;
  int i;
  
  FD_ZERO(&mask);
  FD_SET(fd, &mask);
  
  tmo.tv_sec=tmo_msec / 1000;
  tmo.tv_usec=(tmo_msec % 1000) % 1000 +1;
  ERR_SI(i=uselect(fd+1,&mask,NULL,NULL,&tmo));
    
  if (i==0) return(0); /* timeout */
  return(1);
  OnError: return(-1);
}

SerChannel *SerOpen(const char *hostPort, int msecTmo, int (*idleHdl)(int,int)) {
  AsynSrvChan *aser;
  TermSrvChan *tser;
  char hbuf[64], cport[16], host[32];
  char *p, *c;
  struct sockaddr_in sadr;
  int iret;
  time_t t1, t2;
  static int ecnt;

  time(&t1);
  str_copy(hbuf, hostPort);
  p=str_split(host, hbuf, ':');
  ERR_P(p);
  c=str_split(cport, p, '/');
  if (msecTmo==0) msecTmo=5000;
  if (c!=NULL) {
    NEW(aser, AsynSrvChan); aser->type=ASYNSRV_TYPE;
    str_copy(aser->asyn_info.host, host);
    aser->asyn_info.port=atoi(cport);
    aser->asyn_info.chan=atoi(c);
    
    iret=AsynSrv_Open(&aser->asyn_info);
    if (iret==1) {
      iret=AsynSrv_Config(&aser->asyn_info, "msecTmo", msecTmo, "idleHdl", idleHdl, NULL);
    }
    if (iret!=1) { SerA_error(); goto OnError; }
    time(&t2);
    ecnt=0;
    logfileOut(LOG_MAIN, "connection to %s:%d/%d opened (%d sec)\n",
       aser->asyn_info.host, aser->asyn_info.port, aser->asyn_info.chan,
       t2-t1);
    str_copy(aser->host, hostPort);
    return((SerChannel *)aser);
  } else {
    NEW(tser, TermSrvChan); tser->type=TERMSRV_TYPE;
    tser->tmo=msecTmo;
    if (idleHdl==NULL) {
      tser->idleHdl=SerWait;
    } else {
      tser->idleHdl=idleHdl;
    }
    ERR_I(CocCreateSockAdr(&sadr, host, atoi(p)));
    ERR_SI(tser->fd=socket(AF_INET, SOCK_STREAM, 0));
    ERR_SI(connect(tser->fd, (struct sockaddr *)&sadr, sizeof(sadr)));
    time(&t2);
    ecnt=0;
    logfileOut(LOG_MAIN, "connected to %s (%d sec)\n", hostPort, t2-t1);
    str_copy(tser->host, hostPort);
    return ((SerChannel *)tser);
  }
  OnError:
    time(&t2);
    if (ecnt<4) {
      logfileOut(LOG_MAIN, "failed after %d sec\n", t2-t1);
      ecnt++;
    }
    return(NULL);
}

int SerSetTmo(SerChannel *serch, int msecTmo) {
  AsynSrvChan *aser;
  TermSrvChan *tser;
  int iret;

  if (serch->type==ASYNSRV_TYPE) {
    aser=(AsynSrvChan *)serch;
    iret=AsynSrv_Config(&aser->asyn_info, "msecTmo", msecTmo, NULL);
    if (iret!=1) { SerA_error(); goto OnError; }
  } else if (serch->type==TERMSRV_TYPE) {
    tser=(TermSrvChan *)serch;
    tser->tmo=msecTmo;
  }
  return 0;
  OnError: return -1;
}

void SerClose(SerChannel *serch) {
  AsynSrvChan *aser;
  TermSrvChan *tser;

  if (serch->type==ASYNSRV_TYPE) {
    aser=(AsynSrvChan *)serch;
    AsynSrv_Close(&aser->asyn_info, 1);
  } else if (serch->type==TERMSRV_TYPE) {
    tser=(TermSrvChan *)serch;
    close(tser->fd);
  }    
  serch->type=0;
}

SerChannel *SerCheck(SerChannel *serch) {
  if (serch==NULL || serch->type==0) {
    return(NULL);
  } else {
    return(serch);
  }
}

char *SerCmd(SerChannel *serch, char *cmnd) {
  int l, n;
  AsynSrvChan *aser;
  TermSrvChan *tser;
  char *result, *pos;
  char junk[256];
  int iret, incomplete;

  l=strlen(cmnd);
  if (l>=SER_BUF_LEN-1) ERR_COD(ENOBUFS);
  cmnd[l]=EOT; l++;
  cmnd[l]='\0';

  logfileOut(LOG_SER, ">%s\n", cmnd);
  if (serch->type==ASYNSRV_TYPE) {
    aser=(AsynSrvChan *)serch;
    iret=AsynSrv_SendCmnds(&aser->asyn_info, &aser->to_host, &aser->from_host, cmnd, NULL);
    if (iret!=1) {
      SerA_error();
      SerClose(serch);
      return(NULL);
    }
    result=AsynSrv_GetReply(&aser->asyn_info, &aser->from_host, NULL);
    if (result==NULL) ERR_MSG("empty result");
  } else if (serch->type==TERMSRV_TYPE) {
    tser=(TermSrvChan *)serch;
    ERR_I(iret=SerWait(0, tser->fd));
    while (iret>0) { /* skip contents in type-ahead buffer */
      ERR_SI(l=recv(tser->fd, junk, sizeof(junk)-1, 0));
      if (l==0) {
        ErrMsg("disconnected");
        SerClose(serch);
        goto OnError;
      }
      junk[l]='\0';
      logfileOut(LOG_MAIN, "skipped: '%s'\n", junk);
      ERR_I(iret=SerWait(0, tser->fd));
    }
    iret=send(tser->fd, cmnd, l, 0);
    if (iret<0) {
      ErrMsg("disconnected");
      SerClose(serch);
      goto OnError;
    }
    n=sizeof(tser->res);
    pos=tser->res;
    result=NULL;
    incomplete=1;
    while (incomplete) {
      /* Wait for an event on tser->fd or a timeout tmo */
      iret=tser->idleHdl(tser->tmo, tser->fd);
      if (iret==0) {
        if (result==NULL) {
          result="?TMO";
          return result;
        }
        /* if (result==NULL) ERR_MSG("timeout"); */
        ERR_MSG("missing terminator");
      }
      ERR_SI(l=recv(tser->fd, pos, n, 0));
      if (l==0) {
        ErrMsg("disconnected");
        SerClose(serch);
        goto OnError;
      }
      n -= l;
      if (n==0) ERR_MSG("answer too long");
      while (l>0) {
        if (*pos>' ') {
          if (result==NULL) result=pos; /* skip trailing white space */
        } else if (*pos<' ') { /* all control characters are treated as terminators */
          if (result!=NULL) {
            *pos='\0';
            incomplete=0;
          }
        }
        pos++; l--;
      }
    }    
  } else {
    serch->type=0;
    ERR_MSG("invalid channel");
  }
  logfileOut(LOG_SER, "<%s\n", result);

  return(result);
  OnError:
    return(NULL);
}

/* obsolete ?
char *SerCmdC(SerChannel *serch, const char *cmnd) {
  char cmd[SER_BUF_LEN];
  int l;

  l=strlen(cmnd);
  if (l>=SER_BUF_LEN-1) ERR_COD(ENOBUFS);
  strcpy(cmd, cmnd);
  return(SerCmd(serch, cmd));
  OnError: return(NULL);
}
*/

