#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
#include "myc_err.h"
#include "coc_util.h"
#include "coc_client.h"
#include "myc_str.h"

/* --- non ANSI signal --- */
#ifndef SIGPIPE
#define SIGPIPE 13
#endif

#define COC_NETTMO 5
#define COC_RESTMO 60

#define ARG_CHAR 1
#define ARG_INT 2
#define ARG_FLT 3
#define ARG_ARR 4
#define ARG_OUT 5

/*-------------------------------------------------------------------------*/

int CocConnect(CocConn *conn) { /* connect, but do not start */
  int i;
  struct sockaddr_in sadr;

  ERR_I(CocCreateSockAdr(&sadr, conn->host, conn->port));
  ERR_SI(conn->fd=socket(AF_INET, SOCK_STREAM, 0));
  i = 1;
  ERR_SI(setsockopt(conn->fd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int))); /* allow quick port reuse */
  i=connect(conn->fd, (struct sockaddr *)&sadr, sizeof(sadr));
  if (i<0) {
    close(conn->fd);
    conn->fd=-1;
    if (errno==ECONNREFUSED) return(1);
    ERR_COD(errno);
  }
  return(0);
  OnError: return(-1);
}

int CocOpen(CocConn *conn) { /* connect and start, if not succsessful */ 
  int i, try, tmo;

  ERR_I(i=CocConnect(conn));
  if (i==0) return(0);
  /* connection refused */
  if (conn->startcmd[0]=='\0') {
    CocDelay(500);
    ErrTxt("connect",1); return(1);
  }

  printf("Starting TecsServer ...\n\n%s\n", conn->startcmd);
  ERR_I(system(conn->startcmd));

  try=15;
  tmo=100; /* wait total ca. 10 sec. for max. 15 tries */
  while (try>0) {
    try--;
    CocDelay(tmo); tmo=tmo*5/4;
    ERR_I(i=CocConnect(conn));
    if (i==0) {
      printf("\n... connected to TecsServer\n");
      return(0);
    }
  }
  ERR_MSG("can not start TECS, too many retries");
  OnError: return(-1);
}

/*-------------------------------------------------------------------------*/

int CocInitClient(CocConn *conn, char *host, int port, char *magic, int bufsize, char *startcmd) {
  int iret;
  assert(conn!=NULL);
  if (bufsize==0) bufsize=1024;
  StrLink(&conn->cmdbuf, conn->cmdbuf_);
  StrLink(&conn->resbuf, conn->resbuf_);
  conn->port=port;
  ERR_I(str_copy(conn->host, host));
  ERR_I(str_copy(conn->magic, magic));
  ERR_I(str_copy(conn->startcmd, startcmd));
  conn->fd=-1;
  ERR_I(iret=CocOpen(conn));
  if (iret==0) {
    iret=CocSendMagic(conn, conn->magic);
    if (iret<0) {
      CocCloseClient(conn);
      iret=1;
    }
  }
  return(iret);
  OnError: return(-1);
}

/*-------------------------------------------------------------------------*/

int CocSendMagic(CocConn *conn, char *magic) {
  char *err;

  if (magic != conn->magic) {
    str_copy(conn->magic, magic);
  }
  StrClear(&conn->resbuf); /* use return buffer for command in order to preserve command buffer */
  ERR_I(StrPut(&conn->resbuf, "", COC_MAGIC));
  ERR_I(StrPut(&conn->resbuf, magic, COC_SEP));
  ERR_I(CocSend(conn->fd, conn->resbuf.buf, conn->resbuf.wrpos));
  ERR_I(CocRecv(conn->fd, &conn->resbuf, COC_NETTMO, NULL));
  ERR_P(err=StrGet(&conn->resbuf, NULL, COC_SEP));
  if (err[0]!=COC_MAGIC) ERR_MSG("magic synch error");
  if (err[1]!='\0') { ErrMsg(err+1); ErrTxt(": (response from server)",0); goto OnError; }
  ERR_I(StrGetEnd(&conn->resbuf));
  return(0);
  OnError: return(-1);
}

/*-------------------------------------------------------------------------*/

int CocCheck(CocConn *conn) {

  if (conn->fd<0) return(1);
  ERR_I(CocSend(conn->fd, "_quit", 6));
  ERR_I(CocRecv(conn->fd, &conn->resbuf, COC_NETTMO, NULL));
  return(0);
  OnError:
    if (ErrCode==ECONNRESET || ErrCode==EPIPE) return(1);
    return(-1);
}

/*-------------------------------------------------------------------------*/

int CocTryCmd(CocConn *conn) {
  int iret=-1;

  signal(SIGPIPE, SIG_IGN);
  if (conn->fd<0) {
    ERR_I(iret=CocConnect(conn)); /* was CocOpen earlier */
    if (iret == 1) {
      CocDelay(500);
      ErrTxt("connect",1); return (-1);
    }

    iret=-1;
    ERR_I(CocSendMagic(conn, conn->magic));
  }
  ERR_I(CocSend(conn->fd, conn->cmdbuf.buf, conn->cmdbuf.wrpos));
  ERR_I(CocRecv(conn->fd, &conn->resbuf, COC_NETTMO, NULL));
  return(0);
  OnError:
    if (ErrCode==ECONNRESET || ErrCode==EPIPE) return(-2);
    return(iret);
}

/*-------------------------------------------------------------------------*/

int CocCmdWithRetry(CocConn *conn) {
  int cnt, iret;

  if (conn==NULL) ERR_MSG("not connected");
  cnt=3;
  while (1) {
    cnt--;
    if (cnt<=0) {
      ERR_I(CocTryCmd(conn));
      break;
    }
    iret=CocTryCmd(conn);
    if (iret>=0) break;
    if (conn->fd > 0) close(conn->fd);
    conn->fd=-1;
    if (iret==-1) goto OnError; /* severe error */
    ErrShort(ErrMessage);
    ErrShort("try to reconnect");
  }
  return(0);
  OnError: return(-1);
}

/*-------------------------------------------------------------------------*/

void CocReset(CocConn *conn) { 
  assert(conn!=NULL);
  StrClear(&conn->cmdbuf);
  if (conn->synch!=COC_SYN0) {
    conn->synch=COC_SYN0;
  } else {
    conn->synch=COC_SYN1;
  }
  StrPut(&conn->cmdbuf, "", conn->synch);
  conn->nargs=0;
}

/*-------------------------------------------------------------------------*/

int CocPushArg(CocConn *conn, const char *name, void *value, int type, int size) {
  StrBuf *buf;
  int n;

  assert(conn!=NULL);
  assert(NULL==strchr(name, ' '));
  buf=&conn->cmdbuf;
  n=conn->nargs;
  if (n>=sizeof(conn->args)/sizeof(conn->args[0])) ERR_MSG("too many return arguments");
  conn->args[n].adr=value;
  conn->args[n].type=type;
  conn->args[n].size=size;
  conn->args[n].cmd=buf->buf + buf->wrpos;
  conn->nargs=n+1;
  if (value==NULL) {
    ERR_I(StrPut(buf, name, ' '));
  } else {
    ERR_I(StrPut(buf, name, COC_SEP));
  }
  return(0);
  OnError: return(-1);
}

/*-------------------------------------------------------------------------*/

int CocPutStr(CocConn *conn, const char *name, const char *value) { 

  ERR_I(CocPushArg(conn, name, NULL, ARG_CHAR, 0));
  ERR_I(StrPut(&conn->cmdbuf, value, COC_SEP));
  return(0);
  OnError: return(-1);
}

/*-------------------------------------------------------------------------*/

int CocPutFloat(CocConn *conn, const char *name, float value) {

  ERR_I(CocPushArg(conn, name, NULL, ARG_FLT, 0));
  ERR_I(StrPutFloat(&conn->cmdbuf, value, COC_SEP));
  return(0);
  OnError: return(-1);
}

/*-------------------------------------------------------------------------*/

int CocPutArray(CocConn *conn, const char *name, float *value, int value_size) {

  ERR_I(CocPushArg(conn, name, NULL, ARG_ARR, 0));
  ERR_I(StrPutArray(&conn->cmdbuf, value, value_size));
  return(0);
  OnError: return(-1);
}

/*-------------------------------------------------------------------------*/

int CocPutInt(CocConn *conn, const char *name, int value) {

  ERR_I(CocPushArg(conn, name, NULL, ARG_INT, 0));
  ERR_I(StrPutInt(&conn->cmdbuf, value, COC_SEP));
  return(0);
  OnError: return(-1);
}

/*-------------------------------------------------------------------------*/

int CocGetStr(CocConn *conn, const char *name, char *value, int value_len) { 

  return(CocPushArg(conn, name, value, ARG_CHAR, value_len));
}

/*-------------------------------------------------------------------------*/

int CocGetFloat(CocConn *conn, const char *name, float *value) {

  return(CocPushArg(conn, name, value, ARG_FLT, 0));
}

/*-------------------------------------------------------------------------*/

int CocGetInt(CocConn *conn, const char *name, int *value) {

  return(CocPushArg(conn, name, value, ARG_INT, 0));
}

/*-------------------------------------------------------------------------*/

int CocGetArray(CocConn *conn, const char *name, float *value, int value_size) {

  return(CocPushArg(conn, name, value, ARG_ARR, value_size));
}

/*-------------------------------------------------------------------------*/

int CocGetOut(CocConn *conn, const char *name, OutFunc *func) {
  return(CocPushArg(conn, name, func, ARG_OUT, 0));
}

/*-------------------------------------------------------------------------*/

int CocDoIt(CocConn *conn, char *res, int res_len) {
  StrBuf *buf;
  int i, pending;
  float *arr;
  CocArg *a;
  char *resp, *t, tag, *str;
  int iret=0;
  int siz;
  OutFunc *f;
  
  assert(conn!=NULL);
  ERR_I(CocCmdWithRetry(conn));
  buf=&conn->resbuf;
  while (buf->buf[0] != conn->synch) { /* throw away unsynchronized answers */
    ERR_I(CocRecv(conn->fd, buf, COC_NETTMO, NULL));
  }

  pending=conn->nargs;
  *res='\0';
  while (1) {
    if (buf->buf[0] != conn->synch) ERR_MSG("synch error");
    ERR_P(StrGet(buf, NULL, conn->synch));
    i=0;
    a=conn->args;
    while (pending>0 && i<conn->nargs) {
      if (a->type!=0) {
        resp=buf->buf + buf->rdpos;
        tag=*resp;
        if (tag==COC_DELAYED) { /* delayed response */
          resp++;
          ERR_P(StrGet(buf, NULL, COC_SEP));
        } else {
          pending--;
          if (tag==COC_ERR || tag==COC_TRM) { /* error response */
            str_ncat(res, a->cmd, res_len);
            resp++;
            ERR_P(StrGet(buf, NULL, COC_SEP));
            str_ncat(res, ": ", res_len);
            iret=1;
            if (tag==COC_TRM) return(1);
          } else {
            if (a->adr==NULL) {
              t=strchr(res, COC_SEP);
              str_ncat(res, a->cmd, res_len);
              t=strchr(t, ' ');
              assert(t!=NULL); /* truncate argument */
              *t=COC_SEP;
              str_ncat(res, "=", res_len);
              ERR_P(StrGet(buf, NULL, COC_SEP));
            } else {
              str_ncat(res, a->cmd, res_len);
              str_ncat(res, "=", res_len);
              if (a->type==ARG_INT) {
                ERR_I(StrGetInt(buf, (int *)a->adr, COC_SEP));
              } else if (a->type==ARG_FLT) {
                ERR_I(StrGetFloat(buf, (float *)a->adr, COC_SEP));
              } else if (a->type==ARG_ARR) {
                arr=(float *)a->adr;
                ERR_I(siz=StrGetArray(buf, arr, a->size));
                while (siz < a->size) { /* fill with zeroes */
                  arr[siz++]=0.0;
                }
                resp="<array>";
              } else if (a->type==ARG_CHAR) {
                ERR_P(StrNGet(buf, (char *)a->adr, a->size, COC_SEP));
              } else if (a->type==ARG_OUT) {
                ERR_P(str=StrNGet(buf, NULL, 0, COC_SEP));
                f=(OutFunc *)a->adr;
                f->func(str, f->arg);
              } else {
                ERR_MSG("unknown type");
              }
              a->type=0; /* done */
            }
          }
          str_ncat(res, resp, res_len);
          str_ncat(res, "\n", res_len);
        }
      }
      i++; a++;
    }
    ERR_I(StrGetEnd(buf));
    if (pending==0) return(iret); /* no more delayed response awaiting */

    ERR_I(CocRecv(conn->fd, buf, COC_RESTMO, NULL));
  }
  return(iret);
  OnError: return(-1);
}

static int interrupt, logFd=0;

void CocIntSignal(int sig) {
  char buf[4];

  if (logFd) {
    interrupt=2;
    buf[0]=COC_CLRLOG;
    buf[1]=COC_SEP; /* send "clear log" message */
    printf("\nCLRLOG\n");
    CocSend(logFd, buf, 2);
  } else {
    printf("\ninterrupt\n");
    interrupt=1;
  }
}

int CocWatchLog(CocConn *conn, char *loglist) {
  char cbuf[1024];
  StrBuf *buf;
  int fd, iret;

#if __VMS
  static int init=1;

  if (init) {
    init=0;
    DECC$CRTL_INIT();
  }
#endif
  fd=conn->fd;
  CocReset(conn);
  ERR_I(CocPutStr(conn, "loglist", loglist));
  ERR_I(CocDoIt(conn, cbuf, sizeof(cbuf)));
  buf=&conn->resbuf;
  interrupt=0;
  logFd=fd;
  signal(SIGINT, CocIntSignal);

  while (!interrupt) {
    logFd=fd;
    ERR_I(iret=CocRecv(fd, buf, 60, &logFd));
    if (iret>0) {
      if (buf->buf[0]==COC_CLRLOG) { /* confirmed "clear log" message */
        interrupt=3;
      } else { /* it's a log message */
        StrReset(buf);
        while (!StrEnd(buf)) {
          ERR_P(StrGet(buf, cbuf, COC_SEP));
          printf("%s", cbuf);
        }
      }
    }
  }
  signal(SIGINT, SIG_DFL);
  logFd=0;
  if (interrupt==1) { /* send "clear log" message */
    cbuf[0]=COC_CLRLOG;
    cbuf[1]=COC_SEP; /* send "clear log" message */
    printf("\nclrlog\n");
    CocSend(fd, cbuf, 2);
    do {
      ERR_I(CocRecv(fd, buf, 10, NULL));
    } while (buf->buf[0]!=COC_CLRLOG);
  }
  return(0);
  OnError:
    signal(SIGINT, SIG_DFL);
    return(-1);
}

int CocShowLog(CocConn *conn, char *loglist, int start, int lines) {
  char cbuf[1024];
  StrBuf *buf;
  int fd, iret;

  fd=conn->fd;
  CocReset(conn);
  if (start>0) {
    ERR_I(CocPutInt(conn, "logstart", start));
  }
  ERR_I(CocPutInt(conn, "loglines", lines));
  ERR_I(CocPutStr(conn, "logshow", loglist));
  ERR_I(CocDoIt(conn, cbuf, sizeof(cbuf)));
  buf=&conn->resbuf;

  while (1) {
    ERR_I(iret=CocRecv(fd, buf, 10, NULL));
    if (iret<=0) break;
    if (buf->buf[0]==COC_CLRLOG) { /* end of log message */
      break;
    } else { /* it's a log message */
      StrReset(buf);
      while (!StrEnd(buf)) {
        ERR_P(StrGet(buf, cbuf, COC_SEP));
        printf("%s", cbuf);
      }
    }
  }
  return(0);
  OnError:
    signal(SIGINT, SIG_DFL);
    return(-1);
}

void CocCloseClient(CocConn *conn) {
  assert(conn!=NULL);
  if (conn->fd >= 0) close(conn->fd);
  conn->fd=-1;
}


/*-------------------------------------------------------------------------*/

static char rbuf[256];

int CocSet(CocConn *conn, const char *name, const char *value) {
  int iret;

  assert(conn!=NULL);
  CocReset(conn);
  ERR_I(CocPutStr(conn, name, value));
  ERR_I(iret=CocDoIt(conn, rbuf, sizeof(rbuf)));
  if (iret) ERR_MSG(rbuf);
  return(0);
  OnError: return(-1);
}

int CocGetN(CocConn *conn, const char *name, char *value, int reslen) {
  int iret;

  assert(conn!=NULL);
  CocReset(conn);
  ERR_I(CocGetStr(conn, name, value, reslen));
  ERR_I(iret=CocDoIt(conn, rbuf, sizeof(rbuf)));
  if (iret) ERR_MSG(rbuf);
  return(0);
  OnError: return(-1);
}

int CocSetGetN(CocConn *conn, const char *name, const char *cmd, char *value, int reslen) {

  assert(conn!=NULL);
  CocReset(conn);
  ERR_I(CocPutStr(conn, name, cmd));
  ERR_I(CocDoIt(conn, value, reslen));
  return(0);
  OnError: return(-1);
}

