/*
 * CS560: 
 * dphil_skeleton.c -- Dining philosophers driver program.
 * James S. Plank
 * January, 2009
 */

#define talloc(ty, sz) (ty *) malloc ((sz) * sizeof(ty))

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "cbthread.h"
#include "dphil.h"

void update_times(Philosopher *p)
{
  double last_event_time;

  last_event_time = p->last_event_time;
  p->last_event_time = cbthread_get_fake_time();
  switch (p->state) {
    case STARTING:  p->blocked_time += (p->last_event_time - last_event_time); break;
    case THINKING:  p->thinktime    += (p->last_event_time - last_event_time); break;
    case HUNGRY:    p->blocked_time += (p->last_event_time - last_event_time); break;
    case GOTSTICKS: p->blocked_time += (p->last_event_time - last_event_time); break;
    case EATING:    p->eattime      += (p->last_event_time - last_event_time); break;
    case SATED:     p->blocked_time += (p->last_event_time - last_event_time); break;
    default:
       fprintf(stderr, "update_times -- shouldn't get here\n");
       exit(1);
  }
}

void pick_up_stick(Philosopher *p, int stick, void (*func)())
{
  if (p->s->verbose) {
    printf("%10.3lf: %03d Picking up stick %03d\n", cbthread_get_fake_time(), p->id, stick);
    fflush(stdout);
  }
  if (p->s->chopsticks_in_use[stick]) {
    printf("Error: pick_up_stick(%d) called on a stick in use.\n", stick);
    exit(1);
  }
  p->s->chopsticks_in_use[stick] = 1;
  cbthread_fake_sleep(p->s->sticktime, func, p);
}

void put_down_stick(Philosopher *p, int stick, void (*func)())
{
  if (p->s->verbose) {
    printf("%10.3lf: %03d Putting down stick %03d\n", cbthread_get_fake_time(), p->id, stick);
    fflush(stdout);
  }
  if (!p->s->chopsticks_in_use[stick]) {
    printf("Error: put_down_stick(%d) called on a stick not in use.\n", stick);
    exit(1);
  }
  p->s->chopsticks_in_use[stick] = 0;
  cbthread_fake_sleep(p->s->sticktime, func, p);
}

void philosopher(Philosopher *p)
{
  Simulation *s;
  double thinktime, eattime;
  
  s = p->s;

  update_times(p);

  if (p->state == STARTING) {
    thinktime = drand48()*s->thinkavg*2.0;
    if (s->verbose) {
      printf("%10.3lf: %03d Thinking for %10.3lf.\n", cbthread_get_fake_time(), p->id, thinktime);
      fflush(stdout);
    }
    p->state = THINKING;
    cbthread_fake_sleep(thinktime, philosopher, p);

  } else if (p->state == THINKING) {
    if (s->verbose) {
      printf("%10.3lf: %03d Hungry.\n", cbthread_get_fake_time(), p->id);
      fflush(stdout);
    }
    p->state = HUNGRY;
    i_am_hungry(p);
    fprintf(stderr, "Error: i_am_hungry(%d) returned\n", p->id);
    exit(1);
    
  } else if (p->state == GOTSTICKS) {
    eattime = (drand48()*s->eatavg*2.0);
    
    if (s->verbose) {
      printf("%10.3lf: %03d Eating for   %10.3lf.\n", cbthread_get_fake_time(), p->id, eattime);
      fflush(stdout);
    }
    p->state = EATING;
    cbthread_fake_sleep(eattime, philosopher, p);
  
  } else if (p->state == EATING) {
    if (s->verbose) {
      printf("%10.3lf: %03d Sated.\n", cbthread_get_fake_time(), p->id);
      fflush(stdout);
    }
    p->state = SATED;
    i_am_sated(p);
    fprintf(stderr, "Error: i_am_sated(%d) returned\n", p->id);
    exit(1);
  
  } 
  printf("%10.3lf: At the end of philosopher(%d).  Shouldn't get here.\n", cbthread_get_fake_time(), p->id);
  exit(1);
}
    
void usage(char *s)
{
  fprintf(stderr, "usage: dphil nphilosophers thinkavg eatavg sticktime interval duration seed verbose\n");
  if (s != NULL) fprintf(stderr, "%s\n", s);
  exit(1);
}

void timer(Simulation *s)
{
  double t;
  double tthink;
  double teat;
  double tblock;
  double total;
  double dn;
  int i;

  tthink = 0;
  teat = 0;
  tblock = 0;
  total = 0;
  dn = s->nphil;

  t = cbthread_get_fake_time();
  if (t != 0) {
    for (i = 0; i < s->nphil; i++) {
      update_times(s->p[i]);
      tthink += s->p[i]->thinktime;
      teat += s->p[i]->eattime;
      tblock += s->p[i]->blocked_time;
      total += t;
    }
    printf("%10.3lf:  Thinktime: %10.3lf    Eattime: %10.3lf    Blocked_time: %10.3lf\n", 
           t, tthink/dn, teat/dn, tblock/dn);
  }

  if (t >= s->duration) {
    for (i = 0; i < s->nphil; i++) {
      printf("Philosopher %03d: Think: %10.3lf  Eat: %10.3lf   Blocked: %10.3lf\n", 
              i, s->p[i]->thinktime, s->p[i]->eattime, s->p[i]->blocked_time);
    }
    exit(0);
  }
  if (t + s->interval <= s->duration) {
    cbthread_fake_sleep(s->interval, timer, s);
  } else {
    cbthread_fake_sleep(s->duration-t, timer, s);
  }
}

main(int argc, char **argv)
{
  Simulation *s;
  Philosopher *p;
  long seed;
  int i;

  if (argc != 9) usage(NULL);

  s = talloc(Simulation, 1);

  if (sscanf(argv[1], "%d", &s->nphil) == 0 || s->nphil <= 0) usage("Bad nphil");
  if (sscanf(argv[2], "%lf", &s->thinkavg) == 0 || s->thinkavg < 0) usage("Bad thinkavg");
  if (sscanf(argv[3], "%lf", &s->eatavg) == 0 || s->eatavg < 0) usage("Bad eatavg");
  if (sscanf(argv[4], "%lf", &s->sticktime) == 0 || s->sticktime < 0) usage("Bad sticktime");
  if (sscanf(argv[5], "%lf", &s->interval) == 0 || s->interval <= 0) usage("Bad interval");
  if (sscanf(argv[6], "%lf", &s->duration) == 0 || s->duration <= 0) usage("Bad duration");
  if (sscanf(argv[7], "%ld", &seed) == 0) usage("Bad seed");
  s->verbose = (argv[8][0] == 'y' || argv[8][0] == 'Y');
  s->p = talloc(Philosopher *, s->nphil);
  s->chopsticks_in_use = talloc(int, s->nphil);
  for (i = 0; i < s->nphil; i++) s->chopsticks_in_use[i] = 0;

  srand48(seed);
  initialize_simulation(s);
  
  for (i = 0; i < s->nphil; i++) {
    p = talloc(Philosopher, 1);
    s->p[i] = p;
    p->id = i;
    p->state = STARTING;
    p->s = s;
    p->thinktime = 0;
    p->eattime = 0;
    p->blocked_time = 0;
    p->last_event_time = 0;
    cbthread_fork(philosopher, p);
  }
  cbthread_fork(timer, s);
  cbthread_exit();
}

