/*
 * Implementação com alto paralelismo, mas sujeita a starvation.
 */

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define N 5
#define MAX_REFEICOES 100

#define ESQ (phil_id + 1) % N
#define DIR (phil_id + N - 1) % N

typedef enum {T, H, E} estado_t;
estado_t estado[N];

pthread_cond_t cond[N];
pthread_mutex_t mutex;
int refeicoes[N], total_refeicoes;

/* Considera que já adquiriu o mutex */
void exibe_mesa() {
  int i;
  for (i = 0; i < N; i++)
    switch (estado[N]) {
    case T:  printf("T ");
      break;
    case H:  printf("H ");
      break;
    case E:  printf("E ");
      break;
    }
  printf("\n");
}

/* Considera que já adquiriu o mutex */
void encerra_execucao() {
  int i;

  printf("Total de refeicoes: \n");
  for (i = 0; i < N; i++)
    printf("Filosofo %d: %d\n", i, refeicoes[i]);
  exit(0);
}

/* Considera que já adquiriu o mutex */
void testa_garfos(int phil_id) {
  if (estado[phil_id] == H && estado[ESQ] != E && estado[DIR] != E) {
    estado[phil_id] = E;
    pthread_cond_signal(&cond[phil_id]);
  }
}

void pensa(int phil_id) {
  sleep(1);
}

void pega_garfos(int phil_id) {
  pthread_mutex_unlock(&mutex);    
  estado[phil_id] = H;
  exibe_mesa();
  testa_garfos(phil_id);
  
}

void solta_garfos(int phil_id) {
}

void* f_phil(void *v) {
  int phil_id = *(int *) v;

  while(1) {

    pensa(phil_id);
    
    pega_garfos(phil_id);

    come(phil_id);
    
    solta_garfos(phil_id);
  }
}
    pthread_mutex_lock(&mutex);
    if (estado[ESQ] != E && estado[DIR] != E) 
      estado[phil_id] = E;
    else
      pthread_cond_wait(&cond[phil_id], &mutex);

    /* Filosofo esta comendo */
    exibe_pega_garfos_e_come(&mesa, phil_id);
    pthread_mutex_unlock(&mutex);
    sleep(5); 

    /* Filosofo termina de comer */
    pthread_mutex_lock(&mutex);
    estado[phil_id] = T;
    exibe_solta_garfos_e_pensa(&mesa, phil_id);

    /* Filosofo verifica se a brincadeira acabou */
    refeicoes[phil_id]++;
    if (++total_refeicoes == MAX_REFEICOES)
      encerra_execucao();
   
    /* Filosofo verifica se os seus vizinhos podem comer agora */
    verifica_vizinho(ESQ);
    verifica_vizinho(DIR);

    pthread_mutex_unlock(&mutex);
  }
  return NULL;
}

int main() {
  pthread_t thr[N];
  int i, phil_id[N];
  
  inicia_mesa(&mesa, N);
  pthread_mutex_init(&mutex, NULL);
  total_refeicoes = 0;
  for (i = 0; i < N; i++) {
      pthread_cond_init(&cond[i], NULL);
      phil_id[i] = i;
      refeicoes[i] = 0;
  }

  for (i = 0; i < N; i++) 
    pthread_create(&thr[i], NULL, f_phil, (void*) &phil_id[i]);

  for (i = 0; i < N; i++) 
    pthread_join(thr[i], NULL);
  
  return 0;
}