#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>
#include "server.h"
#include "mytime.h"

static const char *version = "$Id$";

#define NOSIG(expression)			\
  (__extension__				\
   ({ long int __result;			\
     do __result = (long int) (expression);	\
     while (__result == -1L && errno == EINTR);	\
     __result; }))

server_connection *new_server_connection(int s,void *inst_data,int rxlen,int txlen) {
  server_connection *sc = malloc(sizeof(server_connection));
  sc->s = s;
  sc->rx = new_ring_buffer(rxlen);
  sc->tx = new_ring_buffer(txlen);
  sc->inst_data = inst_data;
  sc->next = 0;
  return sc;
}

server_connection *find_connection(server_connection *head,int s) {
  while (head && head->s != s) {
    head = head->next;
  }
  return head;
}

#define START t1 = TimeNow()
#define STOP  t2 = TimeNow(); fprintf(stderr,"<%lld>",t2-t1)

struct instance_data {
  void *context;
  int maplen[16];
  int *diomap[16];
};

// svc = function to call to service incoming data
// data = pointer to pass to svc
// svc_head = pointer to head of list of connections
// clockT = period in ms to call svc to perform background tasks (0=never)
int server(server_fe svr,void *data,server_connection **svc_head,
	   unsigned clockT,void *deleteInst) {
  int running = 1;
  fd_set c_rfd, rfd; // set of descriptors that want to rx data
  fd_set c_wfd, wfd; // set of descriptors that want to tx data
  fd_set c_efd, efd; // watch all descriptors for exceptions
  int dmax;          // highest descriptor to select on, plus 1
  int rc,err,i;
  struct timeval t,*tv=0;
  Time lastTime,t1,t2,nextTime;
  server_connection *conn;
  void (*destInst)(void *) = deleteInst;

  if (clockT > 0) {
    tv = &t;
    clockT = clockT * 1000; // convert to microseconds
    lastTime = TimeNow();
  }
  while (running) {
    // set up rfd for connections with buffers which can hold data
    // set up wfd for connections with buffers which have data to write
    // set up efd for all connections
    FD_ZERO(&rfd);
    FD_ZERO(&wfd);
    FD_ZERO(&efd);
    conn = svc_head[0];
    dmax = 0;
    while (conn) {
      FD_SET(conn->s,&efd);
      if ((conn->s+1) > dmax) dmax = conn->s+1;
      if (ring_bytes_free(conn->rx)) {
	//printf("<%d> ",conn->s);
	FD_SET(conn->s,&rfd);
      }
      if (ring_bytes_ready(conn->tx)) {
	//printf("{%d} ",conn->s);
	FD_SET(conn->s,&wfd);
      }
      conn = conn->next;
    }
    do {
      c_rfd = rfd;
      c_wfd = wfd;
      c_efd = efd;
      if (clockT > 0) { // set up timeout
	nextTime = TimeNow();
	t = TimeLeft(lastTime+clockT-nextTime);
	lastTime = nextTime;
      }
      //printf("select(%d)\n",dmax);
      rc = select(dmax, &c_rfd, &c_wfd, 0, tv);
      //printf("select() returns %d\n",rc);
    } while (rc == -1 && errno == EINTR);
    if (rc == -1) perror("select");
    if (rc == 0) { // timeout
      running = svr(0,data); // timeout servicing
      lastTime = TimeNow();
    } else {
      conn = svc_head[0];
      while (conn) {
	int svc = 0,closing=0;

	if (FD_ISSET(conn->s, &c_wfd)) { // write servicing
	  ring_tx(conn->s,conn->tx); // tx as much data as we can
	  svc = 1; // in case we are blocked on sending more data
	}
	if (FD_ISSET(conn->s, &c_rfd)) { // read servicing
	  if (!ring_rx(conn->s,conn->rx,&err)) { // rx as much data as we can
	    if (err != ENOTCONN) { 
	      // this is sort of a hack.  we have to allocate receive space
	      // or we won't even try to select waiting for read, but if
	      // we don't then a listening socket won't work because new
	      // connections request selecting on read.  but we uniformly
	      // want to actually read into the ring buffer for the
	      // descriptor, but we can't for a listening socket.  this
	      // results in ring_rx() returning 0, but since not reading
	      // any data in the read() call is the only way to tell when
	      // we reach eof or don't have a connection, we have to find
	      // some other way.  so we test for the error returned by
	      // listening sockets.
	      // we could also re-work ring_rx so that it distinguished
	      // between not receiving any data due to read() returning 0,
	      // and due to read() returning a negative value.  the simple
	      // way to do this would be to actually return the negative
	      // value in the latter case.  then error conditions would
	      // not cause the socket to close, but eof would.  but what
	      // if error conditions besides ENOTCONN need closing too?
	      // also, what if other conditions that returing closing also
	      // return ENOTCONN?
	      closing=1; 
	    }
	  }
	  svc = 1;
	}
	if (svc) {
	  running = svr(conn,data); // normal servicing
	}
	if (FD_ISSET(conn->s, &c_efd)) { // exception servicing
	  running = svr(conn,0); // exception servicing
	}
	if (closing) {
	  server_connection *next = conn->next;

//	  printf("Closing %d\n",conn->s);
	  NOSIG(close(conn->s));
	  lock_release_wait(conn->s);
	  server_connectionDelete(svc_head,conn);
	  delete_ring_buffer(conn->rx);
          delete_ring_buffer(conn->tx);
	  destInst(conn->inst_data);
	  free(conn);
	  conn = next;
	} else {
	  conn = conn->next;
	}
      }
    }
    // call any connections that are ready to unblock
    int n;
    server_connection *c;
    while ((n = lock_get_next_client()) >= 0) {
      //lock_release_wait(n);
      c = find_connection(svc_head[0],n);
      if (c) {
	running = svr(c,data);
      }
    }
    while ((n = watch_get_next_client()) >= 0) {
      c = find_connection(svc_head[0],n);
      if (c) {
	running = svr(c,data);
      }
    }



  }
}

/*
  Author: Michael Schmidt (michael@embeddedARM.com)
 */
