// ipcs

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>

// number of philisophers
#define N 5

typedef enum {
  THINKING = 0,
  HUNGRY   = 1,
  EATING   = 2
} state_t;

state_t state[N];

int sems;

#define PHIL_SEM(i) (i)
#define FORK_SEM(i) (i+N)
#define MUTEX_SEM (2*N)

#define LEFT(i) ((i+N-1)%N)
#define RIGHT(i) ((i+1)%N)

void ipc_cleanup(void) {
  // free semaphores
  semctl(sems,0,IPC_RMID);
}

void up_down(int sem, int op) {
  struct sembuf sop;
  sop.sem_num = sem;
  sop.sem_op  = op;
  sop.sem_flg = 0;
  if (-1 == semop(sems,&sop,1)) {
    fprintf(stderr, "semaphore operation failed with %d\n", errno);
    ipc_cleanup();
    exit(1);
  }
}

#define down(semid) up_down(semid,-1)
#define up(semid)   up_down(semid,1)

void think(i) {
  printf("Philospher %d is thinking\n", i);
}

void eat(i) {
  printf("Philospher %d is eating\n", i);
}

void enter_eat(int i) {
  down(MUTEX_SEM);
  state[i] = HUNGRY;
  if (state[LEFT (i)]!=EATING &&
      state[RIGHT(i)]!=EATING)
  {
    state[i] = EATING;
    up(PHIL_SEM(i));
  }
  up(MUTEX_SEM);
  down(PHIL_SEM(i));
}

void leave_eat(int i) {
  down(MUTEX_SEM);
  state[i] = THINKING;
  
  // darf der linke Nachbar essen?
  if (state[LEFT(i)]==HUNGRY &&
      state[LEFT(LEFT(i))]!=EATING)
  {
    state[LEFT(i)] = EATING;
    up(PHIL_SEM(LEFT(i)));
  }
  // darf der rechte Nachbar essen?
  if (state[RIGHT(i)]==HUNGRY &&
      state[RIGHT(RIGHT(i))]!=EATING)
  {
    state[RIGHT(i)] = EATING;
    up(PHIL_SEM(RIGHT(i)));
  }

  up(MUTEX_SEM);
}

void *philosopher(void *data) {
  int i = (int) data;
  printf("philopher %d awoke\n", i);

  while (1) {
    think(i);

    enter_eat(i);    
    down(FORK_SEM(i));
    down(FORK_SEM(RIGHT(i)));
    eat(i);
    up(FORK_SEM(i));
    up(FORK_SEM(RIGHT(i)));
    leave_eat(i);
  }

  return NULL;
}

void exit_signal_handler(int sig) {
  printf("handling signal %d\n", sig);
  ipc_cleanup();  

  // standard signal handler wieder herstellen und signal neu ausloesen
  if (SIG_ERR == signal(sig, SIG_DFL)) {
    fprintf(stderr, "error setting the default signal handler for SIGINT\n");
    exit(1);
  }
  raise(sig);
}

int main(void) {
  pthread_t ptids[N];
  pthread_attr_t ptattr;
  int i;
  int ret;
  void *ptret;
  unsigned short init_sem_vals[2*N+1];

  // Signal Handler fuer SIGINT
  if (SIG_ERR == signal(SIGINT, exit_signal_handler)) {
    fprintf(stderr, "error setting the signal handler for SIGINT\n");
    exit(1);
  }

  // Semaphoren erstellen
  sems = semget(IPC_PRIVATE, 2*N+1, 0600);
  if (sems < 0) {
    fprintf(stderr, "failed to create semaphores with %d\n", sems);
    exit(1);
  }

  // Initialwerte fuer die Semaphoren
  // Philosophen
  for (i = 0; i < N; i++) {
    init_sem_vals[PHIL_SEM(i)] = 0;
  }
  // Gabeln
  for (i = 0; i < N; i++) {
    init_sem_vals[FORK_SEM(i)] = 1;
  }
  // Mutex
  init_sem_vals[MUTEX_SEM] = 1;
  if (-1 == semctl(sems, 0, SETALL, init_sem_vals)) {
      fprintf(stderr, "semctl() failed with %d\n", errno);
      ipc_cleanup();
      exit(1);
  }

  // Fuer jeden Philosophen einen Thread erzeugen
  pthread_attr_init(&ptattr);
  pthread_attr_setdetachstate(&ptattr,PTHREAD_CREATE_JOINABLE);
  for (i = 0; i < N; i++) {
    ret = pthread_create(&ptids[i], &ptattr, philosopher, (void*) i);
    if (ret) {
      fprintf(stderr, "pthread_create() failed with %d\n", ret);
      ipc_cleanup();
      exit(1);
    }
  }
  
  // Auf Terminierung der Threads warten
  for (i = 0; i < N; i++) {
    pthread_join(ptids[i],&ptret);
    printf("philopher %d died\n", i);    
  }

  // IPC Sachen freigeben
  ipc_cleanup();
  return 0;
}
