/*
 * CS560: Operating Systems
 * Jim Plank 
 * dphil_b.c - Picking up a chopstick while your neighbor is dropping his/her chopstick
 * January, 2009
 */

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

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

#define BEGIN 0
#define WANTING_STICKS 1
#define GETTING_FIRST_STICK 2
#define GETTING_SECOND_STICK 3
#define GOT_BOTH_STICKS 4
#define DROPPING_FIRST_STICK 5
#define DROPPING_SECOND_STICK 6

#define FREE 0
#define ALLOCATED 1
#define USING 2
#define DROPPING 3

typedef struct {
  cbthread_gsem *sems;
  int *stick_states;
  int *my_pstates;
  double *hunger_time;
} MyPhil;

int test_philosopher(int pid, MyPhil *m, Simulation *s)
{
  int before, after;

  if (m->my_pstates[pid] != WANTING_STICKS) return 0;

  after = (pid + 1) % s->nphil;

  if (m->my_pstates[after] == WANTING_STICKS && 
      m->hunger_time[pid] - s->eatavg * 10 > m->hunger_time[after]) return 0;

  before = (pid + s->nphil - 1) % s->nphil;
  if (m->my_pstates[before] == WANTING_STICKS && 
      m->hunger_time[pid] - s->eatavg * 10 > m->hunger_time[before]) return 0;

  if (m->stick_states[pid] == USING || m->stick_states[after] == USING) return 0;
  if (m->stick_states[pid] == ALLOCATED || m->stick_states[after] == ALLOCATED) return 0;
  if (m->stick_states[pid] == DROPPING && m->stick_states[after] == DROPPING) return 0;
  if (m->stick_states[pid] == DROPPING || m->stick_states[after] == DROPPING) return 1;
  return 2;
}

void i_am_hungry(Philosopher *p)
{
  MyPhil *m;
  int s1, s2, first, second;

  m = (MyPhil *) p->s->v;
  s1 = p->id;
  s2 = (p->id + 1) % p->s->nphil;

  if (m->my_pstates[p->id] == BEGIN) {
    m->hunger_time[p->id] = cbthread_get_fake_time();
    m->my_pstates[p->id] = WANTING_STICKS;
  }

  if (test_philosopher(p->id, m, p->s) != 0) {
    first = (m->stick_states[s1] == FREE) ? s1 : s2;
    second = (first == s1) ? s2 : s1;
    m->stick_states[first] = USING;
    if (m->stick_states[second] != DROPPING) m->stick_states[second] = ALLOCATED;
    m->my_pstates[p->id] = GETTING_FIRST_STICK;
    pick_up_stick(p, first, i_am_hungry);

  } else if (m->my_pstates[p->id] == WANTING_STICKS) {
    cbthread_gsem_P(m->sems[p->id], i_am_hungry, p);

  } else if (m->my_pstates[p->id] == GETTING_FIRST_STICK) {
    first = (m->stick_states[s1] == USING) ? s1 : s2;
    second = (first == s1) ? s2 : s1;
    if (m->stick_states[second] == DROPPING) {
      cbthread_yield(i_am_hungry, p);
    }
    m->stick_states[second] = USING;
    m->my_pstates[p->id] = GETTING_SECOND_STICK;
    pick_up_stick(p, second, i_am_hungry);

  } else {
    m->my_pstates[p->id] = GOT_BOTH_STICKS;
    p->state = GOTSTICKS;
    philosopher(p);
    cbthread_exit();
  }
}

void i_am_sated(Philosopher *p)
{
  MyPhil *m;
  int s1, s2;
  int before, after;

  m = (MyPhil *) p->s->v;
  s1 = p->id;
  s2 = (p->id+1)%p->s->nphil;
  before = (p->id + p->s->nphil - 1) % p->s->nphil;
  after = s2;

  if (m->my_pstates[p->id] == GOT_BOTH_STICKS) {
    m->my_pstates[p->id] = DROPPING_FIRST_STICK;
    m->stick_states[s1] = DROPPING;
    if (test_philosopher(before, m, p->s) != 0) cbthread_gsem_V(m->sems[before]);
    put_down_stick(p, s1, i_am_sated);

  } else if (m->my_pstates[p->id] == DROPPING_FIRST_STICK) {
    if (m->my_pstates[before] == GETTING_FIRST_STICK) {
      m->stick_states[s1] = ALLOCATED;
    } else {
      m->stick_states[s1] = FREE;
    }
    if (test_philosopher(before, m, p->s) == 1) cbthread_gsem_V(m->sems[before]);
    m->stick_states[s2] = DROPPING;
    if (test_philosopher(after, m, p->s) != 0) cbthread_gsem_V(m->sems[after]);
    m->my_pstates[p->id] = DROPPING_SECOND_STICK;
    put_down_stick(p, s2, i_am_sated);

  } else {
    if (m->my_pstates[after] == GETTING_FIRST_STICK) {
      m->stick_states[s2] = ALLOCATED;
    } else {
      m->stick_states[s2] = FREE;
    }
    if (test_philosopher(after, m, p->s) == 1) cbthread_gsem_V(m->sems[after]);
    m->my_pstates[p->id] = BEGIN;
    p->state = STARTING;
    philosopher(p);
    cbthread_exit();
  }
}

void initialize_simulation(Simulation *s)
{
  MyPhil *m;
  int i;

  m = talloc(MyPhil, 1);
  m->my_pstates = talloc(int, s->nphil);
  m->stick_states = talloc(int, s->nphil);
  m->sems = talloc(cbthread_gsem, s->nphil);
  m->hunger_time = talloc(double, s->nphil);
  for (i = 0; i < s->nphil; i++) {
    m->sems[i] = cbthread_make_gsem(0);
    m->my_pstates[i] = BEGIN;
    m->stick_states[i] = FREE;
  }
  s->v = (void *) m;
  return;
}

