#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/timeb.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include "myc_mem.h"
#include "myc_err.h"
#include "coc_logfile.h"
#include "coc_util.h"
#include "coc_server.h"
#include "myc_str.h"
#include "myc_time.h"
#include "uselect.h"

#define COC_NUL COC_SEP

typedef struct _CocVar {
  struct _CocVar *next;
  char name[32];
  void *var;
  int access;
  int type;
  int size;
  void *strucType;
  int (*hdl)(int, void *, int);
  int pending;
} CocVar;

CocVar *serverVarList=NULL;
static CocVar **varListHandle=&serverVarList;
static CocVar *lastDef=NULL;

typedef struct {
  CocVar *var;
  void *base;
  int mode;
} Pend;
  
typedef struct _CocClient {
  struct _CocClient *next;
  int fd;
  int mode;
  Pend pend[16];
  int npend;
  int logmask;
  char synch;
  void *data;
} CocClient;

void *(*setData)(void *);

/*
static CocClientData **clData;
static int clDataSize;
*/

DeclStrBuf(buf, COC_CMD_LEN);
DeclStrBuf(bufo, COC_RES_LEN);

static fd_set mask, rmask;
static int maxfd;
CocClient *cList;
static int mainFd;
static int modified;
static char loglist[16];

void CocVarList(void **varList) {
  if (varList==NULL) {
    varListHandle=(CocVar **)varList;
  } else {
    varListHandle=&serverVarList;
  }
}

void CocList() {  CocVar *p;

  p=*varListHandle;
  while (p!=NULL) {
    printf("%s %d ", p->name, p->type);
    p=p->next;
  }
  printf("\n");
}

CocVar *CocFindVar1(const char *name) {
  CocVar *p;

  p=*varListHandle;
  while (p!=NULL && 0!=strcasecmp(p->name,name)) p=p->next;
  return(p);
}

void *CocFindVar(const char *name, void **adr) {
  CocVar *p, *t;
  const char *f;
  void *base;
  char nam[32];

  f=str_split(nam, name, '.');
  if (f==NULL) {
    f=str_split(nam, name, '-');
    if (f!=NULL) {
      if (f[0]!='>') {
        f=NULL;
      } else {
        f++;
      }
    }
  }
  if (f!=NULL) {
    if (adr!=NULL) *adr=NULL;
    p=CocFindVar1(nam);
    if (p==NULL) { return(NULL); }
    t=p->strucType;
    if (t==NULL) { return(NULL); }
    str_copy(nam, t->name);
    str_append(nam, ":");
    str_append(nam, f);
    if (adr!=NULL) {
      base=p->var;
      if (p->type==COC_PTR) base=*(void **)base;
      *adr=base;
    }
  } else if (adr!=NULL) {
    *adr=NULL;
  }
  p=CocFindVar1(nam);
  if (p!=NULL && p->type==COC_ALIAS) { /* recursive call for alias */
    p=CocFindVar(p->var, adr);
  }
  return(p);
}

void *CocIntPtr(int *ptr) { return(ptr); }
void *CocFltPtr(float *ptr) { return(ptr); }
void *CocChrPtr(char *ptr) { return(ptr); }

void *CocDefVar(const char *name, void *var, int type, int size, int access) {
  CocVar *p;

  assert(varListHandle!=NULL);
  p=CocFindVar1(name);
  if (p==NULL) {
    NEW(p,CocVar);
    p->next=*varListHandle;
    *varListHandle=p;
    str_copy(p->name, name);
    p->type=type;
    p->size=size;
  } else {
    assert(p->type==type);
  }
  p->var=var;
  p->access=access;
/*  printf("define %s %d\n", name, (int)var); */
  lastDef=p;
  return(p);
  OnError:
    assert(0);
}

void CocHdl(int (*handler)(int, void *, int)) {
  assert(lastDef!=NULL);
  lastDef->hdl=handler;
}

int *CocSizePtr(void) {
  assert(lastDef!=NULL && lastDef->type==COC_ARRAY);
  return &lastDef->size;
}

void CocDefVarS(const char *name, const char *tname, void *var, int type) {
  CocVar *p;

  assert(type==COC_PTR || type==COC_STRUCT);
  p=CocDefVar(name, var, type, 0, COC_RDONLY);
  p->strucType=CocDefVar(tname, NULL, COC_TYPE, 0, COC_RDONLY);
}

char err_name[64];

int CocGetThisVar(CocVar *var, void *base, StrBuf *buf, int separator) {
  void *adr;
  
  if (base==NULL) {
    adr=var->var;
    if (adr==NULL)
      ERR_MSG("NULL pointer accessed");
  } else { /* dereference */
    adr=(char *)base + (int)var->var;
  }
  /* printf("get %s %d\n", name, (int)adr); */
  if (var->type==COC_CHAR) {
    ERR_P(StrNGet(buf, (char *)adr, var->size, separator));
  } else if (var->type==COC_INT) {
    ERR_I(StrGetInt(buf, (int *)adr, separator));
  } else if (var->type==COC_FLT) {
    ERR_I(StrGetFloat(buf, (float *)adr, separator));
  } else if (var->type==COC_ARRAY) {
    ERR_I(StrGetArray(buf, (float *)adr, var->size));
  } else {
    ERR_MSG("unknown type");
  }
  return(0);
  OnError: return(-1);
}

int CocPutThisVar(CocVar *var, void *base, StrBuf *buf, int separator) {
  void *adr;
  
  if (base==NULL) {
    adr=var->var;
    if (adr==NULL)
      ERR_MSG("NULL pointer accessed");
  } else { /* dereference */
    adr=(char *)base + (int)var->var;
  }
  /* printf("put %s %d\n", name, (int)adr); */
  if (var->type==COC_CHAR) {
    ERR_I(StrPut(buf, adr, separator));
  } else if (var->type==COC_INT) {
    ERR_I(StrPutInt(buf, *(int *)adr, separator));
  } else if (var->type==COC_FLT) {
    ERR_I(StrPutFloat(buf, *(float *)adr, separator));
  } else if (var->type==COC_ARRAY) {
    ERR_I(StrPutArray(buf, (float *)adr, var->size));
  } else {
    ERR_MSG("unknown type");
  }
  return(0);
  OnError: return(-1);
}

int CocGetVar(const char *name, StrBuf *buf, int separator) {
  CocVar *var;
  void *base;
  static char msg[128];
  
  var=CocFindVar(name, &base);
  if (var==NULL) {
    str_copy(msg, "undefined variable ; ");
    str_append(msg, name);
    ERR_MSG(msg);
  }
  ERR_I(CocGetThisVar(var, base, buf, separator));
  return(0);
  OnError: str_copy(err_name, name); ErrTxt(err_name,0); return(-1);
}

int CocPutVar(const char *name, StrBuf *buf, int separator) {
  CocVar *var;
  void *base;
  
  var=CocFindVar(name, &base);
  if (var==NULL) ERR_MSG("undefined variable");
  ERR_I(CocPutThisVar(var, base, buf, separator));
  return(0);
  OnError: str_copy(err_name, name); ErrTxt(err_name,0); return(-1);
}

void CocFreeVarList(void) {
  CocVar *p, *v;

  v=*varListHandle;
  while (v!=NULL) {
    p=v;
    v=p->next;
    p->next=NULL;
    FREE(p);
  }
  *varListHandle=NULL;
}

char *CocReadVars(char *str, char stop){
  int i, l;
  char *eql, *cr, buf[256];
  StrBuf sbuf;

  /* interprete variables until stop character appeares */
  i=sscanf(str, "%255s%n", buf, &l);
  while (i>0 && buf[0]!=stop) {
    if (buf[0]=='!') {
      cr=strchr(str, '\n');
      if (cr==NULL) return strchr(str, '\0');
      str=cr+1;
    } else {
      eql=strchr(buf,'=');
      if (eql==NULL) ERR_MSG("syntax error");
      *eql='\0';
      if (eql[1] == '\'' || eql[1]== '"') {
        eql=strchr(str, '=');
        StrNLink(&sbuf, eql+1, 256);
        ERR_I(CocGetVar(buf, &sbuf, StrNONE));
        str = sbuf.buf + sbuf.rdpos;
      } else {
        str+=l;
        StrLink(&sbuf, eql+1);
        ERR_I(CocGetVar(buf, &sbuf, ' '));
      }
    }
    i=sscanf(str, "%255s%n", buf, &l);
  }
  return str;
  OnError: return NULL;
}

void CocToClients(int mask, char *str) {
  int iret;
  CocClient *cl;

  cl=cList->next;
  while (cl!=NULL) {
    if (cl->logmask & mask) {
      iret=CocSend(cl->fd, str, strlen(str)+1);
      if (iret<0) {
        cl->logmask=0; /* disable logging for dead clients */
        logfileOut(LOG_MAIN, "(%d) disconnected while logging\n", cl->fd);
      }
    }
    cl=cl->next;
  }
}

int CocInitServer(void *(*setDataRtn)(void *), int port) {
  int i;
  struct sockaddr_in sadr;

  setData=setDataRtn;
  /*
  clDataSize=clientDataSize;
  clData=clientData;
  */
  NEW(cList,CocClient); /* empty header */

  ERR_SI(mainFd=socket(AF_INET, SOCK_STREAM, 0));
  i = 1;
  ERR_SI(setsockopt(mainFd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int))); /* allow quick port reuse */
  ERR_I(CocCreateSockAdr(&sadr, NULL, port));
  ERR_SI(bind(mainFd, (struct sockaddr *)&sadr, sizeof(sadr)));
  logfileOutRtn=CocToClients;
  logfileOut(LOG_INFO, "created server on port %d\n", port);
  ERR_SI(listen(mainFd, 8));
  FD_ZERO(&mask);
  FD_SET(mainFd, &mask);
  maxfd=mainFd+1;
  CocDefStr(loglist, COC_RDWR);
  return(0);
  OnError: return(-1);
}

int CocHandleThis(CocVar *var, void *base, StrBuf *outBuf, int mode, int fd) {
  int iret;

  if (var->hdl!=NULL) {
    iret=var->hdl(mode, base, fd);
    if (iret<0) { /* error */
      ErrShow(var->name);
      ERR_I(StrPut(outBuf, "", COC_ERR)); /* signal error message */
      ERR_I(StrPut(outBuf, ErrMessage, COC_SEP));
      return(0);
    }
    if (iret==COC_DRD) {
      ERR_I(StrPut(outBuf, "", COC_DELAYED)); /* delayed response message */
      ERR_I(StrPut(outBuf, "", COC_SEP)); /* empty message */
      return(COC_DRD);
    }
    if (iret!=0 && iret!=COC_DWR)
        ERR_MSG("illegal return value from handler");
  } else {
    iret=0;
  }
  ERR_I(CocPutThisVar(var, base, outBuf, COC_NUL));
  return(iret);
  OnError: return(-1);
}

int CocPushThisHandler(CocVar *var, CocClient *cl, void *base, int mode) {
  int n;

  if (mode==COC_DWR) {
    if (var->pending && cl->npend != 0) return(0);
    var->pending=1;
  }
  n=cl->npend;
  if (n * sizeof(Pend) >= sizeof(cl->pend)) {
    ERR_MSG("too many commands")
  }
  cl->pend[n].var=var;
  cl->pend[n].base=base;
  cl->pend[n].mode=mode;
  cl->npend=n+1;
  return(0);
  OnError: return(-1);
}

int CocCallHandlers(void) {
  CocClient *cl;
  CocVar *var;
  Pend *p;
  int i, iret, mode, delayedRead;
  char synch;
  DeclStrBuf(bufr, COC_RES_LEN);

  cl=cList->next;
  while (cl!=NULL) {

    delayedRead=0;
    /* treat delayed write handlers first */
    for (i=0; i<cl->npend; i++) {
      p=&cl->pend[i];
      mode=p->mode;
      var=p->var;
      assert(var!=NULL && var->hdl!=NULL);
      if (mode==COC_DWR) {
        var->pending=0;
        ERR_I(var->hdl(mode, p->base, cl->fd));
        p->mode=0;
      } else {
        delayedRead=1;
      }
    }
    if (delayedRead) {
      StrClear(&bufr);
      synch=cl->synch;
      ERR_I(StrPut(&bufr, "", cl->synch));
      for (i=0; i<cl->npend; i++) {
        p=&cl->pend[i];
        mode=p->mode;
        var=p->var;
        assert(var!=NULL && var->hdl!=NULL);
        if (mode==COC_DRD) {
          iret=var->hdl(mode, p->base, cl->fd);
          if (iret<0) { /* error */
            ERR_I(StrPut(&bufr, "", COC_ERR)); /* signal error message */
            ERR_I(StrPut(&bufr, ErrMessage, COC_SEP));
          }
          ERR_I(CocPutThisVar(var, p->base, &bufr, COC_NUL));
        }
      }
      if (cl->synch==synch) { /* send only if no new request from client received */
        assert(cl->fd!=0);
        ERR_I(CocSend(cl->fd, bufr.buf, bufr.wrpos));
      }
    }
    cl->npend=0;
    cl=cl->next;
  }
  return(0);
  OnError:
    cl->npend=0;
    return(-1);
}

void CocShowHandlers(char *buf, int buf_len) {
  CocClient *cl;
  Pend *p;
  int i;

  buf[0]='\0';
  cl=cList->next;
  while (cl!=NULL) {
    for (i=0; i<cl->npend; i++) {
      p=&cl->pend[i];
      if (p->mode==COC_DWR) {
        assert(p->var!=NULL);
        if (buf[0]!='\0') str_ncat(buf, ", ", buf_len);
        str_ncat(buf, p->var->name, buf_len);
      }
    }
    cl=cl->next;
  }
  return;
}

#ifdef __VMS
  typedef size_t socklen_type;
#else
  typedef socklen_t socklen_type;
#endif

int CocHandle1Request(int tmo_msec, int fd) {
  struct sockaddr_in cadr;
  struct hostent *h;
  struct timeval tmo={0,1};
  CocClient *cl, *cl0;
  CocVar *var;
  int i, lmask, newfd, iret;
  socklen_type cadrlen;
  char *err, *varname;
  void *base;

  rmask=mask;
  if (fd>0) FD_SET(fd, &rmask);
  if (fd>=maxfd) maxfd=fd+1;
  tmo.tv_sec=tmo_msec / 1000;
  tmo.tv_usec=(tmo_msec % 1000)*1000+1;
  ERR_I(i=uselect(maxfd,&rmask,NULL,NULL,&tmo));
  if (fd>0 && FD_ISSET(fd, &rmask)) return(1); /* event on fd */
  if (i==0) return(0); /* timeout */

  if (FD_ISSET(mainFd, &rmask)) {
    cadrlen=sizeof(cadr);
    ERR_SI(newfd=accept(mainFd, (struct sockaddr *)&cadr, &cadrlen));
    FD_SET(newfd, &mask);
    if (newfd>=maxfd) maxfd=newfd+1;
    NEW(cl, CocClient);
    cl->fd=newfd;
    cl->mode=0;
    if (setData!=NULL) {
      ERR_P(cl->data = setData(NULL)); /* create new client data object */
    }
    /*
    ERR_SP(cList=calloc(1,sizeof(CocClient)+clDataSize));
    */
    cl->next=cList->next;
    cList->next=cl;
    h=gethostbyaddr((void *)&cadr.sin_addr, 4, AF_INET);
    if (h==NULL) {
      logfileOut(LOG_INFO, "(%d) open from %s\n", newfd, "local");
    } else {
      logfileOut(LOG_INFO, "(%d) open from %s\n", newfd, h->h_name);
    }
  } else {
    cl0=cList; cl=cl0->next;
    while (cl!=NULL) {
      if (FD_ISSET(cl->fd, &rmask)) {
        iret=CocRecv(cl->fd, &buf, -1, NULL);
        if (iret<=0) {
          logfileOut(LOG_INFO, "(%d) disconnected\n",cl->fd);
          close(cl->fd);
          FD_CLR(cl->fd, &mask);
          cl0->next=cl->next;
          FREE(cl);
          cl=cl0;
        } else {
          cl->logmask=0; /* stop output to log client */
          lmask=0;
          StrReset(&buf);
          StrClear(&bufo);
          err=NULL;
          logfileOut(LOG_NET, "(%d) ", cl->fd);
          logfileOutBuf(LOG_NET, &buf);
          /* cl->npend=0; why that ? */
          logfileOut(LOG_NET, "\n");
          cl->synch=buf.buf[0];
          ERR_P(StrGet(&buf, NULL, cl->synch));
          ERR_I(StrPut(&bufo, "", cl->synch));
          while (!StrEnd(&buf)) {
            ERR_P(varname=StrGet(&buf, NULL, ' '));
            if (cl->synch==COC_MAGIC) { /* access code */
              if (0==strcmp(varname,"rdacs")) {
                logfileOut(LOG_INFO, "set read mode\n");
                cl->mode=1;
                ERR_I(StrPut(&bufo, "", COC_SEP)); /* empty message */
              } else if (0==strcmp(varname,"rwacs")) {
                logfileOut(LOG_INFO, "set write mode\n");
                cl->mode=2;
                ERR_I(StrPut(&bufo, "", COC_SEP)); /* empty message */
              } else {
                ERR_I(StrPut(&bufo, "", COC_ERR)); /* signal error message */
                ERR_I(StrPut(&bufo, "bad access code", COC_SEP));
              }
              ERR_P(StrGet(&buf, NULL, COC_NUL)); /* skip separator */
            } else if (cl->mode==0) {
              ERR_I(StrPut(&bufo, "", COC_ERR)); /* signal terminal error message */
              ERR_I(StrPut(&bufo, "no access", COC_SEP));
              ERR_P(StrGet(&buf, NULL, COC_NUL)); /* skip separator */
            } else {
              if (cl->data!=NULL) {
                setData(cl->data);
              }
              var=CocFindVar(varname, &base);              
              if (var==NULL) {
                ERR_I(StrPut(&bufo, "", COC_ERR)); /* signal error message */
                ERR_I(StrPut(&bufo, "undefined variable", COC_SEP));
                ERR_P(StrGet(&buf, NULL, COC_NUL)); /* skip separator */
              } else if (buf.seen) { /* separator was there: set mode */
                if (var->access > cl->mode) {
                  ERR_I(StrPut(&bufo, "", COC_ERR)); /* signal error message */
                  ERR_I(StrPut(&bufo, "no access", COC_SEP));
                  ERR_P(StrGet(&buf, NULL, COC_NUL)); /* skip separator */
                } else {
                  ERR_I(CocGetThisVar(var, base, &buf, COC_SEP));
                  if (0==strcmp(var->name,"loglist")) {
                    str_upcase(loglist, loglist);
                    if (NULL!=strchr(loglist,'A')) lmask = lmask | LOG_ALL;
                    if (NULL!=strchr(loglist,'S')) lmask = lmask | LOG_SER;
                    if (NULL!=strchr(loglist,'N')) lmask = lmask | LOG_NET;
                    if (NULL!=strchr(loglist,'I')) lmask = lmask | LOG_INFO;
                    if (NULL!=strchr(loglist,'M')) lmask = lmask | LOG_MAIN;
                    ERR_I(StrPut(&bufo, "", COC_NUL)); /* o.k. */
                  } else {
                    ERR_I(iret=CocHandleThis(var, base, &bufo, COC_WR, cl->fd));
                    if (iret) ERR_I(CocPushThisHandler(var, cl, base, iret));
                  }
                  modified=1;
                }
              } else {
                ERR_P(StrGet(&buf, NULL, COC_NUL)); /* skip separator */
                ERR_I(iret=CocHandleThis(var, base, &bufo, COC_RD, cl->fd));
                if (iret) ERR_I(CocPushThisHandler(var, cl, base, iret));
              }
            }
          }
          ERR_I(StrGetEnd(&buf));
          logfileOut(LOG_NET, "    ");
          logfileOutBuf(LOG_NET, &bufo);
          logfileOut(LOG_NET, "\n");
          ERR_I(CocSend(cl->fd, bufo.buf, bufo.wrpos));
          cl->logmask=lmask;
        }
      }
      cl0=cl; cl=cl->next;
    }
  }
  if (modified) return(2);
  return(3);
  OnError: return(-1);
}

int CocHandleRequests(int tmo_msec, int fd) {
  int tdif, iret, tim0;

  if (modified && fd==0) { /* earlier modification */
    modified=0;
    return(2);
  }
  tim0=mycMsecSince(0);
  tdif=tmo_msec;
  while (tdif>=0) {
    ERR_I(iret=CocHandle1Request(tdif, fd));
    if (fd==0) {
      if (iret==2) return(2); /* modification of a variable */
    } else {
      if (iret==1) return(1); /* event on fd */
    }
    if (iret==0) return(0); /* timeout */
    tdif = tmo_msec - mycMsecSince(tim0);
  }
  return(0); /* timeout */
  OnError: return(-1);
}

void CocCloseServer() {
  CocClient *cl, *cl0;

  if (cList) {
    cl=cList->next;
    while (cl!=NULL) {
      close(cl->fd);
      cl0=cl;
      cl=cl->next;
      FREE(cl0);
    }
    FREE(cList);
  }
  close(mainFd);
}
