#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <errno.h>
#include <mqueue.h>
#include <time.h>
#include <signal.h>

                                   
#define     SLEEP_TIME       2     /* maximum amount of time */

void hungry();
void thinking();
void *philosopher(void*);
void eating();
void *waiter(void *arg);
void timerHandler(int signo);

sem_t* gabel[5];	/* semaphores for all forks */
sem_t* teller[5];

mqd_t	waiter_queue;
mqd_t	cook_queue;
struct mq_attr mqa;

pthread_t thread[5];		/* threads for all philosophers */
pthread_t waiter_thread;
int id[5];			/* id parameter */


int main()
{
	int i;

	/************************************************/
	/*variables for timer handling					*/
	/************************************************/
	
	timer_t myTimer;
	struct itimerspec timer_value;
	struct sigaction timerAction;
	
	/************************************************/
	/*initialize 5 semaphores						*/
	/************************************************/

	gabel[0] = sem_open("sem0", O_CREAT , 1, 1);
	gabel[1] = sem_open("sem1", O_CREAT , 1, 1);
	gabel[2] = sem_open("sem2", O_CREAT , 1, 1);
	gabel[3] = sem_open("sem3", O_CREAT , 1, 1);
	gabel[4] = sem_open("sem4", O_CREAT , 1, 1);

	teller[0] = sem_open("t0", O_CREAT, 1, 0);
	teller[1] = sem_open("t1", O_CREAT, 1, 0);
	teller[2] = sem_open("t2", O_CREAT, 1, 0);
	teller[3] = sem_open("t3", O_CREAT, 1, 0);
	teller[4] = sem_open("t4", O_CREAT, 1, 0);

	/************************************************/
	/*initialize message queue */
	/************************************************/

	mqa.mq_flags=0;					/* message queue should be blocking */
	mqa.mq_maxmsg=10;				/* mq can store a maximum of 10 messages */
	mqa.mq_msgsize=sizeof(int);			/* maximum message size */
	waiter_queue = mq_open("/mq", O_CREAT|O_RDWR, 0666, &mqa );
	cook_queue = mq_open("/cook",O_CREAT|O_RDWR, 0666, &mqa );


	/************************************************/
	/*create philosopher threads					*/
	/************************************************/
	for (i=0; i<5; i++) {
		id[i] = i;
		if (0!=pthread_create (&thread[i], NULL, philosopher, &id[i])) /* creation of philosopher thread i */ {
			printf("ERROR: main thread: could not create thread%d\n", i);
			return -1;
		}
		else {
			printf("INFO: main thread: thread%d created\n", i);
		}

	}
	/* creation of waiter thread*/ 
	if (0!=pthread_create (&waiter_thread, NULL, waiter, &id[i]))
	{
		printf("ERROR: main thread: could not create thread\n");
		return -1;
	}

	/************************************************/
	/*Create and initialize the periodic timer		*/
	/*with the period equal to 5 seconds            */
	/************************************************/
	
	timer_value.it_value.tv_sec = 5;
	timer_value.it_value.tv_nsec = 0;
	timer_value.it_interval.tv_sec = 5;
	timer_value.it_interval.tv_nsec = 0;
		
	timerAction.sa_handler = timerHandler;
	timerAction.sa_flags = 0;

	if (0==timer_create(CLOCK_REALTIME, NULL, &myTimer)){
		printf("Timer for the cook thread has been allocated!\n");
		if (0 != timer_settime(myTimer, 0, &timer_value, NULL))
			printf("Timer could not be initialized\n");
		else
			printf("Timer has been initialized\n");
		
		printf ("sigaction = %i\n", sigaction(SIGALRM, &timerAction, NULL));
	} else {
		printf("Timer for the cook thread could not be allocated!\n");
	}
	
	/************************************************/
	/*Wait until the termination of all threads     */
	/************************************************/

	pthread_join(thread[0], NULL);
	pthread_join(thread[1], NULL);
	pthread_join(thread[2], NULL);
	pthread_join(thread[3], NULL);
	pthread_join(thread[4], NULL);
	pthread_join(waiter_thread, NULL);

	timer_delete(myTimer);

	mq_close(waiter_queue);
	mq_close(cook_queue);

	sem_close(gabel[0]);
	sem_close(gabel[1]);
	sem_close(gabel[2]);
	sem_close(gabel[3]);
	sem_close(gabel[4]);
	sem_close(teller[0]);
	sem_close(teller[1]);
	sem_close(teller[2]);
	sem_close(teller[3]);
	sem_close(teller[4]);
	
	printf("END\n");

	return 0;
}

void *waiter(void *arg)
{
	int ID;
	while(1)
	{
		mq_receive(waiter_queue,(char*)&ID,sizeof(ID),NULL);
		printf("Waiter: Philosopher %d is hungry!\n",ID);
		sleep(5); 
		mq_send(cook_queue,(char*)&ID,sizeof(ID),1);
	}

	return 0;
}

/**********************************************************************/
/**FUNCTION:  philTask()                                         	 **/
/**                                                                  **/
/**DESCRIPTION:  every philosopher thinks, is hungry, eats and     	 **/
/** 			 orders wine			                             **/
/**********************************************************************/
void *philosopher(void *arg)
{
	int i;
	struct timespec current_time;

	clock_gettime(CLOCK_REALTIME, &current_time); /* current time */
	srand((int)current_time.tv_nsec);

	/**************************************************************/
	/*philosopher (child) thinks, becomes hungry, ...*/
	/**************************************************************/

	for (i=0;i<10;i++)
	{
		thinking(*(int*)arg, i);
		hungry(*(int*)arg, i);
		eating(*(int*)arg, i);		
	}
	return 0;
}

void timerHandler (int signo)
{
	int ID;
	struct mq_attr attr;
	mq_getattr(cook_queue,&attr);
	if (attr.mq_flags>0)
	{
		mq_receive(cook_queue,(char*)&ID,sizeof(ID),NULL);
		printf("Cook: Philosopher %d served, %d in queue!\n",ID,(int) attr.mq_curmsgs);
		sem_post(teller[ID]);
	}
}

/**********************************************************************/
/**FUNCTION:  hungry()                                               **/
/**                                                                  **/
/**DESCRIPTION:   This function is representative of a philosopher   **/
/** being hungry.                                                    **/
/**********************************************************************/
void hungry(int p_num, int iter_num)
{
	printf("Philosopher %d: I'm hungry! %d iteration\n",p_num, iter_num);
	mq_send(waiter_queue, (char*)&p_num,sizeof(p_num),1);
	sem_wait(teller[p_num]);
}


/**********************************************************************/
/**FUNCTION:   thinking()                                            **/
/**                                                                  **/
/**DESCRIPTION:   This function is representative of a philosopher   **/
/** thinking for a random amount of time.                            **/
/**********************************************************************/
void thinking(int p_num, int iter_num)
{
	printf("Philosopher %d is thinking! %d iteration\n",p_num, iter_num);
	sleep(rand() % SLEEP_TIME);
}


/**********************************************************************/
/**FUNCTION:  eating()                                               **/
/**                                                                  **/
/**DESCRIPTION:   This function is representative of a philosopher   **/
/** eating for a random amount of time.                              **/
/**********************************************************************/
int left( int p )
{
	p--;
	while( p < 0 )
		p = ( 5 + p );
	printf("%d\n",  p % 5 );
	return p % 5;
}

int right( int p )
{
	while( p < 0 )
		p = ( 5 + p );

	printf("%d\n",  p % 5 );
	return p % 5;
}

void eating(int p_num, int iter_num)
{
	if (!( p_num % 2 ) )
	{
		/*********************/
		/****    TAKE FORKS **/
		/*********************/
		sem_wait(gabel[ left( p_num ) ]);
		sem_wait(gabel[ right( p_num ) ]);

		printf("Philosopher %d is eating!\n",p_num);
		sleep(rand() % SLEEP_TIME);

		/*********************/
		/****    GIVE FORKS **/
		/*********************/
		sem_post(gabel[ left( p_num ) ]);
		sem_post(gabel[ right( p_num ) ]);
	}
	else
	{
		/*********************/
		/****    TAKE FORKS **/
		/*********************/
		sem_wait(gabel[ right( p_num ) ]);
		sem_wait(gabel[ left( p_num ) ]);

		printf("Philosopher %d is eating!\n",p_num);
		sleep(rand() % SLEEP_TIME);

		/*********************/
		/****    GIVE FORKS **/
		/*********************/
		sem_post(gabel[ right( p_num ) ]);
		sem_post(gabel[ left( p_num ) ]);
	}

}

