#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <assert.h>
#include "sys_util.h"
#include "err_handling.h"
#include "str_util.h"
#include "coc_util.h"
/*-------------------------------------------------------------------------*/
/* CreateSocketAddress stolen from Tcl. Thanks to John Ousterhout */

CocVar *serverVarList=NULL;
static CocVar **varListHdl=&serverVarList;
int CocRD=0;
int CocWR=0;

int CocCreateSockAdr(
    struct sockaddr_in *sockaddrPtr,    /* Socket address */
    const char *host,                   /* Host.  NULL implies INADDR_ANY */
    int port)                           /* Port number */
{
    struct hostent *hostent;            /* Host database entry */
    struct in_addr addr;                /* For 64/32 bit madness */

    (void) memset((char *) sockaddrPtr, '\0', sizeof(struct sockaddr_in));
    sockaddrPtr->sin_family = AF_INET;
    sockaddrPtr->sin_port = htons((unsigned short) (port & 0xFFFF));
    if (host == NULL || host[0]=='\0') {
        addr.s_addr = INADDR_ANY;
    } else {
        hostent = gethostbyname(host);
        if (hostent != NULL) {
            memcpy((char *) &addr,
                (char *) hostent->h_addr_list[0], (size_t) hostent->h_length);
        } else {
            addr.s_addr = inet_addr(host);
            if (addr.s_addr == (unsigned long)-1) {
                return -1;       /* error */
            }
        }
    }
    /*
     * There is a rumor that this assignment may require care on
     * some 64 bit machines.
     */

    sockaddrPtr->sin_addr.s_addr = addr.s_addr;
    return 0;
}

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

int CocRecv(int fd, Str_Buf *buf)
{
  struct timeval tmo={0,1};
  fd_set mask;
  int i;

  tmo.tv_sec=5; /* timeout 5 sec. */

  FD_ZERO(&mask);
  FD_SET(fd, &mask);
  ERR_SI(i=select(fd+1,&mask,NULL,NULL,&tmo));
  if (i==0) ERR_MSG("time out");

  ERR_SI(buf->wrpos=recv(fd, buf->buf, buf->dsize, 0));
  if (buf->wrpos==0) { ERR_COD(ECONNRESET); }
  str_get_start(buf);
  return(buf->wrpos);
  OnError: return(-1);
}

void CocVarList(CocVar **varList) {
  assert(varList!=NULL);
  varListHdl=varList;
}

void CocList() {
  CocVar *p;

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

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

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

CocVar *CocFindVar(CocVar *varList, 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(varList, 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(varList, nam);
  if (p!=NULL && p->type==COC_ALIAS) { /* recursive call for alias */
    p=CocFindVar(varList, p->var, adr);
  }
  return(p);
}

CocVar *CocDefVar(const char *name, void *var, int type, int *flag) {
  CocVar *p;
  const char *f;
  void *adr;

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

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

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

char err_name[64];

int CocGetVar(CocVar *varList, Str_Buf *buf, const char *name, int secure) {
  CocVar *var;
  void *adr;
  
  var=CocFindVar(varList, name, &adr);
  if (var==NULL) ERR_MSG("undefined variable");
  if (adr==NULL) {
    adr=var->var;
  } else {
    adr=(char *)adr + (int)var->var;
  }
  if (secure) { /* we are the server */
    if (var->flag==&CocRD) ERR_MSG("variable is read only");
  }
  /* printf("get %s %d\n", name, (int)adr); */
  if (var->type==-1) {
    ERR_I(str_get_int(buf, (int *)adr));
  } else if (var->type==-2) {
    ERR_I(str_get_float(buf, (float *)adr));
  } else if (var->type>1) {
    ERR_P(str_nget_str(buf, (char *)adr, var->type));
  } else {
    ERR_MSG("unknown type");
  }
  if (secure) { /* we are the server */
    (*var->flag)++;
  }
  return(0);
  OnError: str_copy(err_name, name); ErrTxt(err_name,0); return(-1);
}

int CocPutVar(CocVar *varList, Str_Buf *buf, const char *name, int secure) {
  CocVar *var;
  void *adr;
  char *c;
  
  var=CocFindVar(varList, name, &adr);
  if (var==NULL) ERR_MSG("undefined variable");
  if (adr==NULL) {
    adr=var->var;
  } else {
    adr=(char *)adr + (int)var->var;
  }
  if (secure) { /* check access */
    if (var->flag==&CocRD) ERR_MSG("variable is read only");
  }
  /* printf("put %s %d\n", name, (int)adr); */
  if (var->type==-1) {
    ERR_I(str_put_int(buf, *(int *)adr));
  } else if (var->type==-2) {
    ERR_I(str_put_float(buf, *(float *)adr));
  } else if (var->type>1) {
    ERR_I(str_put_str(buf, adr));
  } else {
    ERR_MSG("unknown type");
  }
  if (secure) { /* we are a client */
    if (var->flag!=NULL) (*var->flag)++;
  }
  return(0);
  OnError: str_copy(err_name, name); ErrTxt(err_name,0); return(-1);
}

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

  if (varList==NULL) varList=&serverVarList;
  v=*varList;
  while (v!=NULL) {
    p=v;
    v=p->next;
    p->next=NULL;
    FREE(p);
  }
  *varList=NULL;
}

void CocDelay(int msec) {
  struct timeval tmo;

  tmo.tv_sec=msec / 1000;
  tmo.tv_usec=(msec % 1000)*1000+1;
  select(1,NULL,NULL,NULL,&tmo);
}
