/* needed to define accept() and gethostbyaddr() correctly when using flag -std1
*/
#ifdef __unix__
#ifndef _XOPEN_SOURCE_EXTENDED
#define _XOPEN_SOURCE_EXTENDED
#endif
#endif

#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/timeb.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
#include "sys_util.h"
#include "err_handling.h"
#include "coc_logfile.h"
#include "coc_server.h"
#include "str_util.h"

static Str_Buf *buf, *bufo;

static fd_set mask, rmask;
static int maxfd;
struct CocClient *cList, *cLastCmd=NULL;
static int mainFd;
static int modified;

int CocInitServer(int bufsize, int port) {
  int i;
  struct sockaddr_in sadr;
  char *err;

  if (bufsize==0) bufsize=1024;
  ERR_P(buf=str_create_buf(bufsize, '\0'));
  ERR_P(bufo=str_create_buf(bufsize,'\0'));
  NEW(cList); /* empty header */

  /* first try to connect to an existing server */

  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)));
  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;
  return(0);
  OnError: return(-1);
}

int CocHandle1Request(int tmo_msec, int fd) {
  struct sockaddr_in cadr;
  struct hostent *h;
  struct timeval tmo={0,1};
  struct CocClient *cl, *cl0;
  int i, newfd, setmode;
  size_t cadrlen;
  char *err, *varname;

  rmask=mask;
  if (fd>0) FD_SET(fd, &rmask);
  tmo.tv_sec=tmo_msec / 1000;
  tmo.tv_usec=(tmo_msec % 1000)*1000+1;
  if (fd>=maxfd) maxfd=fd+1;
  ERR_SI(i=select(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)) {
    ERR_SI(newfd=accept(mainFd, (struct sockaddr *)&cadr, &cadrlen));
    FD_SET(newfd, &mask);
    if (newfd>=maxfd) maxfd=newfd+1;
    cl=cList;
    cl->fd=newfd;
    cl->mode=0;
    cl->cmd[0]='\0';
    cl->res[0]='\0';
    NEW(cList);
    cList->next=cl;
    h=gethostbyaddr(&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)) {
        buf->wrpos=recv(cl->fd, buf->buf, buf->dsize, 0);
        if (buf->wrpos<=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 {

          str_put_start(bufo);
          str_get_start(buf);
          ERR_I(str_put_str(bufo, "")); /* empty error message */
          setmode=0;
          err=NULL;
          ERR_P(varname=str_get_str(buf, NULL));
          logfileOut(LOG_NET, "(%d) ", cl->fd);
          if (varname[0]=='#') { /* access code */
            if (0==strcmp(varname,"#rdacc")) {
              logfileOut(LOG_INFO, "set read mode\n");
              cl->mode=1;
            } else if (0==strcmp(varname,"#rwacs")) {
              logfileOut(LOG_INFO, "set write mode\n");
              cl->mode=2;
            } else {
              err="bad access code";
            }
          } else if (cl->mode==0) {
            err="no access";
          } else {
            while (1) {
              if (varname[0]=='[') {
                if (cl->mode<2) { err="no write access"; break; }
                setmode=1; /* switch to set mode */
              } else if (varname[0]==']') { /* switch to read mode */
                setmode=0;
              } else if (setmode) {
                if (0==strcmp("$", varname)) { /* special case: command */
                  ERR_P(str_get_str(buf, cl->cmd));
                  cl->res[0]='\0';
                } else {
                  i=CocGetVar(serverVarList, buf, varname, 1);
                  if (i<0) {
                    err=ErrMessage; break;
                  }
                }
                modified=1;
              } else {
                if (0==strcmp("$", varname)) { /* special case: response */
                  ERR_I(str_put_str(bufo, cl->res));
                  cl->res[0]='\0';
                } else {
                  i=CocPutVar(serverVarList, bufo, varname, 0);
                  if (i<0) {
                    err=ErrMessage; break;
                  }
                }
              }
              if (buf->rdpos>=buf->wrpos) {
                ERR_I(str_get_end(buf));
                break;
              }
              ERR_P(varname=str_get_str(buf, NULL));
            }
            str_get_start(buf);
            logfileOutBuf(LOG_NET, buf);
            str_get_start(bufo);
            logfileOut(LOG_NET, " |");
            logfileOutBuf(LOG_NET, bufo);
          }
          if (err==NULL) {
            logfileOut(LOG_NET, "\n");
          } else {
            str_put_start(bufo); /* reset output */
            str_put_str(bufo, err); /* put error message */
            logfileOut(LOG_NET, " (%s)\n", err);
            /* logfileMask(LOG_NET); */
          }
          ERR_SI(send(cl->fd, bufo->buf, bufo->wrpos, 0));
        }
      }
      cl0=cl; cl=cl->next;
    }
  }
  if (modified) return(2);
  return(3);
  OnError: return(-1);
}

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

  if (modified && fd==0) { /* earlier modification */
    modified=0;
    return(2);
  }
  ftime(&tim0);
  tdif=tmo_msec;
  while (tdif>=0) {
    ERR_I(iret=CocHandle1Request(tdif, fd));
    if (fd==0) {
      if (iret==2) return(2); /* modification of a varaible */
    } else {
      if (iret==1) return(1); /* event on fd */
    }
    if (iret==0) return(0); /* timeout */
    ftime(&tim1);
    tdif=tmo_msec-((tim1.time-tim0.time)*1000+tim1.millitm-tim0.millitm);
  }
  return(0); /* timeout */
  OnError: return(-1);
}

struct CocClient *CocGetNextCmd() {
  struct CocClient *cl;

  cl=cLastCmd;
  while (cl!=NULL) {
    if (cl->cmd[0]!='\0') {
      cLastCmd=cl->next;
      return(cl);
    }      
    cl=cl->next;
  }
  cl=cList->next;
  while (cl!=NULL && cl!=cLastCmd) {
    if (cl->cmd[0]!='\0') {
      cLastCmd=cl->next;
      return(cl);
    }      
    cl=cl->next;
  }
  return(NULL);
}

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

  cl=cList->next;
  while (cl!=NULL) {
    close(cl->fd);
    cl0=cl;
    cl=cl->next;
    FREE(cl0);
  }
  FREE(cList);
  close(mainFd);
  str_free_buf(buf); str_free_buf(bufo);
  logfileClose();
}
