/*
 * method-genetic-algorithm.c
 *
 *  Created on: Dec 6, 2010
 *      Author: blackpanther
 */
#include <stdlib.h>
#include <string.h>

#include <math.h>

#include "configuration.h"

#include "utilities.h"
#include "logger.h"

#include "problema.h"
#include "method.h"

#define LOG_LEVEL INFO

typedef struct population {
	size_t   popSize;
	size_t   candidateSize;
	double **pop;
} Population;

static
double*    fetchBestCandidate(Population *population, Problema *pb);

static
double*    randomCandidate(size_t candidateSize, double min, double max);

static
void       initializePopulation(Population *population, size_t populationSize,size_t candidateSize, double min, double max);

static
char*      printPopulation(Population population);

static
size_t     computeNewPopulationSize(size_t size);

static
void       nextPopulation(Population *population, Problema *pb);

static
void       evaluate(Population *population);

static
void       cross(double* candidate1, double *candidate2, size_t dimension,
				double      min,
				double      max);

static
void       mutate(double* candidate, size_t dimension,
				double      min,
				double      max);

static
void       computeNaturalEvents(
				Population *population,
				double      crossProbability,
				double      mutationProbability,
				double      min,
				double      max);

static
void       destroyPopulation(Population population);

void resolveByGeneticAlgorithm(
    Problema*    pb,
    Method*      m,
	double       init_vector[],
    MethodParameters args)
{
	Population population;
	size_t     populationSize;
	double     crossProbability;
	double     mutationProbability;
	double     bounds_min, bounds_max;

	int patience;

	boolean toContinue    = FALSE;
	double  error         = -1;
	int     counter       = 0;

	activateLogger();
	setLoggerLevel(LOG_LEVEL);

	logMessage(INFO,"enter method","Genetic Algorithm");

	patience            = *((int *)(args.parameters[INDEX_PATIENCE]));
	populationSize      = *((int *)(args.parameters[INDEX_POPULATION_SIZE]));
	crossProbability    = *((double *)(args.parameters[INDEX_CROSS_PROBA]));
	mutationProbability = *((double *)(args.parameters[INDEX_MUTATION_PROBA]));
	bounds_min          = *((double *)(args.parameters[INDEX_BOUNDS_MIN]));
	bounds_max          = *((double *)(args.parameters[INDEX_BOUNDS_MAX]));

	logMessage(DEBUG1,"parameter : patience"     ,integerToString(patience));
	logMessage(DEBUG1,"parameter : population size",integerToString(populationSize));
	logMessage(DEBUG1,"parameter : crossProbability",doubleToString(crossProbability));
	logMessage(DEBUG1,"parameter : mutationProbability",doubleToString(mutationProbability));
	logMessage(DEBUG1,"parameter : bounds_min",doubleToString(bounds_min));
	logMessage(DEBUG1,"parameter : bounds_max",doubleToString(bounds_max));

	initializePopulation(&population,populationSize, pb->_dimension,bounds_min,bounds_max);

	logMessage(DEBUG2,"Generate initial population",printPopulation(population));

	logMessage(DEBUG2,"loop","start iterate");
	do
	{
		counter++;
        logMessage(DEBUG1,"counter", integerToString(counter));
        if( population.popSize < 10)
        	logMessage(DEBUG1,"Current population",printPopulation(population));

    	evaluate(&population);

    	nextPopulation(&population,pb);
    	if( population.popSize < 10)
    	    logMessage(DEBUG1,"new population",printPopulation(population));

    	computeNaturalEvents(&population,crossProbability,mutationProbability,bounds_min,bounds_max);
    	if( population.popSize < 10)
    	    logMessage(DEBUG1,"after natural event",printPopulation(population));

        toContinue = TRUE;

        toContinue = toContinue && (counter < patience);
        toContinue = toContinue && (population.popSize > 1);

        logMessage(DEBUG1, "stop condition 1 : counter < patience", printBoolean(counter    < patience));
        logMessage(DEBUG1, "stop condition 2 : populationSize > 0", printBoolean(population.popSize > 1));

        logMessage(DEBUG1, "should we continue", printBoolean(toContinue));
	}while(toContinue);
	logMessage(DEBUG1,"loop","end");

	double *optimum;
	if( counter < patience )
		optimum = population.pop[0];
	else
		optimum = fetchBestCandidate(&population,pb);

	saveAlgorithmData(
			m,
			pb,
            args,
			counter,
			optimum,
			pb->compute(pb,optimum,population.candidateSize)[0],
			error);
	logMessage(DEBUG1,"algorithm data","saved");

	if( !(counter < patience) )
		free(optimum);

	destroyPopulation(population);
	logMessage(DEBUG2,"clean","freeing remaining population");
}

static
double* fetchBestCandidate(Population *population, Problema *pb)
{
	int     i;
	int     bestCandidatePosition = -1;
	double  bestValue;
	double  cursorValue;
	double *bestCandidate;

	for(i=0;i< population->popSize; i++)
	{
		if( population->pop[i] )
		{
			bestCandidatePosition = i;
			break;
		}
	}

	if( bestCandidate < 0 )
	{
		logMessage(WARNING,"empty population","no more candidate to take");
		return NULL;
	}

	logMessage(DEBUG3,"initial best position",integerToString(bestCandidatePosition));

	bestValue = pb->compute(pb,population->pop[bestCandidatePosition],population->candidateSize)[0];

	logMessage(DEBUG3,"initial best vector",printVector(population->pop[bestCandidatePosition],population->candidateSize));
	logMessage(DEBUG3,"initial best vector value",doubleToString(bestValue));

	logMessage(DEBUG3,"iterate old population","start");
	for(i=0;i< population->popSize; i++)
	{
		if( population->pop[i] )
		{
			cursorValue = pb->compute(pb,population->pop[i],population->candidateSize)[0];

			logMessage(DEBUG3,"iteration",printVector(population->pop[i],population->candidateSize));
			logMessage(DEBUG3,"iteration value",doubleToString(cursorValue));

			if( cursorValue < bestValue )
			{
				logMessage(DEBUG3,"iteration selection","better value");
				bestCandidatePosition = i;
				bestValue             = cursorValue;
			}
		}
	}
	logMessage(DEBUG3,"iterate old population","end");

	bestCandidate = (double *)malloc(population->candidateSize*sizeof(double));
	memcpy(bestCandidate,population->pop[bestCandidatePosition],population->candidateSize*sizeof(double));

	free(population->pop[bestCandidatePosition]);
	population->pop[bestCandidatePosition] = NULL;

	return bestCandidate;
}

static
void       initializePopulation(Population *population, size_t populationSize, size_t candidateSize, double min, double max)
{
	int i;
	population->candidateSize = candidateSize;
	population->popSize       = populationSize;
	population->pop = (double **)malloc(population->popSize*sizeof(double *));
	for(i=0;i<population->popSize;i++)
			population->pop[i] = randomCandidate(population->candidateSize, min, max);
}

static
double*    randomCandidate(size_t candidateSize, double min, double max)
{
	int i;
	double* candidate = NULL;
	candidate = (double *)malloc(candidateSize*sizeof(double));
	for(i=0;i<candidateSize;i++)
		candidate[i] = homeRandom(min,max);

	logMessage(DEBUG3,"candidate generated",printVector(candidate,candidateSize));

	return candidate;
}

static
char*      printPopulation(Population population)
{
	static char output[10*MAX_STRING_INPUT];
	int i;

	memset(output,'\0',10*MAX_STRING_INPUT*sizeof(char));

	strcat(output,"\nPopulation\n");
	strcat(output,"{\n");
	for(i=0;i<population.popSize;i++)
		if(population.pop[i])
			sprintf(output,"%s\t- %s \n",output,printVector(population.pop[i],population.candidateSize));
	strcat(output,"}\n");

	return output;
}

static
size_t     computeNewPopulationSize(size_t size) {
	return 0.9 * size;
}

static
void       nextPopulation(Population *population, Problema *pb){
	Population nextPopulation = {0};
	int        bestCandidate;
	int        counter = 0;

	logMessage(DEBUG3,"enter method","next population");

	nextPopulation.popSize       = computeNewPopulationSize(population->popSize);
	nextPopulation.candidateSize = population->candidateSize;
	nextPopulation.pop           = (double **)malloc(nextPopulation.popSize*sizeof(double *));
	memset(nextPopulation.pop,0,nextPopulation.popSize*sizeof(double *));

	logMessage(DEBUG2,"next population size",integerToString(nextPopulation.popSize));

	logMessage(DEBUG2,"filling loop","start");
	while(counter < nextPopulation.popSize)
	{
		bestCandidate = -1;

		logMessage(DEBUG2,"old population",printPopulation(*population));

		double *bestCandidate = fetchBestCandidate(population, pb);

		if( !bestCandidate )
			break;

		logMessage(DEBUG2,"best candidate",printVector(bestCandidate,population->candidateSize));
		logMessage(DEBUG2,"best candidate value",printVector(pb->compute(pb,bestCandidate,population->candidateSize),1));
		nextPopulation.pop[counter] = (double *)malloc(nextPopulation.candidateSize);
		memcpy(nextPopulation.pop[counter],bestCandidate,population->candidateSize*sizeof(double));

		free(bestCandidate);

		counter++;
	}
	logMessage(DEBUG2,"filling loop","end");

	destroyPopulation(*population);

	memcpy(population,&nextPopulation,sizeof(Population));

	logMessage(DEBUG3,"exit method","next population");
}

static
void       evaluate(Population *population)
{
	logMessage(DEBUG3,"enter method","evaluate");

	logMessage(DEBUG3,"exit method","evaluate");
}

static
void       cross(double* candidate1, double *candidate2, size_t dimension, double min, double max)
{
	int    crossPosition = dimension * homeRandom(0,1);
	int    i;
	double delta;
	double swap;

	logMessage(DEBUG3,"cross position",integerToString(crossPosition));

	double localMax;
	double localMin;

	if( candidate1[crossPosition] > candidate2[crossPosition] )
	{
		localMax = candidate1[crossPosition];
		localMin = candidate2[crossPosition];
	}
	else
	{
		localMax = candidate2[crossPosition];
		localMin = candidate1[crossPosition];
	}

	double deltaMin = fabs(min) - fabs(localMin);
	double deltaMax = fabs(max) - fabs(localMax);

	logMessage(DEBUG3,"deltaMin",doubleToString(min + deltaMin));
	logMessage(DEBUG3,"deltaMax",doubleToString(max + deltaMax));

	delta = homeRandom(min + deltaMin,max - deltaMax);
	logMessage(DEBUG3,"delta",doubleToString(delta));


	candidate1[crossPosition] += delta;
	candidate2[crossPosition] -= delta;

	for(i=crossPosition+1;i<dimension;i++)
	{
		swap = candidate1[i];
		candidate1[i] = candidate2[i];
		candidate2[i] = swap;
	}
}

static
void       mutate(double* candidate, size_t dimension, double min, double max)
{
	int    mutatePosition = dimension * homeRandom(0,1);

	candidate[mutatePosition] = homeRandom(min,max);
}

static
void       computeNaturalEvents(
				Population *population,
				double      crossProbability,
				double      mutationProbability,
				double      min,
				double      max)
{
	int i;
	double rand;
	char buffer1[100];
	char buffer2[100];
	logMessage(DEBUG3,"enter method","computeNaturalEvents");

	for(i=0;i<population->popSize;i++)
	{
		if( population->pop[i] )
		{
			rand = homeRandom(0,1);
			logMessage(DEBUG2,"rand value",doubleToString(rand));
			logMessage(DEBUG2,"recall-cross probability",doubleToString(crossProbability));
			logMessage(DEBUG2,"recall-mutation probability",doubleToString(mutationProbability));

			if( population->popSize > 1
					&& rand <= crossProbability )
			{
				double *candidate1 = NULL;
				double *candidate2 = NULL;

				if( i == 0 ) //Beginning of the population
				{
					candidate1 = population->pop[i];
					candidate2 = population->pop[i+1];
				}
				else
				{
					candidate1 = population->pop[i];
					candidate2 = population->pop[i-1];
				}

				strcpy(buffer1,printVector(candidate1,population->candidateSize));
				strcpy(buffer2,printVector(candidate2,population->candidateSize));

				sprintf(buffer,"%s and %s",
						buffer1,
						buffer2);
				logMessage(DEBUG2,"crossover happened",buffer);

				cross(candidate1,candidate2,population->candidateSize,min,max);

				strcpy(buffer1,printVector(candidate1,population->candidateSize));
				strcpy(buffer2,printVector(candidate2,population->candidateSize));

				sprintf(buffer,"%s and %s",
						buffer1,
						buffer2);

				logMessage(DEBUG2,"crossover result  ",buffer);

			}

			if( rand <= mutationProbability )
			{
				logMessage(DEBUG2,"mutation happened",printVector(population->pop[i],population->candidateSize));

				mutate(population->pop[i],population->candidateSize,min,max);

				logMessage(DEBUG2,"mutation result  ",printVector(population->pop[i],population->candidateSize));
			}
		}
	}
	logMessage(DEBUG3,"exit method","computeNaturalEvents");
}

static
void       destroyPopulation(Population population)
{
	int i;
	for(i=0;i<population.popSize;i++)
		free(population.pop[i]);
	free(population.pop);
}
