#include <pthread.h>
#include <stdio.h>
#include <taskLib.h>	
#include <semaphore.h>
#include <mqueue.h>
#include <time.h>
#include <signal.h>

             
#define     SLEEP_TIME       3     /* maximum amount of time */
#define		NUM_PHILOSOPHERS 5	   /* number of philosophers */

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;	/* threads for the waiter       */
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, 1, &mqa);
	cook_queue = mq_open("cook",O_CREAT|O_RDWR,1 , &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);
		}
		taskDelay(1);
	}
	if (0!=pthread_create (&waiter_thread, NULL, waiter, &id[i])) /* creation of waiter thread*/ 
	{
		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;
			
	/*
	timer_create(clockid_t clock_id, struct sigevent * evp, timer_t *pTimer);
	If evp is NULL, a default signal (SIGALRM) is queued to the task, and the signal data is set to the timer ID. Initially, the timer is disarmed. 
	*/
	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);
		printf("join 0\n");
	pthread_join(thread[1], NULL);
		printf("join 1\n");
	pthread_join(thread[2], NULL);
		printf("join 2\n");
	pthread_join(thread[3], NULL);
		printf("join 3\n");
	pthread_join(thread[4], NULL);
		printf("join 4\n");
	/* pthread_join(waiter_thread, NULL);*/
	
	timer_delete(myTimer);
	
	printf("END\n");

	mq_close(waiter_queue);
	mq_close(cook_queue);

	return 0;
}


void *waiter(void *arg)
{
	int ID;
	while(1)
	{
		mq_receive(waiter_queue,&ID,sizeof(ID),NULL);
		printf("Waiter: Philosopher %d is hungry!\n",ID);
		taskDelay(5 * 60); /* 1 tick is normally 1/60s */
		mq_send(cook_queue,&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);
	srand((int)current_time.tv_nsec);

	/**************************************************************/
	/*philosopher (child) thinks, becomes hungry, ...*/
	/**************************************************************/

	for (i=0;i<5;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,&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, &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);
	taskDelay((rand() % SLEEP_TIME) * 60); /* 1 tick is normally 1/60s */
}


/**********************************************************************/
/**FUNCTION:  eating()                                               **/
/**                                                                  **/
/**DESCRIPTION:   This function is representative of a philosopher   **/
/** eating for a random amount of time.                              **/
/**********************************************************************/
void eating(int p_num, int iter_num)
{

	int lower, higher;
	
	/*********************/
	/****    TAKE FORKS **/
	/*********************/
	
	lower = min((p_num + 1) % NUM_PHILOSOPHERS, p_num);
	higher = max((p_num + 1) % NUM_PHILOSOPHERS, p_num);
	printf("*** Philo %d, acquires first %d, then %d, releases them in reverse order; %d iteration\n", p_num, lower, higher, iter_num);
			
	sem_wait(gabel[lower]);
	taskDelay(SLEEP_TIME * 60);
	sem_wait(gabel[higher]);
		
	printf("Philosopher %d is eating!\n",p_num);
	taskDelay((rand() % SLEEP_TIME) * 60); /* 1 tick is normally 1/60s */
			
	/*********************/
	/****    GIVE FORKS **/
	/*********************/
	
	sem_post(gabel[higher]);
	sem_post(gabel[lower]);
		
}

