/*
 * Labyrinthe.c
 *
 * Main labyrinthe file
 *
 * Gouverneur Th. - Cuisinier Gi.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "log.h"
#include "Ecran.h"
#include "Laby.h"
#include "Labyrinthe.h" 
#include "self.h"
#include "pathfinder.h"

#define GOTOXY(X,Y) printf("\033[%d;%dH",Y,X); fflush(stdout)

extern int Laby[NB_LIGNES][NB_COLONNES];

pthread_t	ThreadPion[NB_MAX_PIONS];
pthread_t	ThreadChrono;
pthread_t	ThreadCompteur;
pthread_t	ThreadMaitre;
pthread_t	ThreadManual;
pthread_mutex_t	MutexMap;
pthread_mutex_t	MutexNb;
pthread_mutex_t	MutexManual;
pthread_cond_t	CondNb;
pthread_cond_t	CondManual;
pthread_key_t	PKey;

/* compteurs: */
unsigned int 	NbPion;
unsigned int	NbPionSortis;

/* autres: */
const Pos 	end 	= { LIG_SORTIE, COL_SORTIE };
const Pos 	begin 	= { LIG_ENTREE, COL_ENTREE };
const Pos	bad 	= { -1, -1 };
unsigned int	tBegin;
int		ld; // Laby.log
int		sd; // Sorties.log

/*
 * Fonction principale.
 *
 */
extern "C"
{
int main()
{
	/* init var&mutexes */
	NbPion = NbPionSortis = 0;
	tBegin = time(0);
	pthread_mutex_init(&MutexMap, NULL);
	pthread_mutex_init(&MutexNb, NULL);
	pthread_mutex_init(&MutexManual, NULL);
	pthread_cond_init(&CondNb, NULL);
	pthread_cond_init(&CondManual, NULL);
	pthread_key_create(&PKey, NULL);
	ld = OpenLog("Laby.log");
	sd = OpenLog("Sorties.log");
	sigset_t sset;

	AfficheLaby(); /* avant les threads sinon ca bug :) */

	if (pthread_create(&ThreadManual, NULL, pthread_manual, NULL) != 0)
	{
		WriteLog(ld, "Cannot start thread for manual.. exiting..");
		return -1;
	}

	if (pthread_create(&ThreadChrono, NULL, pthread_chrono, NULL) != 0)
	{
		WriteLog(ld, "Cannot start thread for chrono.. exiting..");
		return -1;
	}
	
	if (pthread_create(&ThreadCompteur, NULL, pthread_compteur, NULL) != 0)
	{
		WriteLog(ld, "Cannot start thread for compteur.. exiting..");
		return -1;
	}

	if (pthread_create(&ThreadMaitre, NULL, pthread_maitre, NULL) != 0)
	{
		WriteLog(ld, "Cannot start thread for maitre.. exiting..");
		return -1;
	}

	sigfillset(&sset);
	pthread_sigmask(SIG_SETMASK, &sset, NULL);

	pthread_join(ThreadMaitre, NULL);

	exit(0);
}
}

/*
 * But: Gerer l'ajout d'un thread pion.
 *
 */
int	addPion(void)
{
	pthread_mutex_lock(&MutexNb);

	if (NbPion >= NB_MAX_PIONS)
	{
		pthread_mutex_unlock(&MutexNb);
		return -1; 
	}

	pthread_mutex_unlock(&MutexNb);

	while(1)
	{	
		pthread_mutex_lock(&MutexMap);

		if (isFree(begin))
		{
			pthread_mutex_unlock(&MutexMap);
			pthread_mutex_lock(&MutexNb);
	
			if (pthread_create(&ThreadPion[NbPion], NULL, pthread_pion, NULL) != 0)
			{	WriteLog(ld, "Cannot create pthread..."); return -1; }
	
			pthread_detach(ThreadPion[NbPion]);
			NbPion++;
			pthread_mutex_unlock(&MutexNb);
			pthread_cond_signal(&CondNb);
			return 0;
		}
		else pthread_mutex_unlock(&MutexMap);
		MicroWait(20000000);
	}
}
	
	
/*
 * But: Faire voyager le pion de l'entree vers la sortie.
 *
 */
extern "C"
{
void * 	pthread_pion(void *arg)
{
	Pos cur = begin;
	Pos old = begin;
	Pos next;
	IThread *ident;
	sigset_t sset;

	pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

	sigfillset(&sset);
	pthread_sigmask(SIG_SETMASK, &sset, NULL);

	ident = (IThread *) malloc(sizeof(IThread));
	ident->Num = pthread_self();
	ident->Entree = time(0) - tBegin;
	ident->State = SNOK;
#ifdef PATHFINDER
	ident->savPos = bad;
	ident->count = -1;
	ident->pf = NULL;
#endif
	pthread_setspecific(PKey, ident);

	pthread_cleanup_push(pthread_clean, NULL);

	WriteLog(ld, "Thread lance");
	
	pthread_mutex_lock(&MutexMap);
	changePos(NULL, &cur);
	pthread_mutex_unlock(&MutexMap);

	while (42)
	{
#ifdef DEBUG
		WriteLog(ld, "[%d] TOP", ident->Num);
#endif
		old = cur;
		if (!posCmp(cur, end)) break;
#ifdef DEBUG_MUTEX
		WriteLog(ld, "[%d] Try to Lock map", ident->Num);
#endif
		pthread_mutex_lock(&MutexMap);
#ifdef DEBUG_MUTEX
		WriteLog(ld, "[%d] Locked MAP", ident->Num);
#endif

		if (!posCmp((next = getNextPos(cur)), bad))
		{
#ifdef DEBUG
			WriteLog(ld, "[%d] Cannot move for now...", ident->Num);
#endif
			pthread_mutex_unlock(&MutexMap);
		}
		else
		{
			changePos(&old, &next);
			cur = next;
			pthread_mutex_unlock(&MutexMap);
#ifdef DEBUG_MUTEX
			WriteLog(ld, "[%d] UnLocked MAP", ident->Num);
#endif
#ifdef DEBUG
			WriteLog(ld, "[%d] MOVE EFFECTUE", ident->Num);
#endif
		}
		MicroWait(200000000);
	}
	pthread_mutex_lock(&MutexMap);
	EffacePion(cur);
	Laby[cur.lig][cur.col] = 0;
	pthread_mutex_unlock(&MutexMap);

	pthread_mutex_lock(&MutexNb);
	NbPionSortis++;
	pthread_mutex_unlock(&MutexNb);
	pthread_cond_signal(&CondNb);

	ident->State = SOK;

	WriteLog(ld, "Thread exit");
	pthread_exit(0);
	
	pthread_cleanup_pop(0); /* MANDATORY, this is a macro
				   function like push one.. and
				   they should be here together...
				*/
	return NULL;
}
}

/*
 * But: Effacer l'ancienne position a l'ecran,
 *     replacer la nouvelle, et effectuer ces
 *     modification dans la grille en memoire. 
 */
void	changePos(Pos *old, Pos *niouw)
{
	if (niouw)
	{
		if (old)
		{
			unsetPos(*old);
			EffacePion(*old);
			GOTOXY(0,11);
		}
		setPos(*niouw);
		DessinePion(*niouw);
		GOTOXY(0,11);
	}
}


/*
 * But: renvoie 1 si la pos est libre, 0 sinon
 *
 */
int	isFree(Pos p)
{	
	if (p.lig < 0 || p.lig > NB_LIGNES) return 0;
	if (p.col < 0 || p.col > NB_COLONNES) return 0;
	if (Laby[p.lig][p.col] == 0) return 1; else return 0;
}

/*
 * But: Set la position a "occupe"
 *
 */
void	setPos(Pos p)
{
	if (p.lig < 0 || p.lig > NB_LIGNES) return;
	if (p.col < 0 || p.col > NB_COLONNES) return;
	Laby[p.lig][p.col] = 4;
}


/*
 * But: Set la position a "libre"
 *
 */
void	unsetPos(Pos p)
{
	if (p.lig < 0 || p.lig > NB_LIGNES) return;
	if (p.col < 0 || p.col > NB_COLONNES) return;
	Laby[p.lig][p.col] = 0;
}

/*
 * But: Compare deux positions.
 *
 */
int	posCmp(const Pos p1, const Pos p2)
{
	return (p1.lig - p2.lig) + (p1.col - p2.col);
}

/* 
 * But: Retourne la position suivante.
 *
 */
Pos	getNextPos(Pos cur)
{
#ifdef PATHFINDER
	IThread *ident = (IThread *) pthread_getspecific(PKey);
	Pos ret = bad;
	if (!ident->pf) ident->pf = new Trajet();
	if (ident->count == -1) 
	{
		WriteLog(ld, "[%d] Trying to find path...", pthread_self());
		if (ident->pf->FindPath(&Laby[0][0], NB_COLONNES, NB_LIGNES, COL_SORTIE, LIG_SORTIE, cur.col, cur.lig) == -1)
		{
			WriteLog(ld, "[%d] Cannot found a good path, trying later dude...", pthread_self());
			return bad;
		}
		ident->pf->ResetMove();
		ident->count = ident->pf->GetNb();
		WriteLog(ld, "[%d] Path finded... %d moves", pthread_self(), ident->count);
	}
	if (ident->count > 0)
	{
		if (posCmp(ident->savPos, bad))	
		{
			WriteLog(ld, "[%d] Return old move...", pthread_self());
			ret = ident->savPos;
			ident->savPos = bad;
			ident->count--;
			return ret;
		}
		ident->pf->GetMove(&ret.col, &ret.lig);
		WriteLog(ld, "[%d] Move get...%d-%d", pthread_self(), cur.lig, cur.col);
		if (isFree(ret))
		{
			WriteLog(ld, "[%d] we can go to this move...", pthread_self());
			ident->count--;
			return ret;
		}
		else
		{
			WriteLog(ld, "[%d] we cannot go to this move...", pthread_self());
			ident->savPos = ret;
			return bad;
		}
	}
	else if (ident->count == 0)
	{
		ident->count = -1;
	}
//	delete pf;
	return ret;
#else
	
#ifdef DEBUG
	IThread *ident = pthread_getspecific(PKey);
#endif
	Pos ret = bad;
	int Valid[4] = { 0,0,0,0 }, NbValid = 0;
	
	cur.col++;
	if (isFree(cur)) Valid[NbValid++] = DROITE;
	cur.col-=2;
	if (isFree(cur)) Valid[NbValid++] = GAUCHE;
	cur.col++;
	cur.lig--;
	if (isFree(cur)) Valid[NbValid++] = HAUT;
	cur.lig+=2;
	if (isFree(cur)) Valid[NbValid++] = BAS;
	cur.lig--;

	if (!NbValid)
	{
#ifdef DEBUG
		WriteLog(ld, "[%d] 0 choices of moves...", ident->Num);
#endif 
		return ret;
	}

	ret = cur;
	srand(time(0));

	switch (Valid[rand()%(NbValid)])
	{
		case DROITE:
			ret.col++;
#ifdef DEBUG
			WriteLog(ld, "[%d] MOV DROITE", ident->Num);
#endif
		break;
		case GAUCHE:
			ret.col--;
#ifdef DEBUG
			WriteLog(ld, "[%d] MOV GAUCHE", ident->Num);
#endif
		break;
		case HAUT:
			ret.lig--;
#ifdef DEBUG
			WriteLog(ld, "[%d] MOV HAUT", ident->Num);
#endif
		break;
		case BAS:
			ret.lig++;
#ifdef DEBUG
			WriteLog(ld, "[%d] MOV BAS", ident->Num);
#endif
		break;
		default:
			cur = bad;
		break;
	}
//WriteLog(ld, "test");
	return ret;
#endif
}

/*
 * But: Attendre un nombre de nanoseconde donne
 *
 */
void 	MicroWait(unsigned int w)
{
	struct timespec tm;
	tm.tv_sec = 0;
	tm.tv_nsec = w;
	nanosleep(&tm, NULL);
}


/*
 * But: Afficher le temps écoulé depuis le début du programme.
 *
 */
extern "C"
{
void *	pthread_chrono (void *arg)
{
	sigset_t sset;
	struct sigaction sa;

	sa.sa_handler = handler_alrm;
	sigemptyset(&(sa.sa_mask));
	sigaction(SIGALRM, &sa, NULL);
	
	sigfillset(&sset);
	sigdelset(&sset, SIGALRM);
	pthread_sigmask(SIG_SETMASK, &sset, NULL);

#ifdef DEBUG_CHRONO
	WriteLog(ld, "[%d] Debut thread chrono", pthread_self());
#endif

	GOTOXY(50, 2); 
	printf("Temps d'execution: ");
	fflush(stdout);

	while ( 42 ) 
	{
		alarm(1); 
		pause();
	}
	return NULL;
}
}


/*
 * But: Mise a jour du temps d'execution.
 *
 */
extern "C"
{
void handler_alrm(int s)
{
#ifdef DEBUG_CHRONO
	WriteLog(ld, "[%d] Handler alarm...", pthread_self());
#endif
	GOTOXY(69,2); printf("%d", time(0) - tBegin); GOTOXY(0,0);
#ifdef DEBUG_CHRONO
	WriteLog(ld, "[%d] Fin handler alarm...", pthread_self());
#endif
}
}

/*
 * But: Afficher les compteurs et mettre a jour leur affichage.
 *
 */
extern "C"
{
void *	pthread_compteur(void *arg)
{
	sigset_t sset;
	sigfillset(&sset);
	pthread_sigmask(SIG_SETMASK, &sset, NULL);
	
	pthread_mutex_lock(&MutexNb);
	while(1)
	{
		GOTOXY(25, 3); printf("Entres:  %2d", NbPion); fflush(stdout);
		GOTOXY(25, 4); printf("Sortis:  %2d", NbPionSortis);  fflush(stdout);
		GOTOXY(25, 5); printf("Restant: %2d", NbPion - NbPionSortis); fflush(stdout);
		GOTOXY(0,0);
		pthread_cond_wait(&CondNb, &MutexNb);
	}
	return NULL;
}
}


/*
 * But: Gere le labyrinthe.
 *
 */
extern "C"
{
void *	pthread_maitre(void *arg)
{
	int fd;
	char buf[10];
	sigset_t sset;
	struct sigaction sa;

	sa.sa_handler = handler_int;
	sigemptyset(&(sa.sa_mask));
	sigaction(SIGINT, &sa, NULL);

	sa.sa_handler = handler_quit;
	sigemptyset(&(sa.sa_mask));
	sigaction(SIGQUIT, &sa, NULL);
	
	sigfillset(&sset);
	sigdelset(&sset, SIGINT);
	sigdelset(&sset, SIGQUIT);
	pthread_sigmask(SIG_SETMASK, &sset, NULL);

	/* ecriture du pid */
	fd = open("Laby.pid", O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0700);
	sprintf(buf, "%d\n", getpid());
	write(fd, &buf, strlen(buf)+1);
	close(fd);
	
	while(42) pause();	
	return NULL;
}
}

/*
 * But: Gere le SIGINT
 *
 */
extern "C"
{
void	handler_int(int s)
{
#ifdef DEBUG_MAITRE
	WriteLog(ld, "[%d] Handler SIGINT...", pthread_self());
#endif
	addPion();
}


/*
 * But: Gere le SIGQUIT
 *
 */
void	handler_quit(int s)
{
	int i;
#ifdef DEBUG_MAITRE
	WriteLog(ld, "[%d] Handler SIGQUIT...", pthread_self());
#endif
	for (i=0;i<NB_MAX_PIONS;i++)
		pthread_cancel(ThreadPion[i]);
	pthread_exit(NULL);	
}
}
/*
 * But: Ecris dans Sortie.log lors de la terminaison du thread
 *
 */
//th_7 :: entree = 11 , sortie = 48 , temps reste dans le labyrinthe = 37
extern "C"
{
void	pthread_clean(void *arg)
{
	IThread *ident = (IThread *)pthread_getspecific(PKey);
	if (ident)
	{
		ident->Sortie = time(0) - tBegin;
		WriteLog(sd, "Tread [%d]\t:: entree = %4d, sortie = %4d, temps = %4d, %s",  ident->Num,
											ident->Entree,
											ident->Sortie,
									ident->Sortie - ident->Entree,
						(ident->State==SOK)?"Termine correctement":"A ete kille");
	}
	else
	{
		WriteLog(sd, "Thread [%d]\t:: Unable to get specific... end at %d", pthread_self(), tBegin);
	}
#ifdef DEBUG_CLEAN
	WriteLog(ld, "[%d] Clean called...", pthread_self());
#endif
	
}
}


/*
 * But: Thread de controle de pion manuel.
 *
 */
extern "C"
{
void *	pthread_manual(void *arg)
{
	sigset_t sset;
	int sig, fd, pid;
	struct sigaction sa;
	char buf[10];
	Pos cur = begin;
	Pos old = begin;
	Pos next;
	State	*SThread = (State *) malloc(sizeof(State));
	SThread->cur = &cur;
	SThread->old = &old;


	pthread_setspecific(PKey, SThread);
#ifdef DEBUG_MANUAL
	WriteLog(ld, "[%d] Enter manual thread", pthread_self());
#endif

	/* ecriture du pid */
	fd = open("Manual.pid", O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0700);
	sprintf(buf, "%d\n", getpid());
	write(fd, &buf, strlen(buf)+1);
	close(fd);

	sa.sa_handler = handler_move;
	sigemptyset(&(sa.sa_mask));
	sigaction(SIGGAUCHE, &sa, NULL);

	sa.sa_handler = handler_move;
	sigemptyset(&(sa.sa_mask));
	sigaction(SIGDROITE, &sa, NULL);

	sa.sa_handler = handler_move;
	sigemptyset(&(sa.sa_mask));
	sigaction(SIGHAUT, &sa, NULL);

	sa.sa_handler = handler_move;
	sigemptyset(&(sa.sa_mask));
	sigaction(SIGBAS, &sa, NULL);

	sigemptyset(&(sa.sa_mask));
	sa.sa_handler = handler_start;
	sigaction(SIGSTART, &sa, NULL);

	sigfillset(&sset);
	sigdelset(&sset, SIGGAUCHE);
	sigdelset(&sset, SIGDROITE);
	sigdelset(&sset, SIGHAUT);
	sigdelset(&sset, SIGBAS);
	sigdelset(&sset, SIGSTART);
	pthread_sigmask(SIG_SETMASK, &sset, NULL);

#ifdef DEBUG_MANUAL
	WriteLog(ld, "[%d] Mask & signal set", pthread_self());
#endif
	pthread_mutex_lock(&MutexManual);
	pthread_cond_wait(&CondManual, &MutexManual);
#ifdef DEBUG_MANUAL
	WriteLog(ld, "[%d] Starting listen...", pthread_self());
#endif

	while(1)
	{	
		pthread_mutex_lock(&MutexMap);

		if (isFree(begin))
		{
//			pthread_mutex_unlock(&MutexMap);

/*
			pthread_mutex_lock(&MutexNb);
	
			pthread_detach(ThreadPion[NbPion]);
			NbPion++;
			pthread_mutex_unlock(&MutexNb);
			pthread_cond_signal(&CondNb);
*/
//			pthread_mutex_lock(&MutexMap);
			changePos(NULL, &cur);
			pthread_mutex_unlock(&MutexMap);

			break;
		}
		else pthread_mutex_unlock(&MutexMap);
		MicroWait(20000000);
	}

	while (42)
	{
		pause();
		if (!posCmp(cur, end))
		{
			pthread_mutex_lock(&MutexMap);
			EffacePion(cur);
			Laby[cur.lig][cur.col] = 0;
			pthread_mutex_unlock(&MutexMap);
			WriteLog(sd, "Tread [%d]\t:: Manual thread Exits..", pthread_self());
			fd = open("Control.pid", 0);
			read(fd, &buf, sizeof(buf));
			pid = atoi(buf);
			kill(pid, SIGHUP);
			break;
		}
	}
	pthread_exit(0);
	return NULL;
}
}
/*
 * But: Gere le SIGSTART et lance un thread de pion manuel.
 *
 */
extern "C"
{
void	handler_start(int s)
{
#ifdef DEBUG_MANUAL
	WriteLog(ld, "[%d] Entering handler_start, launching manual thread", pthread_self());
#endif
	pthread_cond_signal(&CondManual);
}

/*
 * But: tout les handlers de directions:
 *
 */
void 	handler_move		(int move)
{
	Pos next;
	State *SThread = (State *)pthread_getspecific(PKey);
#ifdef DEBUG_MANUAL
	WriteLog(ld, "[%d] Received %d move...", pthread_self(), move);
#endif
	next = *SThread->cur;
	switch(move)
	{
		case SIGGAUCHE:
			WriteLog(ld, "[%d] Move left.", pthread_self());
			next.col--;
		break;
		case SIGDROITE:
			WriteLog(ld, "[%d] Move right.", pthread_self());
			next.col++;
		break;
		case SIGHAUT:
			WriteLog(ld, "[%d] Move up.", pthread_self());
			next.lig--;
		break;
		case SIGBAS:
			WriteLog(ld, "[%d] Move down.", pthread_self());
			next.lig++;
		break;
		default:
			WriteLog(ld, "[%d] Unknown move...", pthread_self());
		break;
	}
	pthread_mutex_lock(&MutexMap);
	if (isFree(next))
	{
			*SThread->old = *SThread->cur;
			changePos(SThread->old, &next);
			*SThread->cur = next;
	}
	pthread_mutex_unlock(&MutexMap);
}
}
