/* interface.c */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <signal.h>
#include "config.h"
#include "object.h"
#include "globals.h"
#include "interp.h"
#include "dbhandle.h"
#include "construct.h"
#include "interface.h"
#include "clearq.h"
#include "file.h"
#include "cache.h"
#include "edit.h"

struct connlist_s {
  int fd;
  struct sockaddr_in address;
  char inbuf[MAX_STR_LEN];
  int inbuf_count;
  int outbuf_count;
  char *outbuf;
  struct object *obj;
};

struct connlist_s *connlist;
int num_conns,sockfd;

void saniflush(int fd,int count,char *buf) {
  struct timeval timeout;
  fd_set writefds,exceptfds;
  int num_written,num_to_write,num_actually_wrote;

  num_written=0;
  while (num_written<count) {
    FD_ZERO(&writefds);
    FD_ZERO(&exceptfds);
    FD_SET(fd,&writefds);
    FD_SET(fd,&exceptfds);
    timeout.tv_sec=0;
    timeout.tv_usec=0;
    select(getdtablesize(),NULL,&writefds,&exceptfds,&timeout);
    if (FD_ISSET(fd,&exceptfds)) return;
    if (!(FD_ISSET(fd,&writefds))) return;
    if ((count-num_written)<WRITE_BURST)
      num_to_write=count-num_written;
    else
      num_to_write=WRITE_BURST;
    num_actually_wrote=write(fd,buf+num_written,num_to_write);
    if (num_actually_wrote<=0) return;
    num_written+=num_actually_wrote;
  }
}

struct object *next_who(struct object *obj) {
  long loop;

  if (obj)
    loop=obj->devnum;
  else
    loop=(-1);
  while (++loop<num_conns)
    if (connlist[loop].fd!=(-1))
      return connlist[loop].obj;
  return NULL;
}

void unbuf_output(int devnum) {
  int num_written;
  char *tmp;

  num_written=write(connlist[devnum].fd,connlist[devnum].outbuf,
                    ((connlist[devnum].outbuf_count>WRITE_BURST) ?
                     WRITE_BURST:connlist[devnum].outbuf_count));
  if (num_written<=0) return;
  if (num_written<connlist[devnum].outbuf_count) {
    tmp=MALLOC(connlist[devnum].outbuf_count-num_written+1);
    strcpy(tmp,&((connlist[devnum].outbuf)[num_written]));
    connlist[devnum].outbuf_count-=num_written;
    FREE(connlist[devnum].outbuf);
    connlist[devnum].outbuf=tmp;
  } else {
    connlist[devnum].outbuf_count=0;
    FREE(connlist[devnum].outbuf);
    connlist[devnum].outbuf=NULL;
  }
}

void set_now_time() {
  now_time=time2int(time(NULL));
}

void immediate_disconnect(int devnum) {
  if (devnum==(-1)) return;
  if (connlist[devnum].obj->flags & IN_EDITOR)
    remove_from_edit(connlist[devnum].obj);
  connlist[devnum].obj->flags&=~CONNECTED;
  if (connlist[devnum].outbuf_count>0)
    saniflush(connlist[devnum].fd,connlist[devnum].outbuf_count,
              connlist[devnum].outbuf);
  shutdown(connlist[devnum].fd,2);
  close(connlist[devnum].fd);
  connlist[devnum].fd=(-1);
  connlist[devnum].obj->devnum=(-1);
  connlist[devnum].outbuf_count=0;
  if (connlist[devnum].outbuf) FREE(connlist[devnum].outbuf);
  connlist[devnum].outbuf=NULL;
}

void make_new_conn() {
  int loop;
  int devnum,new_fd;
  struct sockaddr_in tmpaddr;
  struct object *boot_obj;
  struct var_stack *rts;
  struct var tmp;
  struct fns *func;
  
  boot_obj=ref_to_obj(0);
  devnum=-1;
  loop=sizeof(struct sockaddr);
  new_fd=accept(sockfd,(struct sockaddr *) &tmpaddr,&loop);
  if (new_fd<0) return;
  if (fcntl(new_fd,F_SETFL,O_NDELAY)<0) {
    shutdown(new_fd,2);
    close(new_fd);
    return;
  }
  loop=0;
  while (loop<num_conns) {
    if (connlist[loop].fd==(-1)) {
      devnum=loop;
      break;
    }
    loop++;
  }   
  if (devnum==(-1) || (boot_obj->devnum!=(-1))) {
    shutdown(new_fd,2);
    close(new_fd);
    return;
  }
  connlist[devnum].fd=new_fd;
  connlist[devnum].inbuf_count=0;
  connlist[devnum].address=tmpaddr;
  connlist[devnum].obj=boot_obj;
  connlist[devnum].outbuf_count=0;
  connlist[devnum].outbuf=NULL;
  boot_obj->devnum=devnum;
  boot_obj->flags|=CONNECTED;

#ifdef CYCLE_HARD_MAX
  hard_cycles=0;
#endif /* CYCLE_HARD_MAX */

#ifdef CYCLE_SOFT_MAX
  soft_cycles=0;
#endif /* CYCLE_SOFT_MAX */

  func=find_fns("connect",boot_obj);
  if (func) {
    tmp.type=NUM_ARGS;
    tmp.value.num=0;
    rts=NULL;
    push(&tmp,&rts);
    interp(NULL,boot_obj,NULL,&rts,func);
    free_stack(&rts);
  }
  handle_destruct();
}

void buffer_input(int conn_num) {
  static char buf[MAX_STR_LEN];
  struct var_stack *rts;
  struct var tmp;
  struct fns *func;
  int retlen;
  struct object *obj;
  int loop;
  
  retlen=read(connlist[conn_num].fd,buf,MAX_STR_LEN-2);
  if (retlen<=0) {
    obj=connlist[conn_num].obj;
    immediate_disconnect(conn_num);
    func=find_fns("disconnect",obj);

#ifdef CYCLE_HARD_MAX
    hard_cycles=0;
#endif /* CYCLE_HARD_MAX */

#ifdef CYCLE_SOFT_MAX
    soft_cycles=0;
#endif /* CYCLE_SOFT_MAX */

    if (func) {
      rts=NULL;
      tmp.type=NUM_ARGS;
      tmp.value.num=0;
      push(&tmp,&rts);
      interp(NULL,obj,NULL,&rts,func);
      free_stack(&rts);
    }
    handle_destruct();
    return;
  }
  loop=0;
  while (loop<retlen) {
    if (buf[loop]=='\n') {
       connlist[conn_num].inbuf[connlist[conn_num].inbuf_count]='\0';
       if (connlist[conn_num].obj->flags & IN_EDITOR)
         do_edit_command(connlist[conn_num].obj,connlist[conn_num].inbuf);
       else
         queue_command(connlist[conn_num].obj,connlist[conn_num].inbuf);
       connlist[conn_num].inbuf_count=0;
    } else
      if (buf[loop]!='\r' && (isgraph(buf[loop]) || buf[loop]==' ')) {
        if (connlist[conn_num].inbuf_count<MAX_STR_LEN-2) {
          connlist[conn_num].inbuf[connlist[conn_num].inbuf_count]=buf[loop];
          ++(connlist[conn_num].inbuf_count);
	}
      }
    loop++;
  }
}
  
void handle_input() {
  fd_set input_set,output_set,exception_set;
  int loop;
  struct timeval delay_s;
  struct timeval *delay_p;
  struct var tmp;
  struct var_stack *rts;
  struct fns *func;
  struct object *obj;

  set_now_time();
  while (1) {
    if (cache_top>MAX_CACHEFILE_SIZE) {
      log_sysmsg("  cache: auto-saving");
      if (save_db(NULL))
        log_sysmsg("  cache: auto-save failed");
      else
        log_sysmsg("  cache: auto-save completed");
    }
    FD_ZERO(&input_set);
    FD_ZERO(&output_set);
    FD_ZERO(&exception_set);
    loop=0;
    while (loop<num_conns) {
      if (connlist[loop].fd!=(-1)) {
        FD_SET(connlist[loop].fd,&input_set);
        FD_SET(connlist[loop].fd,&exception_set);
        if (connlist[loop].outbuf_count) {
          FD_SET(connlist[loop].fd,&output_set);
	}
      }
      loop++;
    }
    FD_SET(sockfd,&input_set);
    if (alarm_list) {
      if (alarm_list->delay>=now_time)
        delay_s.tv_sec=alarm_list->delay-now_time;
      else
        delay_s.tv_sec=0;
      delay_s.tv_usec=0;
      delay_p=&delay_s;
    } else
      delay_p=NULL;
    select(getdtablesize(),&input_set,&output_set,&exception_set,delay_p);
    set_now_time();
    if (FD_ISSET(sockfd,&input_set)) make_new_conn();
    loop=0;
    while (loop<num_conns) {
      if (connlist[loop].fd!=(-1))
        if (FD_ISSET(connlist[loop].fd,&exception_set)) {
          obj=connlist[loop].obj;
          immediate_disconnect(loop);
          func=find_fns("disconnect",obj);

#ifdef CYCLE_HARD_MAX
          hard_cycles=0;
#endif /* CYCLE_HARD_MAX */

#ifdef CYCLE_SOFT_MAX
          soft_cycles=0;
#endif /* CYCLE_SOFT_MAX */

          if (func) {
            rts=NULL;
            tmp.type=NUM_ARGS;
            tmp.value.num=0;
            push(&tmp,&rts);
            interp(NULL,obj,NULL,&rts,func);
            free_stack(&rts);
          }
          handle_destruct();
        }
      loop++;
    }
    loop=0;
    while (loop<num_conns) {
      if (connlist[loop].fd!=(-1))
        if (FD_ISSET(connlist[loop].fd,&input_set))
          buffer_input(loop);
      loop++;
    }
    loop=0;
    while (loop<num_conns) {
      if (connlist[loop].fd!=(-1))
        if (FD_ISSET(connlist[loop].fd,&output_set))
          unbuf_output(loop);
      loop++;
    }

#ifdef CYCLE_HARD_MAX
    hard_cycles=0;
#endif /* CYCLE_HARD_MAX */

    do {
      handle_destruct();
      handle_alarm();
      handle_destruct();
      handle_command();
      handle_destruct();
      handle_alarm();
      handle_destruct();
    } while (cmd_head || dest_list);
    unload_data();
  }
}

int init_interface(int port, int do_single) {
  int loop=0;
  struct sockaddr_in server;

  if (do_single) return NOSINGLE;
  sockfd=socket(AF_INET,SOCK_STREAM,0);
  if (sockfd<0) return NOSOCKET;
  server.sin_family=AF_INET;
  server.sin_addr.s_addr=INADDR_ANY;
  server.sin_port=htons(port);
  if (bind(sockfd,(struct sockaddr *) &server, sizeof(server)))
    return PORTINUSE;
  listen(sockfd,5);
  num_conns=getdtablesize()-(7+MIN_FREE_FILES);
  if (num_conns<1) num_conns=1;
  if (num_conns>MAX_CONNS) num_conns=MAX_CONNS;
  connlist=MALLOC(sizeof(struct connlist_s)*num_conns);
  loop=0;
  while (loop<num_conns) {
    connlist[loop].fd=(-1);
    loop++;
  }
  signal(SIGPIPE,SIG_IGN);
  return 0;
}

void shutdown_interface() {
  int loop=0;

  while (loop<num_conns) {
    if (connlist[loop].fd!=(-1))
      immediate_disconnect(loop);
    loop++;
  }
  close(sockfd);
  FREE(connlist);
}

char *get_devconn(struct object *obj) {
  if (!obj) return NULL;
  if (obj->devnum==-1) return NULL;
  return inet_ntoa(connlist[obj->devnum].address.sin_addr);
}

void send_device(struct object *obj, char *msg) {
  int len;
  char *tmp;

  if (!obj) return;
  if (obj->devnum==-1) return;
  if (!msg) return;
  len=strlen(msg);
  if (!len) return;
  if (connlist[obj->devnum].outbuf_count>MAX_OUTBUF_LEN) return;
  if (connlist[obj->devnum].outbuf_count+len>MAX_OUTBUF_LEN) len+=24;
  if (connlist[obj->devnum].outbuf) {
    tmp=MALLOC(connlist[obj->devnum].outbuf_count+len+1);
    strcpy(tmp,connlist[obj->devnum].outbuf);
    strcat(tmp,msg);
    if (connlist[obj->devnum].outbuf_count+len-24>MAX_OUTBUF_LEN)
      strcat(tmp,"\n*** Output Flushed ***\n");
    FREE(connlist[obj->devnum].outbuf);
    connlist[obj->devnum].outbuf=tmp;
    connlist[obj->devnum].outbuf_count+=len;
  } else {
    tmp=MALLOC(len+1);
    strcpy(tmp,msg);
    if (len-24>MAX_OUTBUF_LEN)
      strcat(tmp,"\n*** Output Flushed ***\n");
    connlist[obj->devnum].outbuf=tmp;
    connlist[obj->devnum].outbuf_count=len;
  }
}

int reconnect_device(struct object *src, struct object *dest) {
  if (dest->devnum!=-1) return 1;
  if (src->devnum==-1) return 1;
  dest->devnum=src->devnum;
  src->flags&=~CONNECTED;
  src->devnum=(-1);
  dest->flags|=CONNECTED;
  connlist[dest->devnum].obj=dest;
  return 0;
}

void disconnect_device(struct object *obj) {
  if (obj->devnum==-1) return;
  if (!(obj->flags & CONNECTED)) return;
  immediate_disconnect(obj->devnum);
  obj->flags&=~CONNECTED;
  obj->devnum=(-1);
}