/**
 * server.c, copyright 2001 Steve Gribble
 *
 * The server is a single-threaded program.  First, it opens
 * up a "listening socket" so that clients can connect to
 * it.  Then, it enters a tight loop; in each iteration, it
 * accepts a new connection from the client, reads a request,
 * computes for a while, sends a response, then closes the
 * connection.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/time.h>

#include "SocketLibrary/socklib.h"
#include "common.h"
#include "threadpool.h"

//#define DEBUG
const int TRIGGER_COUNT = 50;   /* number of dispatches between time checks */

extern int errno;

int   setup_listen(char *socketNumber);
char *read_request(int fd);
char *process_request(char *request, int *response_length, int *num_loops);
void  send_response(int fd, char *response, int response_length);

void  run_server(int socket, int pool_size, int num_loops, int checks);
void  process_connection(void * sockp);

struct proc_conn_args {
  int socket;     /* socket to talk on */
  int num_loops;  /* number of loop iterations (simulated work) to do */
};

/**
 * This program should be invoked as 
 *    "./server <socketnumber> <poolsize> <num_loops> <checks>", 
 *
 * where
 *    poolsize      is number of threads in threadpool
 *    num_loops     is the number of loops in the 'process_request' fn
 *    checks        is the number of time checks (datapoints) before
 *                    shutting down.  If 0 or omitted, run indefinitely.
 *
 * For example, "./server 4342 10 50 10000"
 */

int main(int argc, char **argv)
{
  int  socket_listen;
  
  int num_loops;    /* Number of loop iterations in 'process_request' */
  int pool_size;    /* Number of threads in pool */
  int checks = 0;   /* Number of datapoints to take before exiting */
  
  if (argc != 4 && argc != 5)
  {
    fprintf(stderr, 
        "(SERVER): Invoke as  './server socknum poolsize num_loops [checks]'\n");
    fprintf(stderr, "(SERVER): for example, './server 4444 8 1000 100'\n");
    exit(-1);
  }

  /*
   * Get args 'pool_size', 'num_loops', and 'checks'; and sanity-check them
   */
  if ((pool_size = atoi(argv[2])) < 1 ) {
    fprintf(stderr, "(SERVER): pool size must be > 1\n");
    exit(-1); 
  }
  if ((num_loops = atoi(argv[3])) < 1) {
    fprintf(stderr, "(SERVER): number of loops must be > 1\n");
    exit(-1);
  }
  if (4 == argc)
      checks = 0;
  else /* argc is 5 */ if ((checks = atoi(argv[4])) < 0) {
    fprintf(stderr, "(SERVER): checks must be >= 0\n");
    exit(-1);
  } 
  
  /* 
   * Set up the 'listening socket'.  This establishes a network
   * IP_address:port_number that other programs can connect with.
   */
  socket_listen = setup_listen(argv[1]);
#ifdef DEBUG
      fprintf(stderr, "(SERVER) Listening on socket %d\n", socket_listen);
      fflush(stderr);
#endif

  run_server(socket_listen, pool_size, num_loops, checks);

  close(socket_listen);
  return(0);
}

/**
 * This function runs the server, with configuration given by:
 *   socket   : socket to listen on
 *   pool_size: number of threads in the pool
 *   num_loops: number of loop iterations in 'process_connection'
 *   checks   : number of datapoints (time checks) to take
 */
void  run_server(int socket, int pool_size, int num_loops, int checks)
{
  int             socket_talk;      /* socket we reply on */
  threadpool      pool;             /* pool of 'pool_size' threads */
  int             dispatch_count;   /* # of dispatches since last sample */
  struct timeval  then, now, diff;  /* benchmarking timestamps */
  int             do_forever;       /* flag: loop forever? */
  struct proc_conn_args pcargs;     /* holds args for 'process_connection' */
  int count = checks;      
  /* Set up threadpool */
  if ( NULL == (pool = create_threadpool(pool_size))) {
   fprintf(stderr, "(SERVER): Unable to create threadpool.\n");
   exit(-1); 
  }
  
  /* Set up do_forever */
  /* if checks == 0, do_forever = 1 (true); otherwise, the number of */
  /* times through the loop to follow is set by checks. */
  do_forever = (checks==0);
  
  /* MAIN LOOP: do this 'checks' times, or forever if checks == 0
   *  1) Wait on the socket for a new connection to arrive.
   *  2) Get a socket for replying.
   *  3) Dispatch a thread to process request and make reply.
   */
  gettimeofday(&then, NULL);   /* init throughput vars */
  dispatch_count = TRIGGER_COUNT;
  while(do_forever || (checks > 0)) {
    socket_talk = saccept(socket);
    if (socket_talk < 0) {
      fprintf(stderr, "(SERVER) Connection failed because of ");
      perror("");
      exit(1);
    }
    /* Set up arg struct for 'process connection' */
    pcargs.socket = socket_talk;
    pcargs.num_loops = num_loops;
    
    //fprintf(stderr,"Dispatching\n");
    dispatch(pool, process_connection, &pcargs);
    //fprintf(stderr,"Dispatch ends\n");

    //if ((count % 10) == 0)
    //{
    //fprintf(stderr,"Count remaining = %d\n",count);
    //}
    if (count == 100 )
    gettimeofday(&then,NULL);
    if (count == 50 )
     {
      double seconds;  //elapsed time in seconds between then and now
      gettimeofday(&now,NULL);
      timersub(&now, &then, &diff);
      seconds = diff.tv_sec + diff.tv_usec * 0.000001;

      fprintf(stdout, "Loops: %6d  Threads: %2d Dispatches/sec: %10.3f\n",
          num_loops, pool_size, 50/seconds);
     }      
   
    
    /* reset dispatch count, update checks left*/
    dispatch_count = TRIGGER_COUNT;
    --checks;
    --count;
 }
  
}


/**
 * This function implements the action of a thread when processing
 * a new connection.  Its type must be 'dispatch_fn', defined as
 *   void (*dispatch_fn)(void *).  The argument is a socket identifier,
 *   that is, an int.
 * See comments for main() for explanation of steps.
 */
void process_connection(void * args)
{
    char  *request = NULL;
    char  *response = NULL;

    struct proc_conn_args *myargs = (struct proc_conn_args *)args;
    int    socket_talk = myargs->socket;
    int    num_loops = myargs->num_loops;
    
#ifdef DEBUG
    fprintf(stderr, "(SERVER) Processing request on socket %d\n", socket_talk);
#endif
    request = read_request(socket_talk);
    if (request != NULL) {
      int response_length;

      response = process_request(request, &response_length, &num_loops);
      if (response != NULL) {
	send_response(socket_talk, response, response_length);
      }
    }
    close(socket_talk);

    /* clean up allocated memory, if any */
    if (request != NULL)
      free(request);
    if (response != NULL)
      free(response);   
}


/**
 * This function accepts a string of the form "5654", and opens up
 * a listening socket on the port associated with that string.  In
 * case of error, this function simply bonks out.
 */

int setup_listen(char *socketNumber) {
  int socket_listen;

  if ((socket_listen = slisten(socketNumber)) < 0) {
    perror("(SERVER): slisten");
    exit(1);
  }

  return socket_listen;
}

/**
 * This function reads a request off of the given socket.
 * This function is thread-safe.
 */

char *read_request(int fd) {
  char *request = (char *) malloc(REQUEST_SIZE*sizeof(char));
  int   ret;

  if (request == NULL) {
    fprintf(stderr, "(SERVER): out of memory!\n");
    exit(-1);
  }

  ret = correct_read(fd, request, REQUEST_SIZE);
  if (ret != REQUEST_SIZE) {
    free(request);
    request = NULL;
  }
  return request;
}

/**
 * This function crunches on a request, returning a response.
 * This is where all of the hard work happens.  
 * This function is thread-safe.
 */

char *process_request(char *request, int *response_length, int *num_loops) {

  char *response = (char *) malloc(RESPONSE_SIZE*sizeof(char));
  int   loops = *num_loops;
  int   i,j;


  if (response ==  NULL){
    fprintf(stderr, "(SERVER): out of memory!\n");
    exit(-1);    
  }

  // just do some mindless character munging here
  for (i=0; i<RESPONSE_SIZE; i++){
    response[i] = request[i%REQUEST_SIZE];
  }
  for (j=0; j<loops; j++) {
    for (i=0; i<RESPONSE_SIZE; i++) {
      char swap;

      swap = response[((i+1)%RESPONSE_SIZE)];
      response[((i+1)%RESPONSE_SIZE)] = response[i];
      response[i] = swap;
    }
  }
  *response_length = RESPONSE_SIZE;
  return response;
}

