/* **************************************************************************
 * Tricky-Eights Dispatch Program -- te_dispatch.c
 * 
 *      This program executes the a tricky-eights server and four
 * client programs, and routes the standard input/output between the
 * programs.
 *
 *      This code is unix specific -- too bad.
 *
 * Written by:  PAJ -- University of Utah   January 14, 1998.
 * Modified by: no one yet!  :)
 *
 * Makeline for SGI's:  gcc -Wall -o te_dispatch -mips2 -O32 te_dispatch.c
 ************************************************************************* */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/stat.h>

/* **************************************************************************
 * Global variables
 ************************************************************************* */

char players[] = "nesw";
char path [5][100];
char seed[100];
int pid [5] = {0, 0, 0, 0, 0};
int alive[5] = {1, 1, 1, 1, 1};
int pipe_fd [2];
int dispatch_write_end [5];
int dispatch_read_end [5];
int client_write_end [5];
int client_read_end [5];
int killed = 0;

/* **************************************************************************
 * Helper functions
 ************************************************************************* */

int player_to_int (char p)
{
  int c;

  /* Find the integer mapping to the character. */

  for (c = 0; c < 4; c++)
    if (players[c] == p)
      return c + 1;

  /* Not found, indicate error by returning -1. */

  return -1;
}


void check_children ()
{
  int c, status;

  for (c = 0; c < 5; c++)
  {
    if (alive[c] == 0 || pid[c] == 0)
      continue;

    if (pid[c] == waitpid (pid[c], &status, WNOHANG))
    {
      alive[c] = 0;
      fprintf (stderr, "Child process %i, pid %i, program %s terminated.\n", c, pid[c], path[c]);
      pid[c] = 0;
    }
  }
}


void killall (int sig)
{
  /* Mark the program as dead, and return from this function so we can
     quit in non-interrupt mode. */

  killed = 1;

  return;
}


/* **************************************************************************
 * Main function
 ************************************************************************* */

int main (int argc, char ** argv)
{
  int c, d, child, done, target, allow;
  struct stat info;

  char fromserver[500], toserver[500], errormessage[500];
  int fromsize, tosize;

  /* Make sure there are enough parameters, copy them out. */
  
  if (argc < 6)
  {
    fprintf (stderr, "Usage:  te_dispatch server client client client client [seed]\n");
    exit (-1);
  }

  for (c = 1; c <= 5; c++)
    strcpy (path[c-1], argv[c]);
  
  if (argc >= 7)
    strcpy (seed, argv[6]);

  /* Catch kills. */

  signal (SIGINT, killall);
  
  /* Make pipes. */

  for (c = 0; c < 5; c++)
  {
    if (pipe(pipe_fd) != 0)
    {
      fprintf (stderr, "Unable to create pipe pair #a%i.\n", c+1);
      exit (-1);
    }

    dispatch_read_end[c] = pipe_fd[0];
    client_write_end[c] = pipe_fd[1];
  }

  for (c = 0; c < 5; c++)
  {
    if (pipe(pipe_fd) != 0)
    {
      fprintf (stderr, "Unable to create pipe pair #b%i.\n", c+1);
      exit (-1);
    }

    client_read_end[c] = pipe_fd[0];
    dispatch_write_end[c] = pipe_fd[1];
  }

  
  /* Fork off the processes, set up i/o pipes, exec the children. */
  

  for (c = 0; c < 5; c++)
  {
    child = fork ();

    if (child)
      pid[c] = child;

    if (pid[c] == 0) 
    {
      sleep (1);
      dup2 (client_read_end[c], STDIN_FILENO);
      dup2 (client_write_end[c], STDOUT_FILENO);
      for (d = 5; d--;)
	close (dispatch_write_end[d]);
      fprintf (stderr, "Child %i, pid %i, program %s starting...\n", c, (int) getpid (), path[c]);
      execl (path[c], path[c], c == 0 ? seed : "0", NULL);
      exit (-1);
    }
  }

  sleep (0);
  sleep (0);
  sleep (0);
  sleep (0);
  sleep (0);
  sleep (2);
  
  /* Loop until the server quits, dispatching text. */
  /*   If any client quits first, error until the server quits. */

  done = 0;
  fromsize = 0;
  tosize = 0;
  target = 1;
  allow = 0;
  
  while (!done)
  {
    /* Read messages. */

    fstat (dispatch_read_end[0], &info);
    if (info.st_size > 0)
      fromsize += read (dispatch_read_end[0], fromserver+fromsize,
			500-fromsize < info.st_size ? 500-fromsize : info.st_size);
			    
    fstat (dispatch_read_end[target], &info);
    if (info.st_size > 0)
      tosize += read (dispatch_read_end[target], toserver+tosize,
		       500-tosize < info.st_size ? 500-tosize : info.st_size);

    /* Check for redirect command from server. */

    /* fprintf (stderr, "%3i %3i %3i %9i\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", fromsize, tosize, target, count++);
    fflush (stderr); */
    
    if (fromserver[0] == '#' && player_to_int (fromserver[2]) > 0)
    {
      target = player_to_int (fromserver[2]);

      /* Flush unneeded input. */
      /*     
      tosize = 0;
      do
      {
	fstat (dispatch_read_end[target], &info);
	if (info.st_size > 0)
	  read (dispatch_read_end[target], toserver,
		500 < info.st_size ? 500 : info.st_size);
      } while (info.st_size); */

      allow = 1;
    }

    /* Echo completed server lines. */

    for (c = 0; c < fromsize; c++)
      if (fromserver[c] == '\n')
	break;

    if (c++ < fromsize)
    {
      if (fromserver[0] != '#')
      {
	write (dispatch_write_end[target], fromserver, c);
	write (STDERR_FILENO, fromserver, c);
      }
	
      for (d = 0; c < fromsize; c++, d++)
	fromserver[d] = fromserver[c];

      fromsize = d;

      sleep (0);
    }
    
    /* Echo completed client lines. */

    for (c = 0; c < tosize; c++)
      if (toserver[c] == '\n')
	break;

    if (c++ < tosize)
    {
      if (allow)
      {
        write (dispatch_write_end[0], toserver, c);
        write (STDERR_FILENO, toserver, c);

	allow--;
      }

      for (d = 0; c < tosize; c++, d++)
	toserver[d] = toserver[c];

      tosize = d;

      sleep (0);
    }

    /* Error dumps. */

    if (fromsize >= 500)
    {
      sprintf (errormessage, "!!! Server overflowed output buffer.\n");
      write (dispatch_write_end[0], errormessage, strlen(errormessage));
      fprintf (stderr, "Server overflowed output buffer.\n");
    }
  
    if (tosize >= 500)
    {
      sprintf (errormessage, "!!! Player %c, program %s overflowed output buffer.\n", players[target-1], path[target]);
      write (dispatch_write_end[0], errormessage, strlen(errormessage));
      fprintf (stderr, "!!! Player %c, program %s overflowed output buffer.\n", players[target-1], path[target]);
    }
    
    /* See if we are done. */
    
    check_children ();

    if (alive[0] == 0)
      done++;
    else
      for (c = 1; c <= 4; c++)
	if (alive[c] == 0)
	{
	  sprintf (errormessage, "!!! Player %c, program %s terminated early.\n", players[c-1], path[c]);
          write (dispatch_write_end[0], errormessage, strlen(errormessage));
	}

    if (killed > 0 && killed < 5)
    {
      sprintf (errormessage, "!!! User termination.\n");
      write (dispatch_write_end[0], errormessage, strlen(errormessage));

      sleep (0);
    }
    else if (killed >= 5 && killed < 8)
      sleep (1);
    else if (killed == 10)
      break;
  }
  
  fprintf (stderr, "Exiting.\n");

  /* Kill clients. */

  for (c = 5; c--;)
    if (pid[c] != 0)
      kill (pid[c], SIGKILL);

  /* All done -- end the program without error. */

  return 0;
}


