/*
 * method-recuitsimule.c
 *
 *  Created on: Nov 15, 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

#define LOW_ACCEPTANCE  0.1
#define HIGH_ACCEPTANCE 0.9

#define TEMPERATURE_DECREASE_CONSTANT 0.8

static
double* generateNeighbor(double *iteration, size_t dimension, double neighborStep);

static
boolean MetropolisCriteria(double delta,double temperature);

static
boolean levelCondition(int moveNumber, int levelLimit, double acceptanceRate);

// HELP Logarithm decrease
static
void    decreaseTemperature(double *temperature, int levelNumber);

static
boolean acceptanceTest(double acceptanceRate);

void resolveByRecuitSimule(
    Problema*    pb,
    Method*      m,
	double       init_vector[],
    MethodParameters args)
{
	size_t  dimension = pb->_dimension;
	double  iteration[dimension];
	double  iterationValue;
	double *neighbor;
	double  neighborValue;

	double minima[dimension];
	double minimaValue;

	double delta;

	double temperature;
	int patience;
	double neighborStep;
	int levelLimit;

	int    movementNumber = 0;
	double acceptanceRate = 0;

	boolean toContinue    = FALSE;
	boolean criteria;
	int     counter       = 0;
	int     subcounter    = 0;
	double  error         = -1;
	char    buffer[100]   = {0};

	activateLogger();
	setLoggerLevel(LOG_LEVEL);

	logMessage(INFO,"enter method","Simulated annealing");

	patience     = *((int *)getParameterProperty(args,PATIENCE_LOADED));
	temperature  = *((double *)getParameterProperty(args,TEMPERATURE_LOADED));
	neighborStep = *((double *)getParameterProperty(args,NEIGHBOR_STEP_LOADED));
	levelLimit   = *((int *)getParameterProperty(args,LEVEL_LIMIT_LOADED));

	logMessage(DEBUG1,"parameter : vector"       ,printVector(init_vector,dimension));
	logMessage(DEBUG1,"parameter : patience"     ,integerToString(patience));
	logMessage(DEBUG1,"parameter : temperature"  ,doubleToString(temperature));
	logMessage(DEBUG1,"parameter : neighbor step",doubleToString(neighborStep));
	logMessage(DEBUG1,"parameter : level limit",integerToString(levelLimit));

	memcpy(iteration,init_vector,dimension * sizeof(double));
	iterationValue = pb->compute(pb,iteration,dimension)[0];

	memcpy(minima,iteration,dimension*sizeof(double));
	minimaValue = iterationValue;

	sprintf(buffer,"Minima : %s ; Minima value : %lf",printVector(minima,dimension),minimaValue);
	logMessage(DEBUG2,"intialize minimas",buffer);

	logMessage(DEBUG2,"loop","start iterate");
	do
	{
		movementNumber = 0;
		subcounter     = 0;
		acceptanceRate = 0;
		counter++;
        logMessage(DEBUG1, "counter", integerToString(counter));
        logMessage(DEBUG1, "temperature",doubleToString(temperature));

        do
        {
        	subcounter++;
            logMessage(DEBUG1, "subcounter"      ,integerToString(subcounter));
			logMessage(DEBUG1, "iteration"       ,printVector(iteration, dimension));
			logMessage(DEBUG2, "move number"    ,integerToString(movementNumber));
			logMessage(DEBUG2, "acceptance rate",doubleToString(acceptanceRate));
	        logMessage(DEBUG2, "recall-temperature",doubleToString(temperature));
	        logMessage(DEBUG2, "recall-iteration           ",printVector(iteration,dimension));

			neighbor = generateNeighbor(iteration,dimension,neighborStep);
			logMessage(DEBUG2,"step 1 : generated neighbor",printVector(neighbor,dimension));

			neighborValue = pb->compute(pb,neighbor,dimension)[0];
			delta = neighborValue - iterationValue;
			logMessage(DEBUG2,"step 2 : compute delta",doubleToString(delta));

			criteria = MetropolisCriteria(delta,temperature);
			logMessage(DEBUG2,"step 3 : checking MetropolisCriteria",printBoolean(criteria));
			if( criteria )
			{
				movementNumber++;
				iterationValue = neighborValue;
				memcpy(iteration,neighbor,dimension*sizeof(double));

				sprintf(buffer,"Iteration : %s ; value : %lf",printVector(iteration,dimension),iterationValue);
				logMessage(DEBUG2,"Setting the new iteration",buffer);

				if( iterationValue < minimaValue )
				{
					minimaValue = iterationValue;
					memcpy(minima,iteration,dimension*sizeof(double));

					sprintf(buffer,"Minima : %s ; Minima value : %lf",printVector(minima,dimension),minimaValue);
					logMessage(DEBUG2,"Setting new minima",buffer);
				}
			}

			acceptanceRate = ((double)movementNumber/(double)subcounter);

			free(neighbor);
			logMessage(DEBUG2,"step 4 : clean","freeing temporary data");

			criteria = levelCondition(subcounter,levelLimit,acceptanceRate);
			logMessage(DEBUG2,"step 5 : level condition test",printBoolean(criteria));

        }while( criteria );

        //Baisse de temperature
        decreaseTemperature(&temperature,counter);

        toContinue = TRUE;

        toContinue = toContinue && (counter < patience);
        logMessage(DEBUG1, "stop condition 1 : counter < patience", printBoolean(counter < patience));

        if( counter > (patience/3) )
        {
        	toContinue = toContinue && acceptanceTest(acceptanceRate);
            logMessage(DEBUG1, "stop condition 2 : acceptanceTest", printBoolean(acceptanceTest(acceptanceRate)));
        }

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

	sprintf(buffer,"Minima : %s ; Minima value : %lf",printVector(minima,dimension),minimaValue);
	logMessage(DEBUG1,"result minima",buffer);

	saveAlgorithmData(m,pb,args,counter,minima,minimaValue,error);
	logMessage(DEBUG1,"algorithm data","saved");
}

static
double* generateNeighbor(double *iteration, size_t dimension, double neighborStep)
{
	int i;
	double rand;
	double *neighbor = (double *)malloc(dimension*sizeof(double));
	memcpy(neighbor,iteration,dimension*sizeof(double));

	for(i=0;i<dimension;i++)
	{
		rand = homeRandom(0,1);
		logMessage(DEBUG3,"rand value",doubleToString(rand));
		if( rand < 0.3 )
		{
			neighbor[i] += neighborStep;
		}
		else if ( rand < 0.6 )
		{
			neighbor[i] -= neighborStep;
		}
	}

	return neighbor;
}

static
boolean MetropolisCriteria(double delta,double temperature)
{
	logMessage(DEBUG3,"enter method","MetropolisCriteria");
	if( delta <= 0 )
	{
		logMessage(DEBUG3,"exit method",printBoolean(TRUE));
		return TRUE;
	}
	else
	{
		boolean accepted;
		double rand      = homeRandom(0,1);
		double fraction  = -delta / temperature;
		double expo      = exp(fraction);

		logMessage(DEBUG3,"rand value",doubleToString(rand));
		logMessage(DEBUG3,"fraction value",doubleToString(fraction));
		logMessage(DEBUG3,"exponentiel value",doubleToString(expo));

		accepted = (rand <= expo);

		logMessage(DEBUG3,"exit method",printBoolean(accepted));
		return accepted;
	}
}

static
boolean levelCondition(int moveNumber,int levelLimit, double acceptanceRate)
{
	logMessage(DEBUG3,"enter method","levelCondition");
	logMessage(DEBUG3,"parameter : moveNumber",integerToString(moveNumber));
	logMessage(DEBUG3,"parameter : levelLimit",integerToString(levelLimit));
	logMessage(DEBUG3,"parameter : acceptanceRate",doubleToString(acceptanceRate));
	logMessage(DEBUG3,"test : moveNumber > ( levelLimit / 3 )", printBoolean(moveNumber > ( levelLimit / 3 )));
	logMessage(DEBUG3,"test : moveNumber < levelLimit", printBoolean(moveNumber < levelLimit));
	logMessage(DEBUG3,"test : acceptanceRate > 0.1", printBoolean(acceptanceRate > LOW_ACCEPTANCE));
	logMessage(DEBUG3,"test : acceptanceRate < 0.9", printBoolean(acceptanceRate < HIGH_ACCEPTANCE));
	if( moveNumber > ( levelLimit / 3 ) )
		return moveNumber < levelLimit                // Nombre de voisins visité limite atteint
			&& acceptanceRate > LOW_ACCEPTANCE    // Acceptance trop faible
			&& acceptanceRate < HIGH_ACCEPTANCE;                  // Trop de vecteurs acceptés
	else
		return moveNumber < levelLimit;               // Nombre de voisins visité limite atteint
}

static
void    decreaseTemperature(double *temperature, int levelNumber)
{
	//*temperature = ( TEMPERATURE_DECREASE_CONSTANT / log( 1 + levelNumber ) );
	*temperature = TEMPERATURE_DECREASE_CONSTANT * (*temperature);
}

static
boolean acceptanceTest(double acceptanceRate)
{
	return acceptanceRate > LOW_ACCEPTANCE;
}

