
#include <mpi.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>

/* These designate how long the threads wait before successive operations. */
#define SLEEPTIME1 1000000 
#define SLEEPTIME2 4000000
#define SLEEPTIME3 1

/* These are flags that designate different types of messages exchanged
   between the threads. */
#define NEWTASK 1
#define NEWREQ 2
#define TASK_ASSIGN 3

void producer();
void consumer(int);
void master();
void list_remove(MPI_Status *stat, int id);
void list_insert(MPI_Status *stat, int cons, int oldval, int newval);
void list_init();

struct listelt {
  int oldval;
  int newval;
  struct listelt *next;
  struct listelt *prev;
};
struct listelt *head[4];
struct listelt *tail[4];

void list_init()
/* Function to initialize the task queues. */
{
  int i;
  for (i=0;i<4;i++) {
    head[i] = NULL;
    tail[i] = NULL;
  }
}

main(int argc, char **argv)
{
   int node;
   
   /* MPI initialization functions. */
   MPI_Init(&argc,&argv);
   MPI_Comm_rank(MPI_COMM_WORLD, &node);

   /* Each thread starts execution at main and figures out its role
      depending on its node-id. Node 0 is the master function that
      maintains the task queues and performs the walk-thru function.
      Node 1 is the producer function and nodes 2-5 are the consumers. */
   if (node == 0) {
     master();
   }
   else if (node == 1) {
     producer();
   } else {
     consumer(node);
   }
   /* Terminate execution. */
   MPI_Finalize();
}

void master()
/* The master function that receives task-enqueue messages from the producer
   and requests from the consumers for tasks. It also walks through the
   data structure performing frequent updates to the values. */
{
  struct timespec sleeptime;  /* This is used to determine the length of pauses */
  int i, j;
  struct listelt *p;
  int norecv1 = 0;
  int norecv2 = 0;

  /* The variables below are used to set up the data structures required
     for the buffers and condition signals involved in message transfers. */
  int intarr1[3];
  int intarr2[3];
  MPI_Request req1, req2;
  int flag1, flag2;
  MPI_Status stat1, stat2;

  list_init();
  sleeptime.tv_sec=0;     /* Initialize the variable for how long we sleep. */
  srand48(300);           /* Initialize the random number generator. */

  for (j=0;j<2000;j++) {  /* Iterating 2000 times for now. */
    printf("Entering iteration %d\n",j);
    /* Receive message from the producer. This is a non-blocking
       receive. See the man pages for details. Essentially, the
       message receive is started in the background. We will check
       later to see if a message was received. An iteration must
       start a new message-receive operation only if the previous
       one has already yielded a message. When the message is
       received, the contents of the packet are stored in the intarr1
       variable. This operation is trying to receive a message of
       type "NEWTASK" from any of the other nodes (it is looking for
       three integer variables). NEWTASKS are sent to the master
       thread by the producer thread. The variable req1 will store the
       status of the message. */
    if (!norecv1)
      MPI_Irecv(intarr1,3,MPI_INT,MPI_ANY_SOURCE,NEWTASK,MPI_COMM_WORLD,&req1);

    /* Receive message from the consumers as well. You need to provide
       the code for this. */

    /* While messages are being received in the background, walk through
       the entire data structure and increment all the newvals. */
    for (i=0;i<4;i++) {
      p = head[i];
      while (p) {
        p->newval++;
        p=p->next;
      }

      /* Now sleep for a random number of nano seconds. */
      sleeptime.tv_nsec = (long)((double)SLEEPTIME3 * (double)drand48());
      nanosleep(&sleeptime,NULL);
    }

    /* Test req1 to see if a NEWTASK message has been received. If yes,
       then call the list_insert function to enqueue this new task. See
       the man pages for more details. */
    MPI_Test(&req1,&flag1,&stat1);
    if (flag1) {
      list_insert(&stat1,intarr1[0],intarr1[1],intarr1[2]);
      norecv1 = 0;    /* Keep track of the fact that a message was
                         received. This means you'll have to fire up
			 a new MPI_Irecv when you loop back to the
			 next iteration. */
    }
    else
      norecv1 = 1;

    /* Similarly, take the necessary action if a request message is
       received from the consumer thread and call the list_remove
       function to handle it. You need to provide the code for this. */

  }

  /* Tie up loose ends and finish. */
  MPI_Cancel(&req1);
  MPI_Cancel(&req2);
  printf("Done with master\n");
}

void list_insert(MPI_Status *stat, int cons, int oldval, int newval)
/* Function that accepts a new task and queues it up. */
{
  struct listelt *p;
  struct listelt *q;
  struct listelt *elt;

  /* Just a sanity check. We should be receiving NEWTASKS from only
     the producer node. */
  if (stat->MPI_SOURCE != 1) {
    printf("The task did not emerge from the producer thread??!!!\n");
    exit;
  }

  /* The three integers sent in the message correspond to the consumer id
     and the oldval and newval. Set up the new task element and queue
     it up at the tail of the task queue for that consumer. */
  p = head[cons];
  q = tail[cons];
  elt = (struct listelt *)malloc(sizeof(struct listelt));
  elt->oldval = oldval;
  elt->newval = newval;

  if (q == NULL) {   /* If the queue is empty, update the head and tail. */
    head[cons] = elt;
    tail[cons] = elt;
    elt->next = NULL;
    elt->prev = NULL;
  }
  else {             /* If the queue is non-empty, just update the tail. */
    q->next = elt;
    elt->prev = q;
    elt->next = NULL;
    tail[cons] = elt;
  }
}

void producer()
/* Function that reads in the input file and sends the new tasks to the
   master thread. */
{
  FILE *fp;
  int cons, newval, oldval;
  struct timespec sleeptime;
  int intarr[3];

  sleeptime.tv_sec=0;
  srand48(100);

  fp = fopen("input","r");    /* Open the input file. */

  while (fscanf(fp,"%d %d %d",&cons, &oldval, &newval) != EOF) {
  /* Keep reading three integers at a time until you hit the end of the file. */

    /* Set up the data structure that is going to be included in the packet. */
    intarr[0]=cons;
    intarr[1]=oldval;
    intarr[2]=newval;
    /* See man pages for more details. The Send below is shipping off the
       packet with three integer values to node 0 (the master) and this is
       a message of type "NEWTASK". */
    MPI_Send(intarr,3,MPI_INT,0,NEWTASK,MPI_COMM_WORLD);
    printf("Threadp inserted %d %d %d\n",cons, oldval, newval);

    /* Then sleep for a random number of nanoseconds and read the input again.*/
    sleeptime.tv_nsec = (long)((double)SLEEPTIME1 * (double)drand48());
    nanosleep(&sleeptime,NULL);
  }
  fclose(fp);
  printf("Done with producer\n");
}

void list_remove(MPI_Status *stat, int id)
/* This function is called by the master thread when it receives a
   request from a consumer thread. The arguments are the data structures
   for the received message and the consumer id. */
{
  int intarr[3];
  struct listelt *p;

  /* If sender's node id is 2, it corresponds to consumer#0 and so on.
     Confirm that this relationship holds true. This check is not
     necessary, btw...it's just another sanity check. */
  if (stat->MPI_SOURCE != (id+2)) {
    printf("The request id does not match the source??!!\n");
    exit;
  }

  /* Now walk the task queue for that consumer. Remove the head of the
     queue and send it back to the consumer. */
  p = head[id];

  if (head[id]) {              /* There is a task. */
    head[id] = head[id]->next;
    if (head[id]) {            /* There are at least two tasks. */
      head[id]->prev = NULL;
    }
    else {           /* There was only one task -- the queue is now empty. */
      tail[id] = NULL;
    }
    /* Set up the packet that is going to be sent back to the consumer. */
    intarr[0] = id;
    intarr[1] = p->oldval;
    intarr[2] = p->newval;
    
    /* Send the packet to the consumer. You must provide the code for this. */

    free(p);
  }
  else {   /* There was no task for the consumer. */
    intarr[0] = -2;
    
    /* Set the value of one of the packet elements to -2..a special code to
       indicate that there was no task. Then send this packet back to the
       consumer. You must provide the code for the packet send. */
  } 
}

void consumer(int id)
/* This function is executed by the consumer. It sleeps for a random amount
   of time and then contacts the master thread to see if there is a task.
   If there is a task, it simply appends the output file with the newly
   received data. */
{
  FILE *fp;
  int ov=1;
  int nv;
  struct timespec sleeptime;
  char *name;
  int intarr[3];
  MPI_Status stat;

  /* Open up the output file for writing. */
  id = id-2;
  name = (char *)malloc(5*sizeof(char));
  sprintf(name,"out%d",id);
  fp = fopen(name,"w");

  sleeptime.tv_sec=0;
  srand48(200+id);

  while (ov) {  /* Keep looping until the task received has a zero flag
                   that indicates end of tasks. */

    /* Set up the packet to be sent. The first variable has the consumer id. */
    intarr[0] = id;

    /* Request the master for a new task. Receive the task. You must
       provide the code for this. */
    
    if (intarr[0] > -1) {    /* If a new task is received. */
      if (intarr[0] != id) { /* Sanity check. */
        printf("Mismatch!\n");
	exit;
      }
      /* Write the newly received values into the output file. */
      ov = intarr[1];
      nv = intarr[2];
      fprintf(fp,"%d %d %d\n",id, ov, nv);
      printf("Cons %d got %d %d\n",id,ov,nv);
    }
    else {      /* Received a code of -2, indicating that there is no task.
                   Loop back and try again after a short sleep. */
      printf("Cons %d Found nothing\n",id);
    }
    sleeptime.tv_nsec = (long)((double)SLEEPTIME2 * (double)drand48());
    nanosleep(&sleeptime,NULL);
  }
  fclose(fp);
  printf("Done with consumer %d\n",id);
}


