
#include "common.h"
#include "deal.h"
#include "netserv.h"


/* global variable that points to the main host table */
struct hostinfo *tablePtr = NULL;

/* is debugging on? */
int opt_debug = 0;

/* default number of points to assign a host */
int opt_points = 1;

/* main loop. start up, wait for connections and deal with them */
int main(int argc, char *argv[])
{
  int sockfd, newsockfd, cli_len, count;
  struct sockaddr_in serv_addr, cli_addr;
  int one = 1;
  FILE *logPtr;
  struct list *fdlist = NULL;
  struct timeval timeout;
  extern char *optarg;
  int c;

  while ((c = getopt(argc, argv, "dhp:?")) != -1) {
    switch (c) {
    case 'd':
      opt_debug = 1;
      break;
    case 'h':
      usage(argv[0]);
      break;
    case 'p':
      opt_points = atoi(optarg);
      break;
    case '?':
    default:
      usage(argv[0]);
      break;
    }
  }
  if (opt_debug) {
    /* debugging message */
    fprintf(stderr,"Starting up...\n");
  }

  /* open the log file for appending */
  if ((logPtr = fopen(NLOG_FILE,"a")) == NULL) {
    fprintf(stderr,"Can't open log file %s.\n",NLOG_FILE);
    exit(-1);
  }

  /* open a socket */
  if( (sockfd = socket(AF_INET, SOCK_STREAM,0)) < 0) {
    perror("socket");
    exit(-1);
  }
  if (opt_debug) {
    fprintf(stderr,"sockfd is: %d\n",sockfd);
  }

  /* clear out and set the struct for the server address */  
  memset(&serv_addr, 0, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  serv_addr.sin_port = htons(SERV_PORT);

  /* allow the socket to be reused immediately */
  setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one));
  
  /* bind to the socket */
  if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
    perror("bind");
    exit(-1);
  }

  /* list on the socket */
  listen(sockfd,5);
  if (opt_debug) {
    fprintf(stderr,"listening on %d.\n",SERV_PORT);
  }

  /* read in the saved network status from the dump file */
  readdump();

  /* catch SIGINT and SIGHUP and exit if you get them */
  signal(SIGINT,(void *) myexit);
  signal(SIGHUP,(void *) myexit);

  /* select loop */
  for(;;){
    fd_set fdvar;
    struct list *listPtr, **lastPtr, *currPtr = fdlist;

    FD_ZERO(&fdvar);
    FD_SET(sockfd,&fdvar);
    
    while (currPtr != NULL) {
      FD_SET(currPtr->num,&fdvar);
      currPtr = currPtr->next;
    }

    /* set the timeout to wait for the client to send something */
    timeout.tv_sec = TIMEOUT;
    timeout.tv_usec = U_TIMEOUT;

    /* check what file descriptors need managing */
    count = select(NOFILE,(fd_set *)&fdvar,(fd_set *)0,(fd_set *)0,&timeout);
    if (count < 0) { 
      perror("select");
      exit(-1); 
    } 
    if (count == 0){
      time_t currtime =  time((time_t *)0);
      lastPtr = &fdlist;
      while (*lastPtr != NULL) {
	if ((currtime - TIMEOUT) > (*lastPtr)->ctime) {
	  if (opt_debug) {
	    fprintf(stderr,"Timeout on sockfd %d.\n",(*lastPtr)->num);
	  }
	  close((*lastPtr)->num);
	  currPtr= *lastPtr;
	  *lastPtr = (*lastPtr)->next;
	  free(currPtr);
        }
        else {
	  lastPtr = &(*lastPtr)->next;
        }
      }
    }
    else { 
      char caddr_new[MAXADDR];
      struct hostent *hp;
      if (FD_ISSET(sockfd,&fdvar)) {
	cli_len = sizeof(cli_addr);
	if ((newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, 
				&cli_len)) < 0) {
	  perror("accept");
	  exit(-1);
	}
	if (opt_debug) {
	  fprintf(stderr,"newsockfd is %d.\n", newsockfd);
	}
	/* convert the IP address of the host that connected to its cname. */
	hp = gethostbyaddr((const char *) &cli_addr.sin_addr, sizeof(cli_addr.sin_addr), AF_INET);
	strcpy(caddr_new,hp->h_name);

	if (opt_debug) {
	  fprintf(stderr,"%s connected.\n",caddr_new);
	}
    
	listPtr = (struct list *) malloc(sizeof(struct list));
	listPtr->num = newsockfd;
	listPtr->ctime = time((time_t *)0);
	listPtr->next = fdlist;
	fdlist = listPtr;
      }
      
      lastPtr = &fdlist;
      while (*lastPtr != NULL) {
	if (FD_ISSET((*lastPtr)->num,&fdvar)) {
	  char buf[MAXLINE] = "\0";
	  read((*lastPtr)->num, buf, MAXLINE);
	  deal(caddr_new, (*lastPtr)->num, (*lastPtr)->ctime,buf,logPtr);
	  close((*lastPtr)->num);
	  currPtr= *lastPtr;
	  *lastPtr = (*lastPtr)->next;
	  free(currPtr);
	}
	else {
	  lastPtr = &(*lastPtr)->next;
	}
      }
    }
  }

  close(sockfd); 
  fclose(logPtr);
}

/* print a usage message */
void usage(char *name) {
  fprintf(stderr, "Usage: %s [-dh]\n"
          "       -d Turn on debugging.\n"
          "       -h Get this help message.\n",name);
  myexit();
}



/* check what we received and call the appropriate function */
int deal(char caddr_new[MAXADDR], int newsockfd, time_t ntime, char buf[MAXLINE], FILE *logPtr) {
  int reply_len, retval;
  char reply[MAXLINE] = "\0";
  ntime = time((time_t *)0);

  /* check for a join request */
  if ((strncmp(buf,"join",4)) == 0) {

    /* get the number of points if given */
    int num;
    char s[5];
    if (sscanf(buf, "%s %d", s, &num) != 2) {
      num = opt_points;
    }
    /* try to add the host to the network */
    retval = addhost(caddr_new,ntime,num);
    
    /* if the host was added... */
    if ( retval == 0) {
      
      /* Write this to the log file and flush it */
      fprintf(logPtr,"+ %s %d %d\n",caddr_new,(int) ntime, num);
      fflush(logPtr);

      /* reply to the client */
      sprintf(reply, "200 You have been added to the network.\n");
      reply_len = strlen(reply);
      write(newsockfd,reply,reply_len);
      if (opt_debug) {
	fprintf(stderr,"%s joined at %d with %d points.\n",caddr_new,(int) ntime,num);
      }
    }

    /* if the host is already in the network, tell the client that */
    else if (retval == 1){
      sprintf(reply, "401 Duplicate address, not added.\n");
      reply_len = strlen(reply);
      write(newsockfd,reply,reply_len);
      if (opt_debug) {
	fprintf(stderr,"Duplicate request from %s at %d.\n",caddr_new,(int) ntime);
      }
    }
    /* for other errors */
    else {
      fprintf(stderr,"Couldn't add new host %s to network at %d.\n", caddr_new,(int) ntime);
    }
  }

  /* check for dele requests */
  else if ((strncmp(buf,"dele",4)) == 0) {

    /* try to delete the host from the network */
    retval = delhost(caddr_new);

    /* if it was successfully deleted... */
    if ( retval == 0 ){

      /* write this to the log file and flush it */
      fprintf(logPtr,"- %s %d\n",caddr_new,(int) ntime);
      fflush(logPtr);

      /* reply to the client */
      sprintf(reply, "200 You have been deleted from the network.\n");
      reply_len = strlen(reply);
      write(newsockfd,reply,reply_len);
      if (opt_debug) {
	fprintf(stderr,"%s deleted at %d.\n",caddr_new,(int) ntime);
      }
    }

    /* if the address was not in the network, tell the client that */
   else if (retval == 1){
      sprintf(reply, "401 No such address, not deleted.\n");
      reply_len = strlen(reply);
      write(newsockfd,reply,reply_len);
      if (opt_debug) {
	fprintf(stderr,"No such address to delete as %s at %d.\n",caddr_new,(int) ntime);
      }
    }
    
    /* for other errors */
    else {
      fprintf(stderr,"Couldn't delete host %s from network at %d.\n",caddr_new,(int) ntime);
    }
  }

  /* check for list requests */
  else if ((strncmp(buf,"list",4)) == 0){

    /* print the list out to this connection */
    if ((list(newsockfd)) == 0){
      if (opt_debug) {
	fprintf(stderr,"%s requested a listing at %d.\n",caddr_new,(int) ntime);
      }
    }
  }

  /* check for help requests */
  else if ((strncmp(buf,"help",4)) == 0){

    /* reply to the client */
    sprintf(reply,"100 Possible commands are:\n\t join [<points>]\n\t dele\n\t list\n\t since <time>\n\t help\n");
    reply_len = strlen(reply);
    write(newsockfd,reply,reply_len);
    if (opt_debug) {
      fprintf(stderr,"Help request from %s at %d.\n",caddr_new,(int) ntime);
    }
  }

  /* check for since requests */
  else if ((strncmp(buf,"since",5)) == 0) {
    /* get the time argument */
    int *stimePtr = malloc(sizeof(int));
    char s[6];
    if (sscanf(buf,"%s %d",s,stimePtr) == 2) {
    
      /* print the since information out to this connection */
      if ((since(newsockfd,*stimePtr)) == 0) {
	if (opt_debug) {
	  fprintf(stderr,"Since %d request from %s at %d.\n",*stimePtr,caddr_new,(int) ntime);
	}
      }
    }
    
    /* for other errors */
    else {
      sprintf(reply, "400 Invalid since request.\n");
      reply_len = strlen(reply);
      write(newsockfd,reply,reply_len);
      if (opt_debug) {
	fprintf(stderr,"Invalid since request from %s at %d.\n",caddr_new,(int) ntime);
      }
    }
  }

  /* otherwise, we have an unknown request */
  else {
    /* reply to the client */
    sprintf(reply, "400 Unknown request.\n");
    reply_len = strlen(reply);
    write(newsockfd,reply,reply_len);
    if (opt_debug) {
      fprintf(stderr,"Unknown request from %s at %d.\n",caddr_new,(int) ntime);
    }
  }
  return(0);
}


/* dump and exit on a signal */
int myexit() {
  if (opt_debug) {
    fprintf(stderr,"exiting.\n");
  }
  if ((dump()) != 0) {
    fprintf(stderr,"Error in dumping network status, data may be lost.\n");
  }
  exit(0);
}
