/***************************************************************************
 *   Copyright (C) 2007 by Yves Fomekong Nanfack, F.219,+31 20 525 7530,   *
 *   yvesf@science.uva.nl   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <cstdlib>
#include <math.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>


extern "C"{
#include <error.h>                                 /* error handling funcs */
#include "global.h"
#include "mathLib.h"  
#include "rvgs.h"
#include "rngs.h"
#ifdef USING_MPI
#include <mpi.h>
#include "mpi.h"
#endif
}


#include "fly_ES.h"
#include "fly_DS.h"

#ifdef USING_MPI
#include <mpi.h>
#include "mpi.h"
#endif


using namespace std;

void backUpBestIL(int ter);

es **esRun; 

time_t start;
#ifdef USING_MPI
int myIsland;
int numIslands;
#endif


void backUpBestIL(int iter,double bestF){

#ifdef USING_MPI

  cout << iter << ":" << myIsland << " - entering backup function\n";

  if (myIsland == 0)

#endif
    {  
      landscapeFile=fopen(ga_state->tune.outNameLandscapeFirst,"a+"); 
      fprintf(landscapeFile,"%d  \t",iter);        
    }
     
#ifdef USING_MPI

  //double myBestF;
  //double bestF;

  /* each processor finds its best energy */
  //myBestF = esRun[0]->Stat().getBestindvdl().getF();
  
  /* find the best overall energy */
  //MPI_Reduce(&myBestF,&bestF,1,MPI_DOUBLE,MPI_MIN,0,MPI_COMM_WORLD);

  /* record it on node 0 */

  cout << iter << ":" << myIsland << " - writing best individual\n";

  if (myIsland == 0){
    
    time_t current_time;

    current_time=MPI_Wtime();
    
    fprintf(landscapeFile,"%*.*f   \t ",ndigits+4, ndigits, bestF);  
    fprintf(landscapeFile,"%ld  \t ",current_time-start); 
    fprintf(landscapeFile,"\n");      
    fclose(landscapeFile);  
  }
  
  int iMin=0; // we will back up the best indiviudal in each population locally
  int Min= esRun[0]->Stat().getBestindvdl().getF(); // why is this an int? *sigh*

#else

  time_t current_time;
  current_time=time(NULL);

  int Min=FORBIDDEN_MOVE;
  int iMin=0;
  for (int i=0; i<ga_state->tune.nbPopulation;i++){
    fprintf(landscapeFile,"%*.*f   \t ",ndigits+4, ndigits, esRun[i]->Stat().getBestindvdl().getF());      
    if ( esRun[i]->Stat().getBestindvdl().getF()<Min){
      iMin=i;
      Min= esRun[i]->Stat().getBestindvdl().getF();
    }
  }    

  fprintf(landscapeFile,"%ld  \t ",current_time-start);
  fprintf(landscapeFile,"\n");      
  fclose(landscapeFile);    

#endif

  /* back up the best individual (or individuals, if parallel) */

#ifdef USING_MPI

  cout << iter << ":" << myIsland << " - backup up on each node\n";

  char *islandFile      = (char *)calloc(MAX_RECORD, sizeof(char));
  sprintf(islandFile,   "%s_%d", ga_state->tune.outNamePop, myIsland);

  FILE *tmpFile=fopen(islandFile,"w");
  
#else
  
  FILE *tmpFile=fopen(ga_state->tune.outNamePop,"w");

#endif

  char *temp      = (char *)calloc(MAX_RECORD, sizeof(char));	        
  entityToParam(esRun[iMin]->Stat().getBestindvdl().getParameters());
  fprintf( tmpFile,"\n-----best Indiv----------------------------------------------------------------------------------------------------------------------\n");     
  sprintf(temp,"best indiv ===> score %*.*f     from populationSize  %d :\n\n",ndigits+4, ndigits,Min,iMin);
  PrintParameters( tmpFile, GetParameters(), temp,ndigits);    
  fprintf( tmpFile,"\n----------------------------------------------------------------------------------------------------------------------------------\n\n");            
  fclose(tmpFile);
  
#ifdef USING_MPI 

  cout << iter << ":" << myIsland << " - leaving backup function\n";

#endif

}

 

void migrate(int iter){
  
#ifdef USING_MPI
  cout << iter << ":" << myIsland << " - Start of Migration " << migCount << "\n";
  int nbPopulation = numIslands;
  double *param;
  param = new double[esRun[0]->getEsParam()->nbParameters];
  double *senddata;
  senddata = new double[esRun[0]->getEsParam()->nbParameters+2];
  double *recieve;
  recieve = new double[esRun[0]->getEsParam()->nbParameters+2];

#else

  int nbPopulation = ga_state->tune.nbPopulation;

#endif

  int *card;    // array of cards;
  int giver;
  int reciever;
  int idPop;
  int nbIndMigrate=1;
  double fnew, phinew;
  card=new int[nbPopulation];
  //    backUpBestIL(-1);
  
#ifdef USING_MPI
     
  if (myIsland == 0){
    for (idPop=0; idPop<nbPopulation;idPop++) {
      card[idPop] =idPop;  // fill the array in order
    }
    
    //printf ("\n migrate %d:: \n",migCount);
  }
  
  esRun[0]->migBefore();
  
#else
  
  for (idPop=0; idPop<nbPopulation; idPop++) {
    card[idPop] =idPop;  // fill the array in order
    esRun[idPop]->migBefore();
  }
  
  printf ("migrate %d:: ",migCount); 

#endif
  
  //loop over the number of indiviudals that migrate   
  for (int imig=0;imig<nbIndMigrate;imig++){  
    
#ifdef USING_MPI
    
    if (myIsland == 0)
      
#endif
      
      { 
	random_shuffle(card,card+ga_state->tune.nbPopulation);      
	//re-shuffle for each migration
	printf ("\t\t bestindvdl %d::\t\t ",imig);
      } 
    
#ifdef USING_MPI

    cout << iter << ":" << myIsland << " - broadcasting cards\n";

    int bcastcode;

    bcastcode = MPI_Bcast(card,nbPopulation,MPI_INT,0,MPI_COMM_WORLD);

    cout << iter << ":" << myIsland << " - broadcast done with status " << bcastcode << "\n";

#endif

    for (idPop=0;idPop<nbPopulation;idPop++){
      
      giver=card[idPop];
      reciever=card[(idPop+1)%ga_state->tune.nbPopulation];
      
#ifdef USING_MPI
      if (myIsland == giver)
	{  
	  
	  double *param;
	  double *senddata;
	  int sendercode;

	  senddata = new double[esRun[0]->getEsParam()->nbParameters+2];
	  
	  senddata[0] = esRun[0]->Stat().getBestindvdl().getF();
	  senddata[1] = esRun[0]->Stat().getBestindvdl().getPhi();
	  param = esRun[0]->Stat().getBestindvdl().getParameters();
	  
	  //cout << "\n " << myIsland << " is giving to " << reciever << "\n Params: ";
	  for (int i = 0; i < esRun[0]->getEsParam()->nbParameters; i++){
	    senddata[i+2] = param[i];
	    //cout << i << ":" << param[i] << " ";
	  }

	  //cout << "\n" ;

	  cout << iter << ":" << myIsland << " - sending to " << reciever << "\n";

	  sendercode = MPI_Send(senddata,esRun[0]->getEsParam()->nbParameters+2,MPI_DOUBLE,reciever,giver,MPI_COMM_WORLD);
	
	  cout << iter << ":" << myIsland << " - send to " << reciever << " with status " << sendercode << "\n";
  
	  //cout << "Status:" << sendercode << "\n";

	  delete(senddata);
	}
      
      if (myIsland == reciever){
	
	double *param;
	double *recievedata;
	int recercode;

	recievedata = new double[esRun[0]->getEsParam()->nbParameters+2];
	param = new double[esRun[0]->getEsParam()->nbParameters  ];
	
	cout << iter << ":" << myIsland << " - recieving from " << giver << "\n";

	recercode = MPI_Recv(recievedata,esRun[0]->getEsParam()->nbParameters+2,MPI_DOUBLE,giver,giver,MPI_COMM_WORLD,MPI_STATUS_IGNORE);

	cout << iter << ":" << myIsland << " - recieved from " << giver << " with status " << recercode << "\n";

	//cout << "\n " << myIsland << " is taking from " << giver << "\n Params: ";
	  
	for (int i = 0; i < esRun[0]->getEsParam()->nbParameters; i++){
	  param[i] = recievedata[i+2];
	  //cout << i << ":" << param[i] << " ";
	}

	//cout << "\n Status:" << recercode << "\n";

	esRun[0]->getEsPop()->replaceInd(esGP.mu-imig-1,param, recievedata[0],recievedata[1],esRun[0]->getEsParam());
	
// 	param = esRun[0]->getEsPop()->getIndividuals(esGP.mu-imig-1).getParameters();

// 	cout << "\n" << myIsland << " now has: ";
// 	for (int i = 0; i < esRun[0]->getEsParam()->nbParameters; i++){
// 	  cout << i << ":" << param[i] << " ";
// 	}
	
// 	cout << "\n";

	//printf ("\n %d (%f )  -->%d:\t \n ",giver,recievedata[0],reciever);
	
	delete(param);
	delete(recievedata);
      }
      
#else
      
      fnew=esRun[giver]->Stat().getBestindvdl().getF();
      phinew=esRun[giver]->Stat().getBestindvdl().getPhi();
      esRun[reciever]->getEsPop()->replaceInd(esGP.mu-imig-1,esRun[giver]->Stat().getBestindvdl().getParameters() , fnew, phinew,esRun[giver]->getEsParam());
      
      printf ("%d (%f )  -->%d:\t  ",giver,fnew,reciever);
      
#endif
      
      //	cout << giver << "( " <<fnew << ")-->"<< reciever << endl;
    }  
    
#ifdef USING_MPI
    //if (myIsland == 0)
#endif
      //printf ("\n\n");
  }
  
#ifdef USING_MPI
  
  esRun[0]->migAfter();
  
#else
    
  for (idPop=0;idPop<ga_state->tune.nbPopulation;idPop++){
    esRun[idPop]->migAfter();
  }
  
#endif
  
  //backUpBestIL(-1);
  
  delete(card);
  migCount++;

#ifdef USING_MPI
  cout << iter << ":" << myIsland << " - finished migration\n";
#endif

} 

 void runESIL(es **esRun, int nbiter,int nbPopulation){
   
   cout << "done\n";

    int critcount = 0; // N. iterations with F below threshold
    //   double * bestFs = new double [ga_state->tune.nbPopulation]; // vector of best Fs from all populations
    double bestF; // best F from all populations
    double thisBestF;

    cout << "beginning iterations.\n";

    for (int iter=0; iter< nbiter; iter++){
     
      //if (myIsland == 0)
#ifdef USING_MPI
      cout << iter << ":" << myIsland << " - Start of Loop\n";
#endif

      for (int p=0;p< nbPopulation;p++){
       
	esRun[p]->newGeneration(esGP.pf);
       
      }

     /* every checkInterval, check for termination */
      if ((iter)%ga_state->tune.check_interval==0 && iter >0){
       
       // give bestF an arbitrary initial value
       //bestFs[0] = esRun[0]->Stat().getBestindvdl().getF();
	bestF = esRun[0]->Stat().getBestindvdl().getF();
	
#ifdef USING_MPI
       
        double overallBestF;
   
	int bestercode;

        /* find the overall lowest F */
	cout << iter << ":" << myIsland << " - Finding best F\n";
        
	bestercode = MPI_Allreduce(&bestF,&overallBestF,1,MPI_DOUBLE,MPI_MIN,MPI_COMM_WORLD);
	
	cout << iter << ":" << myIsland << " - Found best F with status " << bestercode << "\n";
        
	bestF = overallBestF;
    
#else
        // find the best F values for each population, and track overall best
        for (int i=1; i<nbPopulation;i++){
 	 thisBestF = esRun[i]->Stat().getBestindvdl().getF();
 	 if (thisBestF < bestF){
 	   bestF = thisBestF;
 	 }
        }
 
#endif
       
       // if the bestF is below the threshold...
       if (bestF < ga_state->tune.energy_threshold){
	 critcount++; // increment the counter
	 // otherwise...
       } else { 
	 critcount = 0; // reset it
       }
       
       // if we have stayed below the threshold for long enough, break the loop
       if (critcount >= ga_state->tune.count_threshold){
	 cout << "Exit criteria met. F = ";
	 cout << bestF << endl;
	 //backUpBestIL(iter, bestF);
	 break;
       }

       /* back up and update the log */
#ifdef USING_MPI
       cout << iter << ":" << myIsland << " - starting backup" << "\n";
#endif

       backUpBestIL(iter,bestF);
      
#ifdef USING_MPI
       cout << iter << ":" << myIsland << " - finished backup" << "\n";
#endif

      }


     /* every mixInterval, migrate */
      
      if ((iter)%ga_state->tune.mix_interval==0 && iter >0){
	     migrate(iter);
      }

    }
 }



int main_function(int argc, char *argv[])
{
 
  cout << "Initializing...";
  
  initES(1);
  initGlobal();
  
  cout << "Global done ...";
  
  double ftemp=getFlyScore();  
  double phitemp=0;
  
  /* if we are parralel, we only want one population per computer ... */
#ifdef USING_MPI

  //  cout << "\n\n Seed. Pre:" << ga_state->tune.seed;

  // give each processer a different seed
  ga_state->tune.seed = ((ga_state->tune.seed*(myIsland+100))/100);

  //cout << "Post:" << ga_state->tune.seed;

  int nbPopulation = 1;

  /* ... but if we aren't, we want them all on one computer */
#else

  int nbPopulation = ga_state->tune.nbPopulation;

#endif

  /* allocate memory, create and initialize ESs, and restore if we are restarting */
  esRun =new es*[nbPopulation]  ;
  for (int i=0;i< nbPopulation;i++){
    esRun[i]=new es();
    esRun[i]->initialize(&transform, &ObjectiveES, esGP.esType, ga_state->tune.seed,nbConstraint, nbParameters, ub, lb, esGP.mu, esGP.lambda,esGP.nbGenerations, esGP.Gamma, esGP.Alpha, esGP.varphi, esGP.retry);
    if (ga_state->tune.restart==1){
      esRun[i]->getEsPop()->replaceInd(0,getParameters(nbParameters),ftemp, phitemp, esRun[i]->getEsParam());
    }
    
#ifdef USING_MPI

    if (myIsland == 0)
      
#endif
      esRun[i]->printConfig(stdout);  

    }

  cout << "done\n";

  /* start the stat object for one ES */

#ifdef USING_MPI

  if (myIsland == 0)

#endif
    
    esRun[0]->Stat().stop(); 

  
  /* run the ES */

  cout << "starting ES...\n";

  runESIL(esRun,esGP.nbGenerations,nbPopulation);

  /* print stats object */
#ifdef USING_MPI
  
  if (myIsland == 0)
 
#endif
 
    
    esRun[0]->Stat().print(esRun[0]->getEsParam(),stdout);  

  
  cout << "Starting clock...";

  CChrono chrono;
  chrono.Start();    

  cout << "Done\n" ;

#ifdef USING_MPI

  /* find the best island */

  struct {
    double F;
    int island;
  } myF, bestF;

  myF.F = esRun[0]->Stat().getBestindvdl().getF();
  myF.island = myIsland;

  cout << "\n My F:" << myF.F << " My Island:" << myF.island << "\n";

  MPI_Allreduce(&myF,&bestF,1,MPI_DOUBLE_INT,MPI_MINLOC,MPI_COMM_WORLD);

  cout << "\n Best F:" << bestF.F << " Best Island:" << bestF.island << "\n";

  /* have the best island output parameters and run DS */
  if (myIsland == bestF.island){

    cout << "\n Me! I'm" << myIsland << "\n";

#endif
    
    char *temp    = new char[MAX_RECORD];
    for (int idPop=0; idPop < nbPopulation; idPop++){
      
      cout << "Printing stuff";
      entityToParam(esRun[idPop]->Stat().getBestindvdl().getParameters());
      sprintf(temp,"ga_param_outputs %d",idPop);      
      WriteParameters(ga_state->tune.outname, GetParameters(), temp, 6);    
      sprintf(temp,"ga_score %d",idPop);       
      WriteScore(ga_state->tune.outname,   esRun[idPop]->Stat().getBestindvdl().getF() , temp, 6);              
      
      initDS(esRun[idPop]->Stat().getBestindvdl().getParameters(),esRun[idPop]->Stat().getBestindvdl().getF());
      
      cout << "Done\n Running DS ...";
      runDS()	;
      cout << "Done\n";
      
      for ( int i=0;i<ga_state->tune.len_chrom;i++){
	setParameters(i,dsGP.aux[i+2]);  
      }
      
      cout << "Printing more stuff...";

      sprintf(temp,"ga_DSS_param_outputs %d",idPop);      
      WriteParameters(ga_state->tune.outname, GetParameters(), temp, 6);    
      sprintf(temp,"ga_DSS_score %d",idPop);       
      WriteScore(ga_state->tune.outname,   dsGP.aux[1] , temp, 6);           
      killDS();

      cout << "Done\n";
      
      delete [] temp;
    }
    
#ifdef USING_MPI

  }
  
#endif

  chrono.Stop();
  fprintf(stdout, "\nAll DS time %ld (ms) \n: ", chrono.Read());
  killES();  
  killGlobal();
  cout << "exciting  Test ES!" << endl;
  return EXIT_SUCCESS;
}

int main(int argc, char **argv){

  cout << "starting  the program" << endl;

#ifdef USING_MPI
   
  cout << "Initializing MPI ... ";

  MPI_Init(&argc,&argv);
  
  cout << "done\n Taking Rank Data...";
  
  MPI_Comm_size(MPI_COMM_WORLD,&numIslands);
  MPI_Comm_rank(MPI_COMM_WORLD,&myIsland);

  cout << "MPI done...\n";
  cout << numIslands << " " << myIsland;
  
  start = MPI_Wtime();

#else

  start = time(NULL);

#endif

  ga_state =(NucStateType *)malloc(sizeof(NucStateType));      
  if ((ga_state=fly_main(argc,argv))!=NULL){
    unsigned _argc_ga;
  char **_argv_ga;
  if (ga_state->tune.configName!=NULL){
    _argc_ga=(unsigned)2;	
    _argv_ga=(char **)calloc(_argc_ga,sizeof(char *));
    _argv_ga[0]= (char *)calloc(MAX_RECORD, sizeof(char)); 
    _argv_ga[0]=argv[0];
    _argv_ga[1]= (char *)calloc(MAX_RECORD, sizeof(char)); 
    _argv_ga[1]= ga_state->tune.configName;
  }
  else{
    _argc_ga=(unsigned)1;
    _argv_ga=(char **)calloc(_argc_ga,sizeof(char *));
    _argv_ga[0]= (char *)calloc(MAX_RECORD, sizeof(char)); 
    _argv_ga[0]=argv[0];
  }
  
  cout << "starting the GA " << endl;
  main_function(_argc_ga,_argv_ga);

#ifdef USING_MPI
  MPI_Finalize();
#endif

}  
return 0;
}


