#include <stdio.h>
#include <thread.h>

/* states a philosopher can be in */
#define THINKING 0  
#define HUNGRY 1    
#define EATING 2

#define PHILOSOPHER 5      /* number of philosophers */
#define TIME 5             /* maximal number of seconds a philosopher thinks and eats */

int state[PHILOSOPHER];    /* state each philosopher is in */
sema_t block[PHILOSOPHER]; /* semaphore to block a hungry philosopher when no both forks are available */
sema_t mutex;              /* semaphore to ensure mutual exclusion when accessing or updating shared variables */

int think(int i)
{
  printf("Philosopher %d is thinking.\n", i);
  sleep(rand() % TIME);
}

int eat(int i)
{
  printf("Philosopher %d is eating.\n", i);
  sleep(rand() % TIME);
}

int pick_up_forks(int i)
{
  printf("Philosopher %d is tries to pick up its forks.\n", i);
  sema_wait(&mutex);
  if (state[(i - 1) % PHILOSOPHER] != EATING & state[(i + 1) % PHILOSOPHER] != EATING)
  {
    state[i] = EATING;
    sema_post(&block[i]);
  }
  else
  {
     state[i] = HUNGRY;
  }
  sema_post(&mutex);
  sema_wait(&block[i]);
  printf("Philosopher %d picks up its forks.\n", i);
}

int put_down_forks(int i)
{
  printf("Philosopher %d put down its forks.\n", i);
  sema_wait(&mutex);
  state[i] = THINKING;
  if (state[(i - 1) % PHILOSOPHER] == HUNGRY & state[(i - 2) % PHILOSOPHER] != EATING)
  {
    state[(i - 1) % PHILOSOPHER] = EATING;
    sema_post(&block[(i - 1) % PHILOSOPHER]);
  }
  if (state[(i + 1) % PHILOSOPHER] == HUNGRY & state[(i + 2) % PHILOSOPHER] != EATING)
  {
    state[(i + 1) % PHILOSOPHER] = EATING;
    sema_post(&block[(i + 1) % PHILOSOPHER]);
  }
  sema_post(&mutex);
}

void *philosopher(void *arg)
{
  int i = (int) arg;
  while (1)
  {
    think(i);
    pick_up_forks(i);
    eat(i);
    put_down_forks(i);
  }
}

int main()
{
  int i;
  sema_init(&mutex, 1, NULL, NULL);
  for (i = 0; i < PHILOSOPHER; i++)
  {
    state[i] = THINKING;
    sema_init(&block[i], 0, NULL, NULL);
  }
  for (i = 0; i < PHILOSOPHER; i++)
  {
    thr_create(NULL, NULL, philosopher, (void *) i, NULL, NULL);
  }
  while (thr_join(NULL, NULL, NULL) == 0);
  for (i = 0; i < PHILOSOPHER; i++)
  {
    sema_destroy(&block[i]);
  }
  sema_destroy(&mutex);
}

