/*
 * A test harness for running solutions to the dining philosophers problem.
 *
 * To run:
 *
 *   $ ./restaurant <random-seed>
 *
 * Specific random seeds can be used to re-create particular collision
 * circumstances.  If no random-seed is given, the current system time
 * will be used.
 *
 * Compile with
 *
 *   make restaurant
 *
 */
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

#define NUM_PHILOSOPHERS 5


pthread_t philosophers[NUM_PHILOSOPHERS];
int continuing[NUM_PHILOSOPHERS];
int eating[NUM_PHILOSOPHERS];
char states[NUM_PHILOSOPHERS+1];
int thinking_time[NUM_PHILOSOPHERS];
int eating_time[NUM_PHILOSOPHERS];
int run_time = 60;

void *philosopher(int);

int randomwait(void) {
  int wait = rand()%3;
  sleep(wait);
  return wait;
}

void *runner(void* arg) {
  printf("Thread %d started.\n", (int)arg);
  philosopher((int)arg);
  printf("Thread %d finished.\n", (int)arg);
  pthread_exit(NULL);
}

void eat(int id) {
  int left, right;
  states[id] = 'e';
  left = (id+NUM_PHILOSOPHERS-1)%NUM_PHILOSOPHERS;
  right = (id+1)%NUM_PHILOSOPHERS;
  printf("[%s] Philosopher %d eating...\n", states, id);
  eating[id] = 1;
  if(eating[left] || eating[right]) {
    printf("[%s] Philosopher %d collided with %d!\n", states, id, eating[left]?left:right);
    exit(-1);
  }
  eating_time[id] += randomwait();
  eating[id] = 0;
  printf("[%s] Philosopher %d done eating...\n", states, id);
  states[id] = '.';
}

void think(int id) {
  states[id] = 't';
  printf("[%s] Philosopher %d thinking...\n", states, id);
  /* non threadsafe increment */
  thinking_time[id] += randomwait();
  printf("[%s] Philosopher %d done thinking...\n", states, id);
  states[id] = '.';
}

int main(int argc, char** argv) {
  int result, i;
  int total_thinking_time = 0;
  int total_eating_time = 0;
  printf("Dining Philosophers runtime harness 1.5\n");

  srand(argc==1?time(NULL):atoi(argv[1]));
  for(i = 0; i < NUM_PHILOSOPHERS; i++) {
    continuing[i] = 1;
    thinking_time[i] = 0;
    eating_time[i] = 0;
    eating[i] = 0;
    states[i] = '.';
    states[i+1] = 0;
  }
  for(i = 0; i < NUM_PHILOSOPHERS; i++) {
    result = pthread_create(&philosophers[i], NULL, runner, (void*)i);
    if(result) {
      printf("ERROR: pthread code: %d\n", result);
      exit(-1);
    }
  }
  sleep(run_time);
  /* Signal to all threads they should die.  Assuming they're watching. */
  printf("Restaurant exiting now.\n");
  for(i = 0; i < NUM_PHILOSOPHERS; i++) {
    continuing[i] = 0;
  }
  sleep(5);
  for(i = 0; i < NUM_PHILOSOPHERS; i++) {
    total_thinking_time += thinking_time[i];
    total_eating_time += eating_time[i];
  }
  printf("Thinking time: %d, ratio: %f\n", total_thinking_time,
      (float)total_thinking_time/run_time);
  printf("Eating time: %d, ratio: %f\n", total_eating_time,
      (float)total_eating_time/run_time);
  printf("Wasted time: %d, efficiency %f\n",
      (NUM_PHILOSOPHERS * run_time)-(total_thinking_time + total_eating_time),
      ((float)total_thinking_time + total_eating_time)
      /
      ((float)NUM_PHILOSOPHERS * run_time)
      );
  pthread_exit(NULL);
}
