#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include "err_handling.h"
#include "coc_client.h"
#include "str_util.h"

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

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

int CocConnect(CocConn *conn) {
  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) {
    if (errno==ECONNREFUSED) return(1);
    ERR_COD(errno);
  }
  return(0);
  OnError: return(-1);
}

int CocOpen(CocConn *conn)
{
  int i, try, tmo, iret=-1;

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

  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(iret);
}

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

int CocInitClient(CocConn *conn, char *host, int port, char *magic, int bufsize, char *startcmd) {
  assert(conn!=NULL);
  if (bufsize==0) bufsize=1024;
  ERR_P(conn->cmdbuf=str_create_buf(bufsize, '\0'));
  ERR_P(conn->resbuf=str_create_buf(bufsize, '\0'));
  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;
  conn->varList=NULL;
  CocVarList(&conn->varList);
  ERR_I(CocOpen(conn));
  ERR_I(CocSendMagic(conn, conn->magic));
  return(0);
  OnError: return(-1);
}

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

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

  if (magic[0]!='#') ERR_MSG("magic must start with '#'");
  str_put_start(conn->resbuf); /* use return buffer for command in order to preserve command buffer */
  ERR_I(str_put_str(conn->resbuf, magic));
  ERR_SI(send(conn->fd, conn->resbuf->buf, conn->resbuf->wrpos, 0));
  ERR_I(CocRecv(conn->fd, conn->resbuf));
  ERR_P(err=str_get_str(conn->resbuf, NULL));
  if (*err!='\0') { ErrMsg(err); ErrTxt(": (response from server)",0); goto OnError; }
  ERR_I(str_get_end(conn->resbuf));
  return(0);
  OnError: return(-1);
}

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

int CocCheck(CocConn *conn) {
  if (conn->fd<0) return(1);
  ERR_SI(send(conn->fd, "quit", 5, 0));
  ERR_I(CocRecv(conn->fd, conn->resbuf));
  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=CocOpen(conn));
    iret=-1;
    ERR_I(CocSendMagic(conn, conn->magic));
  }
  ERR_SI(send(conn->fd, conn->cmdbuf->buf, conn->cmdbuf->wrpos, 0));
  ERR_I(CocRecv(conn->fd, conn->resbuf));
  return(0);
  OnError:
    if (ErrCode==ECONNRESET || ErrCode==EPIPE) return(-2);
    return(iret);
}

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

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

  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;
    close(conn->fd);
    conn->fd=-1;
    if (iret==-1) goto OnError; /* severe error */
    ErrShort(ErrMessage);
    ErrShort("try to reconnect");
  }
  ERR_P(err=str_get_str(conn->resbuf, NULL));
  if (*err!='\0') { ErrMsg(err); ErrTxt(": (response from server)",0 ); return(-2); }
  return(0);
  OnError: return(-1);
}

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

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

  assert(conn!=NULL);
  str_put_start(conn->cmdbuf);
  ERR_I(str_put_str(conn->cmdbuf, "["));
  ERR_I(str_put_str(conn->cmdbuf, name));
  ERR_I(str_put_str(conn->cmdbuf, value));
  ERR_I(str_put_str(conn->cmdbuf, "]"));

  ERR_I(iret=CocCmdWithRetry(conn));

  ERR_I(str_get_end(conn->resbuf));
  return(0);

  OnError: return(iret);
}

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

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

  assert(conn!=NULL);
  str_put_start(conn->cmdbuf);
  ERR_I(str_put_str(conn->cmdbuf, name));

  ERR_I(iret=CocCmdWithRetry(conn));

  ERR_P(str_nget_str(conn->resbuf, value, reslen));
  ERR_I(str_get_end(conn->resbuf));
  return(0);

  OnError: return(iret);
}

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

int CocCmd(CocConn *conn, const char *rwList)
{ int setmode, i, iret=-1;
  const char *t, *s;
  char nam[32];
  CocVar *var;
  void *adr;

  assert(conn!=NULL);
  str_put_start(conn->cmdbuf);
  s=rwList;
  setmode=0;
  do {
    t=strchr(s, ',');
    if (t==NULL) t=s+strlen(s);
    if (*s=='[') {
      if (!setmode) ERR_I(str_put_str(conn->cmdbuf, "["));
      s++;
      setmode=1;
    }
    i=t-s;
    assert(i<32);
    str_ncpy(nam, s, i+1);
    if (setmode) {
      if (nam[i-1]==']') { i--; nam[i]='\0'; setmode=0; }
      ERR_I(str_put_str(conn->cmdbuf, nam));
      ERR_I(CocPutVar(conn->varList, conn->cmdbuf, nam, 0));
      if (!setmode) ERR_I(str_put_str(conn->cmdbuf, "]"));
    } else {
      var=CocFindVar(conn->varList, nam, NULL);
      if (var==NULL) ERR_MSG("variable not found");
      ERR_I(str_put_str(conn->cmdbuf, nam));
    }
    s=t+1;
  } while (*t!='\0');

  ERR_I(iret=CocCmdWithRetry(conn));

  /* read values */
  s=rwList;
  setmode=0;
  do {
    t=strchr(s, ',');
    if (t==NULL) t=s+strlen(s);
    if (*s=='[') {
      s++;
      setmode=1;
    }
    i=t-s;
    if (setmode) {
      if (*(t-1)==']') setmode=0;
    } else {
      str_ncpy(nam, s, i+1);
      ERR_I(CocGetVar(conn->varList, conn->resbuf, nam, 0));
    }
    s=t+1;
  } while (*t!='\0');
  ERR_I(str_get_end(conn->resbuf));
  return(0);

  OnError: return(iret);
}

void CocCloseClient(CocConn *conn) {
  assert(conn!=NULL);
  close(conn->fd);
  str_free_buf(conn->cmdbuf);
  str_free_buf(conn->resbuf);
  CocFreeVarList(&conn->varList);
}
