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

/*global variables:
 * X...........extent of the finest lattice in x-direction
 * Y...........extent of the finest lattice in y-direction
 * XL..........extent of the coarsest lattice in x-direction
 * YL..........extent of the coarsest lattice in y-direction
 * L...........size of the box, set to max(XL-1,YL-1)[later on]
 * NP..........# iterations used to solve the laplace equation or precision
 * NPHI........# of different potentials set at the start
 * NGROUND.....# of different grounded areas
 * outdir[]....output directory
 * method[]....method specified in the infile [jacobi / red-black / gauss-seidel]
 * exerc[].....exercise-label
 * *phi0.......pointer to the start-values of the potential
 * **ground....grounded areas.=>ground[#grounded areas][coordinate]
 *             example: ground[0][x1],ground[0][x2],ground[0][y1],ground[0][y2];
 *  */

int X, Y, XL, YL, L, F, NPHI, NGROUND, p=0;
char outdir[1000], method[100], exerc[100];
pot *phi0;
int **ground;
double NP;

/*
 * reads the input parameters and checks them for validity.
 *
 *  variables are explained in readme.in
 *
 * */

void read_infile(char *in) {
        FILE *file;
        int n;
        check_file(in, "r");
        file = fopen(in, "r");
        fscanf(file, "%s\n", exerc);
        fscanf(file, "%s\n", outdir);
        fscanf(file, "%s\n", method);
        if(!(strcmp(method, "gauss-seidel")==0 || strcmp(method, "jacobi")==0 || strcmp(method, "red-black")==0)){
                printf("%s is not a valid method.\n", method);
                abort();
        }/*abort if the method is unknown.*/

        fscanf(file, "X %d\n", &X);
        fscanf(file, "Y %d\n", &Y);
        fscanf(file, "%*s %d\n", &F);
        fscanf(file, "%*s %d\n", &XL);
        fscanf(file, "%*s %d\n", &YL);
        fscanf(file, "%*s %lf\n", &NP);
        fscanf(file, "%*s %d\n", &NPHI);
        phi0 = malloc(NPHI * sizeof(pot));
        for(n=0; n<NPHI; n++){
                fscanf(file, "%d %d %d %d %lf\n", &phi0[n].pos[0], &phi0[n].pos[1],&phi0[n].pos[2],&phi0[n].pos[3],&phi0[n].val);
                if(phi0[n].pos[0]<0 || phi0[n].pos[1]>XL || phi0[n].pos[2]<0 || phi0[n].pos[3]>YL){
                        printf("X=0..%d Y=0..%d\tpotential outside boundary of the small grid\n", XL-1, YL-1);
                        abort();
                }/*abort if the potential is outside of the boundaries of the coarsest lattice*/
        }
        fscanf(file, "%*s %d\n", &NGROUND);
        if(NGROUND>0 && (strcmp(method, "red-black")==0 || strcmp(method, "gauss-seidel")==0)){
                printf("Grounded areas are not implemented for the %s method.\n", method);
                abort();
        }
        ground=malloc(NGROUND*sizeof(int *));
        for(n=0; n<NGROUND; n++){
                ground[n]=malloc(4*sizeof(int));
                fscanf(file, "%d %d %d %d\n", &ground[n][0],&ground[n][1],&ground[n][2],&ground[n][3]);
                if(ground[n][0]<0 || ground[n][1]>=XL || ground[n][2]<0 || ground[n][3]>=YL){
                        printf("X=0..%d Y=0..%d\tgrounded area outside or on the boundary\n", XL, YL);
                        abort();
                }/*grounded areas must be inside of the box, they may not overlap with the boundary*/
        }
        fscanf(file, "plot %d", &p);
        fclose(file);

        /*prints the infile in the terminal for checks.*/
        printf("%s\n", exerc);
        printf("%s\n", outdir);
        printf("%s\n", method);
        printf("X %d\n", X);
        printf("Y %d\n", Y);
        printf("F %d\n", F);
        printf("XL %d\n", XL);
        printf("YL %d\n", YL);
        printf("NP %.2e\n", NP);
        printf("npotential %d\n", NPHI);
        for(n=0; n<NPHI; n++){
                printf("phi[%d]=% .2e at %d %d %d %d\n", n, phi0[n].val, phi0[n].pos[0], phi0[n].pos[1], phi0[n].pos[2],phi0[n].pos[3]);
        }
        printf("nground %d\n", NGROUND);
        for(n=0; n<NGROUND; n++){
                printf("ground[%d] at %d %d %d %d\n", n, ground[n][0],ground[n][1],ground[n][2],ground[n][3]);
        }
}



int main(int arg, char *args[]) {
        int i, x, y, Xstep, Ystep, ix, iy, step;
        double *phi, *phibuf, *range, x1, x2, y1, y2, buf;
        char out[1000], title[1000], xlabel[100], ylabel[100], plot[1000], dif[1000];
        static int my_rank;
        FILE *file;
        plotinfo *pi;

        if(arg!=2){
                printf("incorrect number of input parameters.\n");
                abort();
        }

        read_infile(args[1]);/*get input data and test it*/

        /*initialization of the MPI-process, required to measure processor-time*/
        MPI_Init(&arg, &args);

        phi=malloc(XL*YL*sizeof(double));
        pi=malloc(3*sizeof(plotinfo));
        range=malloc(6*sizeof(double));
        for(i=0; i<6; i++)
                range[i]=0.;

        /*initialize potential*/
        for(x=0; x<XL; x++){
                for(y=0; y<YL; y++){
                        phi[y*XL+x]=0.;
                }
        }
        for(i=0; i<NPHI; i++){
                printf("nphi[%d]\n", i);
                for(x=phi0[i].pos[0]; x<=phi0[i].pos[1]; x++){
                        for(y=phi0[i].pos[2]; y<=phi0[i].pos[3]; y++){
                                phi[y*XL+x]=phi0[i].val;
                                printf("%d %d %d %f\n", i, x, y, phi[y*XL+x]);
                        }
                }
        }

        Xstep=XL;/*lattice extent of the current step*/
        Ystep=YL;
        step=1;
        while(Xstep<=X && Ystep<=Y){
                printf("solving lattice: %dx%d\n", Xstep, Ystep);
                sprintf(dif, "%s/dif_X%d_Y%d_%s_%s_m%d.dat", outdir, Xstep, Ystep,exerc, method, step);
                sprintf(out, "%s/%s_%s.log", outdir, exerc, method);
                /*compute the solution of the laplace equation using the functions defined in functions.c*/
                if(0==strcmp(method, "jacobi"))
                        jacobi(Xstep, Ystep, NP, phi, NPHI, phi0, NGROUND, ground, dif, out);
                else if(0==strcmp(method, "red-black"))
                        red_black(Xstep, Ystep, NP, phi, NPHI, phi0, dif, out);
                else if(0==strcmp(method, "gauss-seidel"))
                        gauss_seidel(Xstep, Ystep, NP, phi, NPHI, phi0, dif, out);

                /*define the plotranges*/
                if(Xstep>Ystep)
                        L=Xstep-1;
                else
                        L=Ystep-1;
                range[0]=0, range[1]=(double)(Xstep-1)/L,range[2]=0, range[3]=(double)(Ystep-1)/L;/*plotranges*/
                /*generate the output for the potential*/
                sprintf(out, "%s/phi_X%d_Y%d_%s_%s.dat", outdir, Xstep, Ystep,exerc, method);
                printf("%s\n", out);
                check_file(out, "w");
                file=fopen(out, "w");
                fprintf(file, "#Plotdata for:\n");
                fprintf(file, "#%s\n", exerc);
                fprintf(file, "#%s\n", outdir);
                fprintf(file, "#%s\n", method);
                fprintf(file, "#X %d\n", X);
                fprintf(file, "#Y %d\n", Y);
                fprintf(file, "#NP %.2e\n", NP);
                fprintf(file, "#npotential %d\n", NPHI);
                for(i=0; i<NPHI; i++){
                        fprintf(file, "#phi[%d]=% .2e at %d %d %d %d\n", i, phi0[i].val,
                                        phi0[i].pos[0], phi0[i].pos[1], phi0[i].pos[2],phi0[i].pos[3]);
                }
                fprintf(file, "#nground %d\n", NGROUND);
                for(i=0; i<NGROUND; i++){
                        fprintf(file, "#ground[%d] at %d %d %d %d\n", i,
                                        ground[i][0],ground[i][1],ground[i][2],ground[i][3]);
                }
                for(x=0; x<Xstep; x++){
                        for(y=0; y<Ystep; y++){
                                fprintf(file, "%4f %4f % .6e\n",(double)x/L, (double)y/L, phi[y * Xstep + x]);
                        }
                        fprintf(file, "\n");/*to create "set pm3d map"
                        each matrix line has to be separated by the next by an additional line.*/

                }
                fclose(file);
                if(p==1){
                        sprintf(pi[0].color, "red");
                        sprintf(pi[0].data, "phi_X%d_Y%d_%s_%s.dat", Xstep, Ystep,exerc, method);
                        sprintf(pi[0].label, "$\\Phi(NP=%.2e)$", NP);
                        pi[0].lw=1;
                        pi[0].pt=1;
                        pi[0].x=1;
                        pi[0].y=2;
                        sprintf(xlabel, "x[a]");
                        sprintf(ylabel, "y[a]");
                        sprintf(title, "$\\Phi(x,y)$ at NP=%.2e and $m=%d$", NP, step);
                        sprintf(plot, "potential_X%d_Y%d_%s_%s", Xstep, Ystep,exerc, method);
                        plot_data_color(outdir, plot, title, xlabel, ylabel, range, Xstep, Ystep, 1, pi);
                }
                /*generate the output for the electric field*/
                sprintf(out, "%s/E_X%d_Y%d_%s_%s.dat", outdir, Xstep, Ystep,exerc, method);
                printf("%s\n", out);
                check_file(out, "w");
                file=fopen(out, "w");
                for(x=0; x<Xstep-1; x++){
                        for(y=0; y<Ystep-1; y++){
                                /*discretized gradient of phi:*/
                                x1=(phi[y*Xstep+x]+phi[(y+1)*Xstep+x])/2;
                                x2=(phi[y*Xstep+x+1]+phi[(y+1)*Xstep+x+1])/2;
                                y1=(phi[y*Xstep+x]+phi[y*Xstep+x+1])/2;
                                y2=(phi[(y+1)*Xstep+x]+phi[(y+1)*Xstep+x+1])/2;
                                buf=sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1))/2;
                                fprintf(file, "%f %f %f %f\n", (double)(2*x+1)/2,(double)(2*y+1)/2,     -(x2-x1)/2/buf, -(y2-y1)/2/buf);
                        }
                        fprintf(file, "\n");
                }
                fclose(file);
                if(p==1){
                        sprintf(pi[0].color, "black");
                        sprintf(pi[0].data, "E_X%d_Y%d_%s_%s.dat", Xstep, Ystep,exerc, method);
                        sprintf(pi[0].label, "$\\vec{E}$");
                        pi[0].lw=3;
                        pi[0].x=1;
                        pi[0].y=2;
                        pi[0].u=3;
                        pi[0].v=4;
                        sprintf(xlabel, "x[a]");
                        sprintf(ylabel, "y[a]");
                        sprintf(title, "$\\vec{E}(x,y)=-\\nabla\\Phi$ at $m=%d$", step);
                        sprintf(plot, "E_X%d_Y%d_%s_%s", Xstep, Ystep,exerc, method);
                        range[0]=0., range[1]=(double)Xstep, range[2]=0., range[3]=(double)Ystep;
                        plot_data_vecfield(outdir, plot, title, xlabel, ylabel, range, Xstep, Ystep, 1, pi);
                }
                /*generate the output for the magnetic field*/
                sprintf(out, "%s/B_X%d_Y%d_%s_%s.dat", outdir, Xstep, Ystep,exerc, method);
                printf("%s\n", out);
                check_file(out, "w");
                file=fopen(out, "w");
                for(x=0; x<Xstep; x++){
                        for(y=0; y<Ystep; y++){
                                if((x>0 && x<Xstep-1) && (y>0 && y<Ystep-1)){
                                        /*discretized \nabla x \phi:*/
                                        x1=(phi[(y+1)*Xstep+x]-phi[(y-1)*Xstep+x])/2.;
                                        y1=-(phi[y*Xstep+x+1]-phi[y*Xstep+x-1])/2.;
                                        buf=sqrt((x1*x1+y1*y1));
                                        fprintf(file, "%f %f %f %f\n", (double)x,(double)y,     x1/buf, y1/buf);
                                }
                                else{
                                        fprintf(file, "%f %f %f %f\n", (double)x,(double)y,     0., 0.);
                                }
                        }
                        fprintf(file, "\n");
                }
                fclose(file);
                if(p==1){
                        sprintf(pi[0].color, "black");
                        sprintf(pi[0].data, "B_X%d_Y%d_%s_%s.dat", Xstep, Ystep,exerc, method);
                        sprintf(pi[0].label, "$\\vec{E}$");
                        pi[0].lw=3;
                        pi[0].x=1;
                        pi[0].y=2;
                        pi[0].u=3;
                        pi[0].v=4;
                        sprintf(xlabel, "x[a]");
                        sprintf(ylabel, "y[a]");
                        sprintf(title, "$\\vec{B}(x,y)=\\nabla\\times A$ at $m=%d$", step);
                        sprintf(plot, "B_X%d_Y%d_%s_%s", Xstep, Ystep,exerc, method);
                        range[0]=0., range[1]=(double)Xstep, range[2]=0., range[3]=(double)Ystep;
                        plot_data_vecfield(outdir, plot, title, xlabel, ylabel, range, Xstep, Ystep, 1, pi);
                        sprintf(out, "rm %s/*.dat", outdir);
                        printf("%s\n", out);
                        system(out);
                }
/*allocate memory for a temporary array and store the current potential*/
                phibuf=malloc(Xstep*Ystep*sizeof(double));
                for(x=0; x<Xstep; x++){
                        for(y=0; y<Ystep; y++){
                                phibuf[y*Xstep+x]=phi[y*Xstep+x];
                        }
                }
                free(phi);/*free the memory used by the current step's potential*/
                Xstep*=F;/*increase the lattice-extent to the next level*/
                Ystep*=F;
                /*allocate memory for the next step's potential and store the old
                 * potential, such that one lattice site of the previous step is stored in
                 * F*F lattice sites at the appropriate coordinates.
                 * Example: F=2: x-> xx
                 *                                       xx*/

                phi=malloc(Xstep*Ystep*sizeof(double));
                for(x=0, ix=0; x<Xstep; x++){
                        for(y=0, iy=0; y<Ystep; y++){
                                phi[y*Xstep+x]=phibuf[iy*(int)Xstep/F+ix];
                                if((y+1)%F==0)
                                        iy++;
                        }
                        if((x+1)%F==0)
                                ix++;
                }
                free(phibuf);/*free memory used by the temporary potential*/
                /*
                 * as the lattice size is increased the predefined potentials and grounded
                 * areas must grow by the same factor:
                 * */

                for(i=0; i<NPHI; i++){
                        for(x=0; x<4; x++){
                                phi0[i].pos[x]*=F;
                        }
                }
                for(i=0; i<NGROUND; i++){
                        for(x=0; x<4; x++){
                                ground[i][x]*=F;
                        }
                }
                step++;
        }/*end loop over lattice-volumes*/

        return 0;
}