/*
Author: Eric Thibodeau
Date:	19/07/2006
Course: MGL810
Subject: PThreaded calculation of a string vibration
Good reference: http://math.arizona.edu/~swig/documentation/pthreads/
*/
// #define DEBUG
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <sys/times.h>
#include <time.h>
#include <pthread.h>
#include <semaphore.h>

#include "calc_point.h"
#include "barrier.h"

// #include "pthread_gate.h"

#define X_RESN   800		/* largeur de la fenetre par defaut */
#define Y_RESN   200		/* heuteur de la fenetre par defaut */
#define MAX_ITER 100		/* max iterations par defaut */

#define IMAG_MIN -1
#define IMAG_MAX  1
#define WHITE     16777215
#define BLACK     0

#define timeadd(a,b) pthread_mutex_lock(&TimerMutex);\
					a.tv_sec+=b.tv_sec;\
					if ((a.tv_usec+b.tv_usec) > 1000000)\
					{\
						a.tv_usec+=b.tv_usec-1000000;\
 						a.tv_sec++;\
					}\
 					else \
						a.tv_usec+=b.tv_usec;\
					pthread_mutex_unlock(&TimerMutex);

#define time2double(a) ((double) a.tv_sec + (double) a.tv_usec/1000000)
//TIC_START tags timeofday into start
#define TIC_START(start)  pthread_mutex_lock(&TimerMutex);\
						gettimeofday(&start,NULL);\
						pthread_mutex_unlock(&TimerMutex);

//TIC_STOP tags time difference between start TAG and _now_ and adds it to GV.t_store
#define TIC_STOP(t_store,t_start){ struct timeval t_stop,t_diff;\
	gettimeofday(&t_stop,NULL);\
	timeval_subtract(&t_diff,&t_stop,&t_start);\
	timeadd(t_store,t_diff);\
}

pthread_mutex_t	TimerMutex = PTHREAD_MUTEX_INITIALIZER;

typedef struct
{
	// 	PThread variables
	barrier_t	 barrierStart,barrierStop;
	sem_t        semStart;
	sem_t        semStop;
	pthread_mutex_t	StartMutex,StopMutex;
	pthread_cond_t	condStart,condStop;
	unsigned int ThreadCount;
	unsigned int ThreadIdCounter, ThreadsReady;

	// 	Problem (Commandline) parameters
	unsigned long StrLen;
	unsigned int maxiters;

	// String variables
	double * String, * String_h1, * String_h2;

	//	GUI variables
	Display *display;
	Window win;
	GC gc;
	int GUI;				// Enable/Disable GUI
	short width, height;	// XDrawPoint(3x11) limits display sizes to short
	short offset;
	int screen;
	XPoint * OldPoints, * NewPoints;
	unsigned long white;
	unsigned long black;

	// Timer variables
// 	struct timeval t_start,t_stop,t_idle,t_diff,t_comm,t_disp;
	struct timeval t_start,t_idle,t_diff,t_comm,t_disp;

} GlobalVars;

Display * DisplayInit(GlobalVars * GV);
int  timeval_subtract (struct timeval *, struct timeval *, struct timeval *);
void * CalcDispThread(void * GlobVals);

int main (int argc, char *argv[])
{
	GlobalVars GV;

	unsigned short	excite=1;	//cycles of excitation
	unsigned int	excitePoint;//Location of excitation point
	unsigned int	i=0,j=0;
	int 			rc; 		// General ReturnCode container

// 	**************************** PThread variables ***************************
	pthread_t * tid;
	pthread_attr_t attr;

// 	**************************** Timer variables ***************************
	char dummy;		/* faire un scanf pour inserer une pause avant de quiter */
	struct tms t;	/* Pour le calcul du temps d'exeution */

// 	-------------------VARIABLE DECLARATIONS END----------------------------

// 	-------------------Commandline parsing --------------------------------
	if ( (strcmp(argv[1], "-h") == 0) || (argc < 5) )
	{
		fprintf (stderr, "Usage:  string [maxiter] [GV.width] [GV.height] [GV.String Length] [GUI 0/1/2] [Thread count]\n");
		exit (EXIT_FAILURE);
	}

	GV.maxiters    = atoi(argv[1]);
	GV.width       = atoi(argv[2]);
	GV.height      = atoi(argv[3]);
	GV.StrLen      = atoi(argv[4]);
	GV.GUI         = atoi(argv[5]);
	GV.ThreadCount = atoi(argv[6]);

// 	-------------------Commandline parsing  END----------------------------

//	Global Initialization
	GV.t_comm.tv_sec = GV.t_comm.tv_usec = 0;
	GV.t_disp.tv_sec = GV.t_disp.tv_usec = 0;
	GV.t_idle.tv_sec = GV.t_idle.tv_usec = 0;

	excitePoint=GV.StrLen/2;

	if (GV.GUI)
	{
		GV.display = DisplayInit(&GV);
	}

	GV.String    = (double*) malloc(sizeof(double)*GV.StrLen);
	GV.String_h1 = (double*) malloc(sizeof(double)*GV.StrLen);
	GV.String_h2 = (double*) malloc(sizeof(double)*GV.StrLen);
	if( GV.String == NULL || GV.String_h1 == NULL || GV.String_h2 == NULL )
	{
		printf("GV.String Malloc error, exiting\n");
		return 1;
	}
	else
	{
		// String Initialization
		for (i=0; i<GV.StrLen; i++) GV.String[i] = 0.0;
		memcpy((void*)GV.String_h1,(void*)GV.String,GV.StrLen);
		memcpy((void*)GV.String_h2,(void*)GV.String,GV.StrLen);
	}
	if(GV.GUI)
	{
		GV.offset    = GV.height/2;
		GV.OldPoints = (XPoint*) malloc(GV.StrLen*sizeof(XPoint));
		GV.NewPoints = (XPoint*) malloc(GV.StrLen*sizeof(XPoint));
		if( GV.OldPoints == NULL || GV.NewPoints == NULL )
		{
			printf("Points Malloc error, exiting\n");
			return 1;
		}
		for (i=0; i<GV.StrLen; i++)
		{
			GV.OldPoints[i].x = GV.NewPoints[i].x = i;
			GV.OldPoints[i].y = (short) (GV.offset + GV.offset * GV.String[i] );
		}
	}

	tid = (pthread_t *) malloc( GV.ThreadCount * sizeof(pthread_t));
	if(tid == NULL)
	{
		printf("pthread_t Malloc error, exiting\n");
		return 1;
	}

// 	Now that the workplace is set up, we initialize the Mutexes and create the worker threads :
// 	Partially extracted from synchro.c given in class
// 	barrier.h was extracted from D. Butenhof, PROGRAMMING WITH POSIX THREADS, (page 245). (c) 1997 Addison-Wesley-Longman Inc.
	barrier_init(&GV.barrierStart, (GV.ThreadCount+1) ); // We add 1 to the ThreadCount since the main thread will also use it
	barrier_init(&GV.barrierStop , (GV.ThreadCount+1) ); // We add 1 to the ThreadCount since the main thread will also use it
	pthread_mutex_init (&GV.StartMutex, NULL);
// 	pthread_mutex_init (&GV.StopMutex , NULL);
// 	sem_init (&GV.semStart, 0, 0); // Synchronizing Semaphore
// 	sem_init (&GV.semStop , 0, 0); // Synchronizing Semaphore
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

	GV.ThreadsReady=0;
	GV.ThreadIdCounter=0;
// 	pthread_mutex_lock (&GV.StartMutex); // We don't want the threads to get ahead of themselves...
	for (i = 0; i < GV.ThreadCount; i++)
	{
		rc = pthread_create(&tid[i], &attr, CalcDispThread, &GV);
		if (rc) {
			printf("Thread creation error; pthread_create() error code: %d\n", rc);
			exit(-1);
		}
	}

	while(--GV.maxiters)
	{
		if( excite )
		{
		//Dirac string excitation
			GV.String_h1[excitePoint] = -1.0;
		//Sin string excitation
//		GV.String_h1[excitePoint]=sin(GV.maxiter);
			excite--;
		}
		barrier_wait(&GV.barrierStart);
		barrier_wait(&GV.barrierStop );
		if(GV.GUI && GV.GUI != 4)
		{
			short x;
			TIC_START(GV.t_start);
			//Convert data for displaying it:
			for (x=0; x<GV.StrLen; x++)
			{
				GV.NewPoints[x].y = (short) (GV.offset + GV.offset * GV.String[x]);
			}
			TIC_STOP(GV.t_disp,GV.t_start);
			TIC_START(GV.t_start);
// 			XLockDisplay(GV.display);
			//Delete present lines and leave a shadow of Color bgColor:
			XSetForeground	(GV.display, GV.gc, GV.white>>8);
			XDrawLines		(GV.display, GV.win, GV.gc, GV.OldPoints, GV.StrLen, CoordModeOrigin);
			//Draw new lines
			XSetForeground	(GV.display, GV.gc, GV.black);
			XDrawLines		(GV.display, GV.win, GV.gc, GV.NewPoints, GV.StrLen, CoordModeOrigin);
			XFlush (GV.display);
// 			XUnlockDisplay(GV.display);
			TIC_STOP(GV.t_disp,GV.t_start);
		} // GV.GUI
		//switch historical strings and display variables around:
		{
			double * ptrDbl;
			XPoint * tempPoints;

			ptrDbl      = GV.String_h2;
			GV.String_h2= GV.String_h1;
			GV.String_h1= GV.String;
			GV.String   = ptrDbl;

			tempPoints   = GV.OldPoints;
			GV.OldPoints = GV.NewPoints;
			GV.NewPoints = tempPoints;
		}

	} // while(Maxiter)

	if (GV.GUI)
	{
		free(GV.OldPoints);
		free(GV.NewPoints);
		if (GV.GUI == 2) do{
			// Attendre la reponse de l'usager, sortir apres
				fprintf (stderr, "Taper q pour quitter le programme.\n");
				fscanf (stdin, "%c", &dummy);
		}while (dummy != 'q');
	}

// 	Extract execution times according to the OS
	times(&t);

	{
		double CPU_time=(double) (t.tms_utime + t.tms_stime + t.tms_cutime + t.tms_cstime) / CLK_TCK;
		fprintf(stdout,"T_comm,t_disp,t_idle,t_CPU,Tot\n");
		fprintf(stdout,"%f\t%f\t%f\t%f\t%f\n",
				time2double(GV.t_comm),
				time2double(GV.t_disp),
				time2double(GV.t_idle),
				CPU_time,
				time2double(GV.t_comm)+time2double(GV.t_disp)+time2double(GV.t_idle)+CPU_time);
	}

	barrier_destroy(&GV.barrierStart);
	barrier_destroy(&GV.barrierStop );
	return EXIT_SUCCESS;
}

Display * DisplayInit(GlobalVars * GV)
{
	unsigned int	border_width,
					x = 0, y = 0,				// GV->screen position
					disp_width, disp_height;	// GV->screen size
	char *window_name = "String Vibration", *disp_name = NULL;
	unsigned long valuemask = 0;

	// 	Xlib types:
	XGCValues  values;
	XSizeHints size_hints;
	XSetWindowAttributes attr[1];
	// This is a multi-threaded environment!
	if ( ! XInitThreads() )
	{
		fprintf(stderr,"Failed to start with XInitThreads, exiting\n");
		exit(-1);
	}

	/* Connection to Xserver */
	if ( (GV->display = XOpenDisplay (disp_name)) == NULL ) {
		fprintf (stderr, "drawon: cannot connect to X server %s\n",
				 XDisplayName (disp_name) );
		exit (EXIT_FAILURE);
	}

	/* GV->GUI initialization */

	GV->screen  = DefaultScreen(GV->display);
	disp_width  = DisplayWidth (GV->display, GV->screen);
	disp_height = DisplayHeight(GV->display, GV->screen);

	border_width = 4;
	GV->win = XCreateSimpleWindow (GV->display, RootWindow (GV->display, GV->screen),
							   x, y, GV->width, GV->height, border_width,
							   BlackPixel (GV->display, GV->screen),
							   WhitePixel (GV->display, GV->screen));

	size_hints.flags = USPosition|USSize;
	size_hints.x = x;
	size_hints.y = y;
	size_hints.width  = GV->width;
	size_hints.height = GV->height;
	size_hints.min_width  = 300;
	size_hints.min_height = 300;

	XSetNormalHints (GV->display, GV->win, &size_hints);
	XStoreName(GV->display, GV->win, window_name);

	/* GV->GUI Context */
	GV->gc = XCreateGC (GV->display, GV->win, valuemask, &values);

	/* White and Black color values (depends on GV->display color depth) */
	GV->white = WhitePixel (GV->display, GV->screen);
	GV->black = BlackPixel (GV->display, GV->screen);
// 		fprintf (stderr,"white: %lu black: %lu\n",white,black);

	XSetBackground (GV->display, GV->gc, GV->white);
	XSetForeground (GV->display, GV->gc, GV->black);
	XSetLineAttributes (GV->display, GV->gc, 1, LineSolid, CapRound, JoinRound);

	attr[0].backing_store  = Always;
	attr[0].backing_planes = 1;
	attr[0].backing_pixel  = GV->white;

	XChangeWindowAttributes(GV->display, GV->win,
							CWBackingStore | CWBackingPlanes | CWBackingPixel,
							attr);
	XMapWindow (GV->display, GV->win);
	XSync(GV->display, 0);
	return GV->display;
}

/* timeval_subtract extracted from http://www.gnu.org/software/libc/manual/html_mono/libc.html#Elapsed%20Time*/

/* Subtract the `struct timeval' values X and Y,
        storing the result in RESULT.
        Return 1 if the difference is negative, otherwise 0.
		result = x - y*/

int timeval_subtract (result, x, y)
		struct timeval *result, *x, *y;
{
	/* Perform the carry for the later subtraction by updating y. */
	if (x->tv_usec < y->tv_usec) {
		int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
		y->tv_usec -= 1000000 * nsec;
		y->tv_sec += nsec;
	}
	if (x->tv_usec - y->tv_usec > 1000000) {
		int nsec = (x->tv_usec - y->tv_usec) / 1000000;
		y->tv_usec += 1000000 * nsec;
		y->tv_sec -= nsec;
	}

       /* Compute the time remaining to wait.
	tv_usec is certainly positive. */
	result->tv_sec = x->tv_sec - y->tv_sec;
	result->tv_usec = x->tv_usec - y->tv_usec;

	/* Return 1 if result is negative. */
	return x->tv_sec < y->tv_sec;
}

void * CalcDispThread(void * GlobVars)
{
	GlobalVars * GV = (GlobalVars *) GlobVars;
	unsigned long bgColor  = GV->white;
	unsigned int  iters    = GV->maxiters; 	// For sync. simplicity, we will keep track of iterations locally
	unsigned int  ThreadID;

	unsigned int  idxStart=0,idxStop=0, ChunkSize=0,x;

	pthread_mutex_lock (&GV->StartMutex);
	ThreadID = GV->ThreadIdCounter++;
	pthread_mutex_unlock (&GV->StartMutex);

	bgColor  = GV->white - ThreadID*3; //different colours for different threads ;)

	// General case:
	ChunkSize = GV->StrLen / GV->ThreadCount;
	idxStart  = ChunkSize  * ThreadID       ;

	if ( ThreadID == (GV->ThreadCount - 1) )		// last thread
	{
		ChunkSize += GV->StrLen % GV->ThreadCount - 1 ; // Collect the leftovers and offset - 1 for end of string
	}
	idxStop   = idxStart + ChunkSize ;
	ChunkSize++; // Off by one due to 0 indexing
	while(iters--)
	{
// 		PTHread style Barrier
		barrier_wait(&GV->barrierStart);

		calc_point(&GV->String[idxStart],
				   &GV->String_h1[idxStart],
				   &GV->String_h2[idxStart],
				   ChunkSize, 0.5);

#ifdef DEBUG
		fprintf(stderr,"ThreadID: %i Iter: %i idxStart: %i idxStop: %i ChunkSize: %i\n",ThreadID,iters,idxStart,idxStop,ChunkSize);
		fflush(stderr);
#endif
		if(GV->GUI == 4)
		{
			TIC_START(GV->t_start);
			//Convert data for displaying it:
			for (x=idxStart; x<=idxStop; x++)
			{
				GV->NewPoints[x].y = (short) (GV->offset + GV->offset * GV->String[x]);
			}
			TIC_STOP(GV->t_disp,GV->t_start);
			TIC_START(GV->t_start);
			XLockDisplay(GV->display);
			//Delete present lines and leave a shadow of Color bgColor:
			XSetForeground	(GV->display, GV->gc, bgColor);
			XDrawLines		(GV->display, GV->win, GV->gc, &GV->OldPoints[idxStart], ChunkSize, CoordModeOrigin);
			//Draw new lines
			XSetForeground	(GV->display, GV->gc, GV->black);
			XDrawLines		(GV->display, GV->win, GV->gc, &GV->NewPoints[idxStart], ChunkSize, CoordModeOrigin);
			XFlush (GV->display);
			XUnlockDisplay(GV->display);
			TIC_STOP(GV->t_disp,GV->t_start);
		} // GV->GUI
		barrier_wait(&GV->barrierStop);
	}
	pthread_exit(NULL);
}
