#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "mpi.h"
#include "header.h"

#ifndef FUNCTIONS_C
#define FUNCTIONS_C

/*
 * compilation of the used functions to solve the laplace equation.
 * variables are named consistently with the input parameters, thus
 * further no explanations are given considering these.
 * */

/*
 * this function performs a check whether
 * the directory 'char *in' is available or not.
 * */
void check_file(char *in, char *tags){
	FILE *file;
	if(0==strcmp(tags, "r")){
		if(NULL==(file=fopen(in, "r"))){
			printf("failed to open %s for reading.\n", in);
			abort();
		}
		fclose(file);
	}
	else if(0==strcmp(tags, "w")){
		if(NULL==(file=fopen(in, "w"))){
			printf("failed to open %s for writing.\n", in);
			abort();
		}
		fclose(file);
	}
	else if(0==strcmp(tags, "rw")){
		if(NULL==(file=fopen(in, "r"))){
			if(NULL==(file=fopen(in, "w"))){
				printf("failed to open %s for writing.\n", in);
				abort();
			}
		}
		else{
			printf("%s already exists.\n", in);
			abort();
		}
	}
	else if(0==strcmp(tags, "a")){
		if(NULL==(file=fopen(in, "a"))){
			printf("failed to open %s for appending\n", in);
			abort();
		}
		fclose(file);
	}
}

int map(int ix, int *x, int *y, int X, int Y){
	int i=0,j=0,k=-1;
	while(i<X && ix!=k){
		j=0;
		while(j<Y && ix!=k){
			k=j*X+i;
			j++;
		}
		i++;
	}
	if(k==ix){
		*(x)=i-1;
		*(y)=j-1;
		return 0;
	}
	else{
		printf("int map(%d,...,X=%d, Y=%d) failed to find coordinates\n", ix, X, Y);
		return 1;
	}
}
/*
 * check if (ix, iy) is set to a potential
 * */
int check_potential(int ix, int iy, int NPHI, pot *phi0){
	int n;
	for(n=0; n<NPHI; n++){
		if(ix>=phi0[n].pos[0] && ix<=phi0[n].pos[1] && iy>=phi0[n].pos[2] && iy<=phi0[n].pos[3])
			return 1;
	}
	return 0;
}

/*
 * check if (ix, iy) is inside of the grounded area
 */
int check_ground(int ix, int iy, int NGROUND, int **ground){
	int n;
	for(n=0; n<NGROUND; n++){
		if(ix>ground[n][0] && ix <ground[n][1] && iy>ground[n][2] && iy<ground[n][3])
			return 1;
	}
	return 0;
}


/*
 * average over grounded lattice sites. separate grounded areas are averaged independently.
 *
 * NOTE: only the boundary of the grounded sites is computed void jacobi() thus only these lattice sites
 * may contribute to the average. the interior of the grounded areas is set to this average.
 * */
void ground_it(int NGROUND, int **ground, int X, double *phi){
	int j, x, y, N=0;
	double buf;
	for(j=0; j<NGROUND; j++){
		for(N=0, buf=0., x=ground[j][0], y=ground[j][2]; x<=ground[j][1]; x++, N++)
			buf+=phi[y*X+x];

		for(x=ground[j][0], y=ground[j][3]; x<=ground[j][1]; x++, N++)
			buf+=phi[y*X+x];

		for(y=ground[j][2]+1, x=ground[j][0]; y<=ground[j][3]-1; y++, N++)
			buf+=phi[y*X+x];

		for(y=ground[j][2]+1, x=ground[j][1]; y<=ground[j][3]-1; y++, N++)
			buf+=phi[y*X+x];

		buf/=(double)N;
		for(x=ground[j][0]; x<=ground[j][1]; x++){
			for(y=ground[j][2]; y<=ground[j][3]; y++){
				phi[y*X+x]=buf;
			}
		}
	}
}

/*
 * Implementation of the jacobi algorithm to solve a 2d-laplace equation.
 * char *dif is the output directory for the precision and iteration-number.
 * char *logf is the output directory for the total required number of iterations,
 * the lattice volume of the solution, the total time for the solution and its actual
 * precision.
 * */
void jacobi(int X, int Y, double NP, double *phi, int NPHI,
		pot *phi0, int NGROUND, int **ground, char *dif, char *logf){
	double *dum, pres=1.+NP, buf=0.;
	int x, y, i, ix;
	double t1, t2;
	FILE *file;
	/*MPI_Wtime() returns the current time of the process in seconds with double-precision. meaning
	 * that this function allows for a much more precise estimate for the required time than any function
	 * defined in time.h where the highest precision is in seconds. For example consider a lattice of the size 8x8,
	 * the solution of this lattice is computed in the order of 10e-4 seconds.
	 * */
	t1 = MPI_Wtime();
	/*
	 * double *dum is used to store the new solution of the laplace equation.
	 * */
	dum=malloc(X*Y*sizeof(double));
	for(ix=0; ix<X*Y; ix++)
		dum[ix]=phi[ix];

	check_file(dif, "w");
	file=fopen(dif, "w");
	i=0;
	/*
	 * loop over either the precision (pres) if NP<1.
	 * or the iteration number (i) if NP>1.
	 * */
	while((i<(int)NP && NP>1.) || (pres>NP && NP<1.)){
		for(x=1; x<X-1; x++){/*loop over the volume of the lattice, excluding the boundary*/
			for(y=1; y<Y-1; y++){
				ix=y*X+x;
				/*
				 * check if (x,y) is inside of the grounded area or if it is
				 * predefined by a potential.
				 * */
				if(check_potential(x, y, NPHI, phi0)==0 && check_ground(x, y, NGROUND, ground)==0){
					dum[ix]=(phi[(y-1)*X+x]+phi[(y+1)*X+x]+phi[y*X+(x-1)]+phi[y*X+(x+1)])/4.;
					/*dum[ix]=(1.-r)*phi[ix]+r*0.25*(phi[(y-1)*X+x]+phi[(y+1)*X+x]+phi[y*X+(x-1)]+phi[y*X+(x+1)]);*/
				}
			}
		}/*end loop over lattice-volume
		average over the grounded area
		*/
		ground_it(NGROUND, ground, X, dum);
		buf=0.;/*buffer for the precision*/
		for(x=1; x<X-1; x++){
			for(y=1; y<Y-1; y++){
				ix=y*X+x;
				if(fabs(phi[ix]-dum[ix])>buf)
					buf=fabs(phi[ix]-dum[ix]);/*find the maximum absolute change from the previous step to this one*/
				phi[ix]=dum[ix];/*copy the new solution*/
			}
		}
		fprintf(file, "%d %.6e\n", i, buf);/*output for the current precision and iteration-number*/
		pres=buf;
		i++;
	}
	fclose(file);
	/*
	 * logfile output: lattice extent, method, volume, iteration-number, precision and total time.
	 * */
	check_file(logf, "a");
	file=fopen(logf, "a");
	fprintf(file, "###################################################\n");
	fprintf(file, "#X = %d\t Y = %d\t jacobi\n", X, Y);
	fprintf(file, "#\tX*Y\tN\tP\tR\tTime\n");
	t2 = MPI_Wtime();
	fprintf(file, "%8d %8d %.2e %.4e\n", X*Y, i-1, pres, t2-t1);
	fclose(file);
	free(dum);/*free memory required for the temporary array*/
}

/**
 * Implementation of the red-black algorithm for the solution of the 2d-laplace equation.
 * Since it is largely the same as the jacobi-algorithm only changes to the algorithm are commented.
 *
 * NOTE: grounded areas are not computed with this method.
 */
void red_black(int X, int Y, double NP, double *phi, int NPHI,
		pot *phi0, char *dif, char *logf){
	double *dum, pres=1.+NP, buf=0.;
	int x, y, i,ix, eo;
	double t1, t2;
	FILE *file;
	t1 = MPI_Wtime();
	dum=malloc(X*Y*sizeof(double));
	for(ix=0; ix<X*Y; ix++)
		dum[ix]=phi[ix];

	check_file(dif, "w");
	file=fopen(dif, "w");
	i=0;
	while((i<(int)NP && NP>1.) || (pres>NP && NP<1.)){
		/*loop over even and odd lattice sites, which are ordered like:
		 * eoeoeo
		 * oeoeoe
		 * eoeoeo
		 * oeoeoe
		 * */
		for(eo=0; eo<2; eo++){
			for(x=1; x<X-1; x++){
				for(y=1; y<Y-1; y++){
					if((x+y)%2==eo && check_potential(x, y, NPHI, phi0)==0){/*compute either even or odd lattice sites*/
						dum[y*X+x]=(phi[(y-1)*X+x]+phi[(y+1)*X+x]+phi[y*X+(x-1)]+phi[y*X+(x+1)])/4.;
					}
				}
			}
			/*partial update of either even or odd lattice sites.*/
			for(x=1, buf=0.; x<X-1; x++){
				for(y=1; y<Y-1; y++){
					if((x+y)%2==eo){
						ix=y*X+x;
						if(fabs(phi[ix]-dum[ix])>buf)
							buf=fabs(phi[ix]-dum[ix]);

						phi[ix]=dum[ix];
					}
				}
			}
		}
		fprintf(file, "%d %.6e\n", i, buf);
		pres=buf;
		i++;
	}
	fclose(file);
	check_file(logf, "a");
	file=fopen(logf, "a");
	fprintf(file, "###################################################\n");
	fprintf(file, "#X = %d\t Y = %d\t red-black\n", X, Y);
	fprintf(file, "#\tX*Y\tN\tP\tR\tTime\n");
	t2 = MPI_Wtime();
	fprintf(file, "%8d %8d %.2e %.4e\n", X*Y, i-1, pres, t2-t1);
	fclose(file);
	free(dum);
}

/**
 * Implementation of the gauss-seidel algorithm for the solution of the 2d-laplace equation.
 * Since it is largely the same as the jacobi-algorithm only changes to the algorithm are commented.
 *
 * NOTE: grounded areas are not computed with this method.
 */
void gauss_seidel(int X, int Y, double NP, double *phi, int NPHI,
		pot *phi0, char *dif, char *logf){
	double  pres=1.+NP, buf=0., buf2=0.;
	int x, y, i, ix;
	double t1, t2;
	FILE *file;
	t1 = MPI_Wtime();
	check_file(dif, "w");
	file=fopen(dif, "w");
	i=0;
	while((i<(int)NP && NP>1.) || (pres>NP && NP<1.)){
		for(y=1, buf2=0.; y<Y-1; y++){
			for(x=1; x<X-1; x++){
				ix=y*X+x;
				if(check_potential(x, y, NPHI, phi0)==0){
					/*the buffer is only needed to compute the current precision*/
					buf=(phi[(y-1)*X+x]+phi[(y+1)*X+x]+phi[y*X+(x-1)]+phi[y*X+(x+1)])/4.;
					if(fabs(phi[ix]-buf)>buf2){
						buf2=fabs(phi[ix]-buf);
					}
					phi[ix]=buf;/*update of the new step within the same loop as the actual computation.*/
				}
			}
		}
		pres=buf2;
		fprintf(file, "%d %.6e\n", i, pres);
		i++;
	}
	fclose(file);
	check_file(logf, "a");
	file=fopen(logf, "a");
	fprintf(file, "###################################################\n");
	fprintf(file, "#X = %d\t Y = %d\t gauss-seidel\n", X, Y);
	fprintf(file, "#\tX*Y\tN\tP\tR\tTime\n");
	t2 = MPI_Wtime();
	fprintf(file, "%8d %8d %.2e %.4e\n", X*Y, i-1, pres, t2-t1);
	fclose(file);
}


#endif






