#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/stat.h>

int next_free_pipe = 0;
FILE *to_app[3], *from_app[3];

// application-specific data: arguments and results
int arg1[3], arg2[3], result[3];

// The actual application is placed here for now but could just as easily be
// a complete external program invoked by 'ssh'.  It receives its parameters
// and returns its results on stdin and stdout.  Simplest unix paradigm possible.

int app_global_evaluation_move_finder (int argc, char **argv) { // just like 'main()'
   int i, j;

   // This is for use with a scrabble move finder which takes a board
   // description and other parameters on stdin, and writes its results
   // to stdout - this trivial 'add two numbers' is just a placeholder for
   // debugging the method...

   // NOTE: the application should not produce *any* output until it is ready
   // to produce all the output.

   // The ultimate intention is to invoke the move finder via calls to 'ssh'
   // and have multiple copies of it execute in parallel on other systems...

   fscanf (stdin, "%d", &i);
   fscanf (stdin, "%d", &j);
   sleep (i);			// simulate a lot of computation...
   fprintf (stdout, "%d\n", i + j);
   exit (EXIT_SUCCESS);
   return (EXIT_FAILURE);
}

// These two procedures (app_add and app_collect_results) have to be in the calling
// program and need to know the specifics of how parameters are passed in to the app
// and returned from the app via stdio.  Other than these interface procedures, the
// rest of this framework is more or less independent of the details of the application.

int new_process (void);
void app_add (int a, int b) {
   int instance = new_process ();

   arg1[instance] = a; arg2[instance] = b;	// save parameters to be used once result is calcuated

   // Application-specific transmission of parameters to app:
   fprintf (to_app[instance], "%d\n%d\n", a, b); fflush (to_app[instance]);

   return;
}

void cleanup (int pipeno);
void app_collect_result (int pipeno) { // read the whole stream unconditionally
   // Application-specific collection of results from app:
   fscanf (from_app[pipeno], "%d", &result[pipeno]);
   fprintf (stderr, "%d + %d = %d\n", arg1[pipeno], arg2[pipeno], result[pipeno]); // or save and display later?
   fflush (stderr);
   cleanup (pipeno);
}

char *fifo[3];
char *progname;

int new_process (void)
{
   static char command[1024];
   int status;

   fifo[next_free_pipe] = tempnam ("/tmp", "fifo_");
   if (fifo[next_free_pipe] == NULL) {
      perror ("tempnam");
      exit (EXIT_FAILURE);
   }
   status = mkfifo (fifo[next_free_pipe], S_IWUSR | S_IRUSR /* | S_IRGRP | S_IROTH */ );
   if (status == -1) {
      perror ("mkfifo");
      exit (EXIT_FAILURE);
   }

   sprintf (command, "%s app_main < %s", progname, fifo[next_free_pipe]);
   from_app[next_free_pipe] = popen (command, "r");
   if (from_app[next_free_pipe] == NULL) {
      perror ("popen");
      exit (EXIT_FAILURE);
   }
   setvbuf (from_app[next_free_pipe], 0, _IOLBF, 0);

   to_app[next_free_pipe] = fopen (fifo[next_free_pipe], "w");
   setvbuf (to_app[next_free_pipe], 0, _IOLBF, 0);

   return next_free_pipe++;
}

int data_available (int pipeno)
{				// poll for first piece of output,
   struct pollfd fds[1];

   if (from_app[pipeno]) {
      fds[0].fd = fileno (from_app[pipeno]);
      fds[0].events = POLLIN;
      fds[0].revents = 0;
      return ((poll (fds, 1, 0) > 0) && (fds[0].revents & POLLIN));
   } else return (0 != 0);
}

void cleanup (int pipeno)
{
   fclose (to_app[pipeno]);
   pclose (from_app[pipeno]);
   from_app[pipeno] = NULL;	// mark stream so it will not be read from again
   remove (fifo[pipeno]);
   free (fifo[pipeno]);
}

void collect_all_results (void)
{
   int pipeno, reaped = 0;

   while (reaped < next_free_pipe) {
      // wait for any process to have data, then gobble it.
      for (pipeno = 0; pipeno < next_free_pipe; pipeno++) {
	 if (data_available (pipeno)) {
            // could handle result here or store and handle all results at end.
	    app_collect_result (pipeno);
	    reaped++;
	 }
      }
      usleep (10);		// Centisecond sleep not significant in a table game
   }
}

int main (int argc, char **argv)
{
   if ((argc == 2) && (strcmp (argv[1], "app_main") == 0)) {
      exit (app_global_evaluation_move_finder (argc, argv));
   } else if (argc == 1) {
      int i;
      /* test code - send multiple pairs of ints, from array, save results in 
         indexed slots have the app do a random wait (within bounds) to
         simulate cpu expenditure be prepared for the case where not complete 
         in time? have app send intermediate results if useful? */
      for (i = 0; i < 2; i++) { // test that we can perform the whole sequence repeatedly
	 next_free_pipe = 0; // MUST be reset for each cycle
         progname = argv[0];

         app_add (2, 9);
         app_add (5, 3);
         app_add (1, 4);

         collect_all_results ();	// could code this to EITHER handle results
				// incrementally or all at end depending on program logic

      }
      exit (EXIT_SUCCESS);
   } else {
      fprintf (stderr, "syntax: apptest\n");
      exit (EXIT_FAILURE);
   }
   return (EXIT_FAILURE);
}