#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <error.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>



#define N 5                         /* pocet filosofu */
#define THINK_COUNT (3 + rand() %5)  /* kolikrat budu premyslet - jist */
#define THINK_PERIOD (1 + rand()%3)  /* jak dlouho budu premyslet */
#define EAT_PERIOD (1 + rand()%5)    /* jak dlouho budu jist */

#define LEFT (i > 0 ? i-1 : N-1)
#define RIGHT ((i+1) % N)

enum status { THINK, HUNGRY, EAT, LEAVE };
enum status *state; /* pole stavu ve kterem se nachazeji filosofove */
int segment_id; /* identifikace zdilene pameti */
sem_t *mutex;
sem_t *s[N];
char sname[10]; /* jmeno semaforu */

int myid; /* index filosofa, v kazem vlaknu unikatni */

void init()
{
    int i;
    /** alokace zdilene pameti **/
    segment_id = shmget(IPC_PRIVATE, sizeof(enum status)*N, IPC_CREAT | S_IRUSR | S_IWUSR);
    if(!segment_id) {
        perror("shmget");
    }
    state = (enum status *) shmat(segment_id, NULL, 0);
    /** inicializace semaforu */
    mutex = sem_open("mutex", O_CREAT, 0, 1);
    if (mutex == SEM_FAILED) {
        perror("Error creating mutex semaphore");
        exit(1);
    }

    for (i = 0; i < N; i++) {
	    state[i] = THINK;
        sprintf(sname,"s%d",i);
        s[i] = sem_open(sname, O_CREAT, 0, 0);
        if (s[i] == SEM_FAILED) {
            perror("Error creating s semaphore");
            exit(1);
        }
    }
}

void destroy() 
{
    int i;
    sem_close(mutex);
    sem_unlink("mutex");
    for (i = 0; i < N; i++) {
        sem_close(s[i]);
        sprintf(sname,"s%d",i);
        sem_unlink(sname);
    }
    shmdt(state);
    shmctl(segment_id, IPC_RMID, NULL);
}

void show_table()
{
    int i;
    for(i =  0; i < N; i++)
    {
        switch(state[i]) {
            case THINK: printf("T "); break;
            case HUNGRY: printf("H "); break;
            case EAT: printf("E "); break;
            case LEAVE: printf("  "); break;
        }
    }
    printf("\n");

    for(i = 0; i < N; i++)
    {
        if(state[i] != EAT) continue;
        if(state[LEFT] == EAT || state[RIGHT] == EAT)
        {
            fprintf(stderr,"Chybna logika !\n");
            exit(1);
        }
    }
}

void think()
{
    sleep(THINK_PERIOD);
}
void eat()
{
    sleep(EAT_PERIOD);
}

void test(int i)
{
    if (state[i] == HUNGRY && state[LEFT] != EAT && state[RIGHT] != EAT) {
        state[i] = EAT;
        show_table();
        if(sem_post(s[i]))
            perror("sem_post(s[...)");
    }
}

void take_forks(int i)
{
    sem_wait(mutex);
    state[i] = HUNGRY;
    show_table();
    test(i);
    sem_post(mutex);
    sem_wait(s[i]);
}

void put_forks(int i)
{
    sem_wait(mutex);
    if(state[i] == HUNGRY) {
        printf("hungry is going to think %d\n",i);
    }
    state[i] = THINK;
    show_table();
    test(LEFT);
    test(RIGHT);
    sem_post(mutex);
}

void philosopher(int i)
{
    int s = THINK_COUNT;
    while (--s) {
	    think();
    	take_forks(i);
    	eat();
	    put_forks(i);
    }
    state[i] = LEAVE;
    show_table();
}

int main(void)
{
    int i;
    pid_t pid;
    pid_t filosofove[N];
    init();

    for (i = 0; i < N; i++) {
	    if (!(pid = fork())) {	// jsem potomek (filosof)
    	    sleep(i + 1);
    	    break;
    	}
    	filosofove[i] = pid;
    }
    if (pid) {
	    for (i = 0; i < N; i++) {
    	    waitpid(filosofove[i], NULL, 0);
        }
        destroy();
    	printf("Konec\n");
    	return 0;
    }
    srand(time(NULL));
    myid = i;
    philosopher(myid);
    return 0;
}

