/*
 * server.c
 * Programmers:
 *   Ernest Kim
 *   Grace Lin
 *   Joseph Kim
 * CS320
 * Final Project
 */



#include "server.h"
Player *HEAD;
int num_play;
int socketfd;

main (int argc, char *argv[]) {
  int addrlen, client_fd, cli_len, kids_ptr, count, pipes[MAX_CHILD][2], maxfd;
  int len, buf_ptr, port;
  pid_t kids[MAX_CHILD], grp_pid;
  struct sockaddr_in serv_addr, client_addr;
  char buf[BUF_SIZE], *cli_inet;
  Player *end, *new, *current, *prev; 
  fd_set fd_read;
  struct timeval timeout;
  struct hostent *client_ent;

  port = PORT;
  if (argc == 2) 
    port = atoi (argv[1]);
  num_play = 0;
  end = HEAD = NULL;
  len = kids_ptr = 0;
  FD_ZERO(&fd_read);
  serv_siginit();
  serv_buf_init(buf);
  timeout.tv_sec = 0;
  timeout.tv_usec = 250000;

  if ((socketfd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
    perror ("server.c, socket error");
    exit (1);
  }

  maxfd = socketfd;
  
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons (port) ;
  serv_addr.sin_addr.s_addr = htonl (INADDR_ANY);

  if ( bind(socketfd, (struct sockaddr *)&serv_addr, sizeof (serv_addr)) < 0) {
    perror ("server.c, bind error");
    exit (1);
  }

  listen (socketfd, MAX_CON);
  FD_SET (socketfd, &fd_read);

  while (1) {
    select (maxfd+1, &fd_read, (fd_set *) 0, (fd_set *) 0, 
	    (struct timeval *) 0);
    select (0, NULL, NULL, NULL, &timeout);
    for (prev=NULL,current=HEAD; current!=NULL; current=current->next) {
      if (FD_ISSET (current->client_fd, &fd_read)) {
	len = read (current->client_fd, buf, BUF_SIZE-1);

	/* Check for closing of socket */
	if (len==0) {

	  /* Adjust the linked list */
	  if (prev==NULL) 
	    HEAD = current->next;
	  else 
	    prev->next = current->next;

	  /* Clean up */
	  close (current->client_fd);
	  FD_CLR (current->client_fd, &fd_read);
	  if (maxfd == current->client_fd) {
	    maxfd = socketfd;
	    serv_recalc_maxfd(&maxfd);
	  }

	  serv_write_all(current->name, "logged off");
	  num_play--;
	  free(current);
	  continue;
	} /* End of checking of closing of port */
	
	
	else if  (len > 0) {
	  buf[len]='\0';
	  serv_parse (current, buf);
	  serv_buf_init (buf);
	}

	else {
	  perror("server.c after read");
	  printf("buffer: %s\n", buf);
	}
      }
      FD_SET (current->client_fd, &fd_read);
      prev=current;
    }
    
    /* New Connection */
    if (FD_ISSET(socketfd, &fd_read)) {
      cli_len = sizeof (client_addr);
      client_fd = accept (socketfd,(struct sockaddr *) &client_addr,&cli_len);
      if (client_fd < 0) {
	perror ("server.c, accept error");
	continue;
      }
      
      new = serv_new_player(client_fd);
      client_ent = gethostbyaddr((char *) &client_addr.sin_addr, 
				 sizeof(struct in_addr), AF_INET);
      if (client_ent != NULL) 
	new->hostname = strdup(client_ent->h_name);
      else
	new->hostname = strdup(inet_ntoa(client_addr.sin_addr));
      if (maxfd < client_fd) maxfd = client_fd;
      FD_SET (client_fd, &fd_read);
      num_play++;
      if (num_play == MAX_PLAYER) {
	serv_start_game();
	FD_ZERO(&fd_read);
	maxfd = socketfd;
      }
    }
    FD_SET (socketfd, &fd_read);
  }
}

void serv_parse (Player *sender, char *buf) {
  char *last;
  char *comm;
  char message[BUF_SIZE];

  buf = strtok(buf, "\r\n");

  strtok_r (buf, ":", &last);

  if (strcmp(buf, I_SET_NAME) == 0) {
    /* Set the player's name */
    sender->name = strdup (last);
    sprintf(message, "logged in from %s (%d)", sender->hostname, num_play);
    serv_write_all(last, message);
    return;
  }
  else if (strcmp (buf, I_SET_PLAY) == 0) {
    /* Set play now flag */
    sender->f_playNow = 1;
    sprintf (message, "2:play");
    write (sender->client_fd, message, strlen(message));
    return;
  }
  else if (strcmp (buf, I_CHAT) == 0) {
    /* Chat message */
    serv_write_all(sender->name, last);
    return;
  }
}
  

void serv_write_all (char *from, char *mesg) {
  Player *current;
  char obuf[BUF_SIZE];

  sprintf (obuf, "3:%s: %s\n", from, mesg);

  for (current = HEAD; current != NULL; current = current->next) 
    write (current->client_fd, obuf, strlen(obuf));
}


void serv_siginit() {
  signal (SIGHUP, terminate);
  signal (SIGSEGV, terminate);
  signal (SIGINT, terminate);
  signal (SIGCHLD, SIG_IGN);
}


void terminate() {
  Player *current;

  printf ("Pid %d: Closing ports\n", getpid());
  for (current = HEAD; current != NULL; current = current->next)
    close (current->client_fd);
  close (socketfd);
  exit (0);
}
  
    
void serv_recalc_maxfd(int *maxfd) {
  Player *current;

  for (current = HEAD; current != NULL; current = current->next) {
    if (current->client_fd > *maxfd) *maxfd = current->client_fd;
  }
}


Player *serv_new_player (int client_fd) {
  Player *new;

  new = (Player *) malloc (sizeof(Player));
  new->client_fd = client_fd;
  new->next = HEAD;
  HEAD = new;
  
  new->name = strdup ("Somebody\0");
  new->f_playNow = 0;

  return (new);
}


void serv_buf_init (char *buf) {
  int x;

  for (x=0; x< BUF_SIZE; x++) 
    buf[x]=0;
}


void serv_start_game() {
  int child_pid, num;
  Player *current, *prev;

  /* Fork and check */
  if ((child_pid=fork()) < 0) {
    perror("Error in forking, server.c");
    serv_write_all("Server","Error, closing server");
    close (socketfd);
    terminate();
    exit(1);
  }

  /* Child process */
  else if (child_pid == 0) {
    for (current=HEAD; current != NULL; current=current->next)
      current->id_num = num;
    close (socketfd);
    game_play(HEAD);
  }

  /* Parent process */
  else if (child_pid > 0) {
    for (current=HEAD; current != NULL; current=current->next) {
      close (current->client_fd);
      if (prev!=NULL) free(prev);
    }
    free (prev);
    HEAD = NULL;
    num_play = 0;
  }
}
