/* 
   This file is part of Practical Distributed Processing
   Copyright (C) 2006-2007 Phillip J. Brooke and Richard F. Paige
*/

#include "compat.h"
#include "constants.h"
#include "netstr.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

/* We will need a linked list of the sockets we're serving. */
typedef struct socket_node {
  int                 s;
  int                 closed;
  struct socket_node *next;
} snode;

/* 
   Accept multiple stream connections, and print whatever is sent to us.
*/

int main ()
{
  char *             buffer        = malloc(BUFFER_SIZE);
  fd_set             rd;
  int                i;
  int                n;
  int                on            = 1;
  int                recv_value;
  int                s1;
  int                s2;
  int                select_val;
  snode *            scurr;
  snode *            shead = NULL;
  socklen_t          sockaddr_in_len;
  struct sockaddr_in a;

  printf("Server starting...\n");

  /* Create a socket to listen on. */
  if ((s1 = socket(PF_INET, SOCK_STREAM, 0)) >= 0)
    printf("The socket has been created\n");
  else
    {
      perror("Could not create socket");
      exit(EXIT_FAILURE);
    }

  /* Reuse local addresses when binding the socket.  See socket(7). */
  if (setsockopt(s1, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    {
      perror("Problem setting socket option");
      exit(EXIT_FAILURE);
    }

  /* Describe the addresses we'll accept connections from. */
  a.sin_family = AF_INET;
  a.sin_addr.s_addr = INADDR_ANY;
  a.sin_port = htons(EXAMPLE_PORT);

  /* Bind the socket to the address. */
  if (bind(s1, (struct sockaddr *) &a, sizeof(a)) == 0)
    printf("Bound socket\n");
  else
    {
      perror("Could not bind socket");
      exit(EXIT_FAILURE);
    }

  /* Listen on the socket. */
  printf("Setting socket to listen...");
  if (listen(s1, 5) != 0)
    {
      perror("Problem listening on s1");
      exit(EXIT_FAILURE);
    }
  printf(" listening on socket\n");

  /* Now loop forever. */
  while (1) {
    /* We wait for an inbound connection, for an existing connection
       to send something, or for an existing connection to go away. */
    printf("Waiting for something to happen...\n");
    /* Set up our set of descriptors. */
    FD_ZERO(&rd);
    /* Monitor our inbound connection. */
    FD_SET(s1, &rd);
    n = s1;
    /* Walk through the list of connections. */
    scurr = shead;
    while (scurr != NULL) {
      if (!(scurr->closed)) {
	FD_SET(scurr->s, &rd);
	if (scurr->s > n) { n = scurr->s; }
      }
      scurr = scurr->next;
    }
    /* Wait forever until something happens. */
    select_val = select(n+1, &rd, NULL, NULL, NULL);
    if (select_val == -1) 
      {
	perror("Select failed!");
	exit(EXIT_FAILURE);
      }
    /* If we're here, then select returned with (hopefully) stdin or s
       having changes. */
    /* Check s1 for new inbound connections. */
    if (FD_ISSET(s1, &rd)) {
      /* Accept the next connection.  accept() shouldn't block! */
      printf("Accepting new connection...\n");
      sockaddr_in_len = sizeof(struct sockaddr_in);
      s2 = accept(s1, (struct sockaddr *) &a, &sockaddr_in_len);
      if (s2 == -1)
	{
	  perror("Problem accepting connection");
	  exit(EXIT_FAILURE);
	}
      else
	printf("Accepted connection from client on %s as socket %d\n",
	       inet_ntoa(a.sin_addr), s2);
      /* We add s2 to our linked list. */
      scurr = malloc(sizeof(snode));
      scurr->s = s2;
      scurr->closed = 0;
      scurr->next = shead;
      shead = scurr;
    }
    /* Walk through the list of connections, checking if something
       arrived. */
    scurr = shead;
    while (scurr != NULL) {
      if ((!(scurr->closed)) && FD_ISSET(scurr->s, &rd)) {
	recv_value = NSrecv(scurr->s, buffer, BUFFER_SIZE, 0);
	if (recv_value == 0) {
	  /* Close the inbound socket. */
	  printf("Closing connection for socket %d...\n", scurr->s);
	  if (close(scurr->s) != 0)
	    perror("Warning: problem closing socket");
	  /* Mark this entry as closed.  Really, we should remove
	     these entries from the linked list to avoid a memory
	     leak. */
	  scurr->closed = 1;
	} else if (recv_value < 0) {
	  perror("Problem with recv");
	  exit(EXIT_FAILURE);
	} else {
	  printf("Received from %d: %s\n", scurr->s, buffer);
	  /* Send an anagram back. */
	  {
	    int send_val;
	    char *bufferfry;
	    char *newln_position;
	  
	    /* We don't want any newlines in the anagram, so replace the
	       first (if any) with end-of-string. */
	    newln_position = strchr(buffer, '\n');
	    if (newln_position != NULL) {
	      *newln_position = '\0';
	    }
	    /* Get our anagram and send it.  Do this a random number of times*/
	    for (i = 0; i<(random()%10); i++) {
	      bufferfry = (char *)strfry(buffer);
	      printf("Sending response %d to socket %d...%s\n", 
		     i, scurr->s, bufferfry);
	      send_val = send(scurr->s, bufferfry, recv_value, 0);
	      if (send_val < 0) 
		{
		  perror("Send failed!");
		  exit(EXIT_FAILURE);
		}
	      printf("Sent response to socket %d\n", scurr->s);
	    }
	  }
	}
      }
      scurr = scurr->next;
    }
  }

  /* We never exit the while loop.  If we did, we should close
     s1 and exit. */
}
