#undef G_DISABLE_ASSERT
#undef G_LOG_DOMAIN

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

#define TRUE 1
#define FALSE 0

static int n_children = 3;
static int n_active_children;
static int n_iters = 10000;

static int write_fds[1024];
static struct pollfd poll_fds[1024];

void
my_pipe (int *fds)
{
  if (pipe(fds) < 0)
    {
      fprintf (stderr, "Cannot create pipe %s\n", strerror (errno));
      exit (1);
    }
}

int
read_all (int fd, char *buf, int len)
{
  size_t bytes_read = 0;
  ssize_t count;

  while (bytes_read < len)
    {
      count = read (fd, buf + bytes_read, len - bytes_read);
      if (count < 0)
	{
	  if (errno != EAGAIN)
	    return FALSE;
	}
      else if (count == 0)
	return FALSE;

      bytes_read += count;
    }

  return TRUE;
}

int
write_all (int fd, char *buf, int len)
{
  size_t bytes_written = 0;
  ssize_t count;

  while (bytes_written < len)
    {
      count = write (fd, buf + bytes_written, len - bytes_written);
      if (count < 0)
	{
	  if (errno != EAGAIN)
	    return FALSE;
	}

      bytes_written += count;
    }

  return TRUE;
}

void
run_child (int in_fd, int out_fd)
{
  int i;
  int val = 1;

  for (i = 0; i < n_iters; i++)
    {
      write_all (out_fd, (char *)&val, sizeof (val));
      read_all (in_fd, (char *)&val, sizeof (val));
    }

  val = 0;
  write_all (out_fd, (char *)&val, sizeof (val));

  exit (0);
}

int
input_callback (int source, int dest)
{
  int val;
  
  if (!read_all (source, (char *)&val, sizeof(val)))
    {
      fprintf (stderr,"Unexpected EOF\n");
      exit (1);
    }

  if (val)
    {
      write_all (dest, (char *)&val, sizeof(val));
      return TRUE;
    }
  else
    {
      close (source);
      close (dest);
      
      n_active_children--;
      return FALSE;
    }
}

void
create_child (int pos)
{
  int pid;
  int in_fds[2];
  int out_fds[2];
  
  my_pipe (in_fds);
  my_pipe (out_fds);

  pid = fork ();

  if (pid > 0)			/* Parent */
    {
      close (in_fds[0]);
      close (out_fds[1]);

      write_fds[pos] = in_fds[1];
      poll_fds[pos].fd = out_fds[0];
      poll_fds[pos].events = POLLIN;
    }
  else if (pid == 0)		/* Child */
    {
      close (in_fds[1]);
      close (out_fds[0]);

      setsid ();

      run_child (in_fds[0], out_fds[1]);
    }
  else				/* Error */
    {
      fprintf (stderr,"Cannot fork: %s\n", strerror (errno));
      exit (1);
    }
}

static double 
difftimeval (struct timeval *old, struct timeval *new)
{
  return
    (new->tv_sec - old->tv_sec) * 1000. + (new->tv_usec - old->tv_usec) / 1000;
}

int 
main (int argc, char **argv)
{
  int i, j;
  struct rusage old_usage;
  struct rusage new_usage;
  
  if (argc > 1)
    n_children = atoi(argv[1]);

  if (argc > 2)
    n_iters = atoi(argv[2]);

  printf ("Children: %d     Iters: %d\n", n_children, n_iters);

  n_active_children = n_children;
  for (i = 0; i < n_children; i++)
    create_child (i);

  getrusage (RUSAGE_SELF, &old_usage);

  while (n_active_children > 0)
    {
      int old_n_active_children = n_active_children;

      poll (poll_fds, n_active_children, -1);

      for (i=0; i<n_active_children; i++)
	{
	  if (poll_fds[i].events & (POLLIN | POLLHUP))
	    {
	      if (!input_callback (poll_fds[i].fd, write_fds[i]))
		write_fds[i] = -1;
	    }
	}

      if (old_n_active_children > n_active_children)
	{
	  j = 0;
	  for (i=0; i<old_n_active_children; i++)
	    {
	      if (write_fds[i] != -1)
		{
		  if (j < i)
		    {
		      poll_fds[j] = poll_fds[i];
		      write_fds[j] = write_fds[i];
		    }
		  j++;
		}
	    }
	}
    }

  getrusage (RUSAGE_SELF, &new_usage);

  printf ("Elapsed user: %g\n",
	   difftimeval (&old_usage.ru_utime, &new_usage.ru_utime));
  printf ("Elapsed system: %g\n",
	   difftimeval (&old_usage.ru_stime, &new_usage.ru_stime));
  printf ("Elapsed total: %g\n",
	  difftimeval (&old_usage.ru_utime, &new_usage.ru_utime) +	   
	   difftimeval (&old_usage.ru_stime, &new_usage.ru_stime));
  printf ("total / iteration: %g\n",
	   (difftimeval (&old_usage.ru_utime, &new_usage.ru_utime) +	   
	    difftimeval (&old_usage.ru_stime, &new_usage.ru_stime)) /
	   (n_iters * n_children));

  return 0;
}

