/*
Author: Eric Thibodeau
Date:	19/07/2006
Course: MGL810
Subject: MPI Parallel calculation of a string vibration
MPI reference: MPI The Complete Reference, Volume 1, The MPI Core, second edition.
MPI_SENDRECV() --> p66.
*/
#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 <mpi.h>

// #define DEBUG
// #define DEBUG1
#define DEBUG2

#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 REAL_MIN  0
#define IMAG_MIN -1
#define REAL_MAX  X_RESN
#define IMAG_MAX  1
#define WHITE     16777215
#define BLACK     0

#define timeadd(a,b) 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;

#define time2double(a) ((double) a.tv_sec + (double) a.tv_usec/1000000)
//TIC_START tags timeofday into start
#define TIC_START(start) gettimeofday(&start,NULL);

//TIC_STOP tags time difference between start TAG and _now_ and adds it to t_store
#define TIC_STOP(t_store,start) gettimeofday(&t_stop,NULL);\
						timeval_subtract(&t_diff,&t_stop,&t_start);\
						timeadd(t_store,t_diff);



/* 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;
}

/*
Calcul du déplacement d'une section de corde délimité par les positions en X par
les paramètres start et stop.
Variables d'entrée:
C = Constante d'élasticité de la corde
Length = La longueur de la corde (nb de pts sur la corde)
Variables modifiés:
Corde
Corde_h1
Corde_h2
*/
void calc_point(double * Corde, double * Corde_h1, double * Corde_h2, unsigned int Length, double C)
{
	unsigned int i;
	double Pos_avant2;
	for(i=1; i<Length; i++) //on a besoin de i±1 pour le calcul, 
							//donc commencer à 1 et arrêter à Length -1
	{
		Pos_avant2= 2.0 * Corde_h1[i];
		Corde[i]=Pos_avant2 - Corde_h2[i] + ( C * ( Corde_h1[i-1] - Pos_avant2 + Corde_h1[i+1] ));
	}
	return ;
}

/*
Calcul de la moyenne du voisinnage pour permettre la requantification de la corde pour permettre
l'affichage d'une corde plus grande que ce qui peut etre affiché dans la fenêtre présente
*/
/*double * requant(const double *Corde,
				 double *Corde_requant,
				 const unsigned int Length,
				 const unsigned int newLength,
				 const unsigned int window)
{
	unsigned int i,j;
	for(i=1; i<Length; i++) //on a besoin de i±1 pour le calcul, 
							//donc commencer à 1 et arrêter à Length -1
	{
		for (j=0;j<window;j++)
		{
			Corde_requant[i]=
		}
		Pos_avant2=2.0*Corde_h1[i];
		Corde[i]=Pos_avant2-Corde_h2[i]+( C * ( Corde_h1[i-1] - Pos_avant2 + Corde_h1[i-1] ));
	}
	return ;
}*/

/* ---- Fonction principale ------------------------------------------------ */

int main (int argc, char *argv[])
{

	/*Variable pour l'afffichage*/
	Display *display;
	Window win;			/* initialisation de la fenetre */
	GC gc;
	int GUI;			/* Oui ou non pour l'affichage*/
	
	/* Variables partagees*/

	short width = 0, height = 0;			/* taille de la fenetre, limité à short selon man XDrawPoint(3x11) */

	/* Variables de la corde  */
	unsigned long Length;
	
	int maxiter = 0;		/* maximum d'iterations */
		
	/* Autres variables */
	
	char dummy;		/* faire un scanf pour inserer une pause avant de quiter */
	struct tms t;	/* Pour le calcul du temps d'exeution */
	struct timeval t_start,t_stop,t_idle,t_start_c,t_stop_c,t_diff,t_comm,t_disp;

	t_comm.tv_sec = t_comm.tv_usec = 0;
	t_disp.tv_sec = t_disp.tv_usec = 0;
	t_idle.tv_sec = t_idle.tv_usec = 0;
	
	unsigned long  white=WHITE, black=BLACK;	/* valeurs des pixels blancs et noirs */
	
	unsigned int    x, y,						/* position de la fenetre */
					border_width,				/* largeur des bords en pixels */
					disp_width, disp_height,	/* taille de l'ecran */
					screen;						/* ecran */

	char *window_name = "String Vibration", *disp_name = NULL;
	unsigned long valuemask = 0;
	XGCValues values;
	XSizeHints size_hints;

	XSetWindowAttributes attr[1];
		

	/* Les arguments de la commande s'il y en a */
	/* Utilisation:
		string-mpi [maxiter] [width] [height]
		ou
		string-mpi -h pour afficher le message
	*/

	if ((argc > 1) && (strcmp(argv[1], "-h") == 0)) 
	{
		fprintf (stderr, "Usage:  string [maxiter] [width] [height] [String Length] [GUI 0/1/2]\n");
		exit (EXIT_FAILURE);
	}

	if (argc > 1) maxiter = atoi(argv[1]);
	if (argc > 2) width   = atoi(argv[2]);
	if (argc > 3) height  = atoi(argv[3]);
	if (argc > 4) Length  = atoi(argv[4]);
	if (argc > 5) GUI     = atoi(argv[5]);
	
	maxiter = (maxiter > 0) ? maxiter : MAX_ITER;
	width   = (width   > 0) ? width   : X_RESN;
	height  = (height  > 0) ? height  : Y_RESN;
	
	if (GUI)
	{	/* Connection a Xserver */
		if ( (display = XOpenDisplay (disp_name)) == NULL ) {
			fprintf (stderr, "drawon: cannot connect to X server %s\n",
				XDisplayName (disp_name) );
			exit (EXIT_FAILURE);
		}

		/* Initialisation de l'affichage graphique */
	
		screen = DefaultScreen (display);
		disp_width = DisplayWidth (display, screen);
		disp_height = DisplayHeight (display, screen);
	
		x = 0;  y = 0;		/* position de la fenetre  */
	
		border_width = 4;
		win = XCreateSimpleWindow (display, RootWindow (display, screen),
					x, y, width, height, border_width, 
					BlackPixel (display, screen), 
					WhitePixel (display, screen));
	
		size_hints.flags = USPosition|USSize;
		size_hints.x = x;
		size_hints.y = y;
		size_hints.width = width;
		size_hints.height = height;
		size_hints.min_width = 300;
		size_hints.min_height = 300;
	
		XSetNormalHints (display, win, &size_hints);
		XStoreName(display, win, window_name);
	
		/* contexte du graphique */	
		gc = XCreateGC (display, win, valuemask, &values);
		
		/* les valeurs des couleurs blanc et noir */
		white = WhitePixel (display, screen);
		black = BlackPixel (display, screen);
		fprintf (stderr,"white: %lu black: %lu\n",white,black);
		
		XSetBackground (display, gc, white);
		XSetForeground (display, gc, black);
		XSetLineAttributes (display, gc, 1, LineSolid, CapRound, JoinRound);
	
		attr[0].backing_store = Always;
		attr[0].backing_planes = 1;
		attr[0].backing_pixel = white;
	
		XChangeWindowAttributes(display, win, 
				CWBackingStore | CWBackingPlanes | CWBackingPixel,
				attr);
		XMapWindow (display, win);
		XSync(display, 0);
	}

	/* Calculer et afficher les points */
	{
		/*Start of actual processing*/
		unsigned short excite=1; //cycles of excitation
		unsigned int excitePoint=Length/2;
		double * Corde, * Corde_h1, * Corde_h2, * ptrDbl;
		XPoint * OldPoints, * NewPoints;
		short offset=height/2, x;
		unsigned long bgColor = white>>8;
		unsigned int i=0;
		
		Corde    = (double*) malloc(sizeof(double)*Length);
		Corde_h1 = (double*) malloc(sizeof(double)*Length);
		Corde_h2 = (double*) malloc(sizeof(double)*Length);
		if( Corde == NULL || Corde_h1 == NULL || Corde_h2 == NULL )
		{
			printf("Corde Malloc error, exiting\n");
			return 1;
		}
		else
		{
			//Initialisation des Cordes
			for (i=0;i<=Length;i++) Corde[i] =0.0;
			memcpy((void*)Corde_h1,(void*)Corde,Length);
			memcpy((void*)Corde_h2,(void*)Corde,Length);
		}
		if(GUI)
		{
			OldPoints = (XPoint*) malloc(Length*sizeof(XPoint));
			NewPoints = (XPoint*) malloc(Length*sizeof(XPoint));
			if( OldPoints == NULL || NewPoints == NULL )
			{
				printf("Points Malloc error, exiting\n");
				return 1;
			}
#ifdef DEBUG2
			fprintf (stderr,"OldPoints=%p - NewPoints: %p\n",OldPoints,NewPoints);
#endif
		}
		while(maxiter)
		{
// 			//Sin string excitation
// 			if( excite-- ) Corde_h1[0]=sin(maxiter);
			//Dirac string excitation
			if( excite )
			{
				Corde_h1[excitePoint] = -1.0;
				excite--;
			}
// 			else           Corde_h1[0] = Corde_h2[0] = Corde[0] = 0.0;
// 			Corde_h1[0]=sin(maxiter);
#ifdef DEBUG1
			fprintf (stderr,"Corde_h1[0]=%f : Display: %i\n",Corde_h1[0],(short) (IMAG_MIN + height/2 +( Corde[0]  * 1/scale_y )) );
#endif
			//calc_point(double * Corde, double * Corde_h1, double * Corde_h2, unsigned int Length, double C)
			calc_point(Corde, Corde_h1, Corde_h2, Length, 0.5); //last point is anchored to 0
			
			if(GUI)
			{
				TIC_START(t_start);
				//Convert data for displaying it:
				for (x=0;x<=Length;x++)
				{
					OldPoints[x].x = NewPoints[x].x = x;
					OldPoints[x].y = (short) (offset + offset * Corde_h1[x]);
					NewPoints[x].y = (short) (offset + offset * Corde[x]   );
#ifdef DEBUG
					fprintf (stderr,"%i -> %i\n",OldPoints[x].y, NewPoints[x].y);
#endif
				}
				//Delete present points:
				XSetForeground (display, gc, bgColor);
				//XDrawPoints(display, win, gc, OldPoints, Length, CoordModeOrigin);
				XDrawLines(display, win, gc, OldPoints, Length, CoordModeOrigin);
				//redraw
				XSetForeground (display, gc, black);
				//XDrawPoints(display, win, gc, NewPoints, Length, CoordModeOrigin);
				XDrawLines(display, win, gc, NewPoints, Length, CoordModeOrigin);
				//XDrawLine(display, win, gc, 0, height/2, Length, height/2);
				XFlush (display);
				TIC_STOP(t_disp,t_start);
#ifdef DEBUG
				fprintf (stderr, "Appuyer une touche pour continuer (il reste %i itération(s)).\n",maxiter);
				fscanf (stdin, "%c", &dummy);
#endif
			} // GUI
			
			//switch historical strings around:
			ptrDbl  = Corde_h2;
			Corde_h2= Corde_h1;
			Corde_h1= Corde;
			Corde   = ptrDbl;
			maxiter--;
		} // while(Maxiter)
		if(GUI)
		{
#ifdef DEBUG2
			fprintf (stderr,"OldPoints=%p - NewPoints: %p\n",OldPoints,NewPoints);
#endif
			fprintf (stderr, "Free in\n");
			free(OldPoints);
			free(NewPoints);
			fprintf (stderr, "Free out\n");
		}
	}

	if (GUI)
	{
		XFlush (display);
		if (GUI > 1) 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');
	}
	
	/* Calculer le temps d'execution et produire le texte de sortie */
	times(&t);
	
	{
		double CPU_time=(double) (t.tms_utime + t.tms_stime + t.tms_cutime + t.tms_cstime) / CLK_TCK;
		fprintf(stdout,"%f\t%f\t%f\t%f\t%f\n",
				time2double(t_comm),
				time2double(t_disp),
				time2double(t_idle),
				CPU_time,
				time2double(t_comm)+time2double(t_disp)+time2double(t_idle)+CPU_time);
	}

	return EXIT_SUCCESS;
}

