/*
 * BASIC method-tabou-research.c
 *
 *  Created on: Nov 15, 2010
 *      Author: blackpanther
 */
#include <time.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "configuration.h"

#include "problema.h"
#include "method.h"
#include "utilities.h"
#include "logger.h"

#define DEBUG_LEVEL   INFO

#define OLD 0
#define NEW 1

#define MAX_TABOU 10

double SPACE_WIDE = 0.01;

typedef struct _entry {
	double *minima;
} Entry;

typedef struct _tabouList {
	int cursor;
	int size;
	Entry list[MAX_TABOU];
} TabouList;

static
char* printNeighborsList(double **neighbors, size_t neighbors_size,
		size_t dimension);

static
char* printTabouList(TabouList list, size_t dimension);

static
double* fetchMinima(double **neighbors, int neighbor_size, Problema* pb);
static
void recursivelyGenerateNeighbor(double **neighbors, TabouList *tabou,
		double *current_neighbor, int composant_index, int dimension);

static
void generateNeighborSamples(double *currentVector, TabouList *tabouList,
		size_t dimension, double **neighbors, int neighborsNumber);

static boolean aspirationCriteriaTest();

static
void addTabouVector(TabouList *list, Entry neighbor, size_t dimension);

static boolean isBest(double *v1, double *v2);

static boolean isIn(TabouList *list, double *neighbor, size_t dimension);

void resolveByTabouResearch(Problema *pb, Method *m, double init_vector[],
		MethodParameters args) {
	// Default value
	double iteration[pb->_dimension];
	boolean toContinue = TRUE;
	double absolute_error[2] = { -1.0, -1.0 };
	int patience;
	int counter = 0;
	int i;

	// Tabou variables
	double *minima_vector = NULL;
	double *minima_function_value = NULL;
	double *selectedNeighbor = NULL;

	int neighbors_size = pow(3, pb->_dimension) - 1;
	double *neighbors[neighbors_size];

	TabouList tabouList;
	Entry entryBuffer;

	activateLogger();
	setLoggerLevel(DEBUG_LEVEL);

	logMessage(INFO, "enter method", "resolveByTabouResearch");

	patience = *((int *) getParameterProperty(args, PATIENCE_LOADED));
	logMessage(DEBUG1, "parameters : patience", integerToString(patience));
	logMessage(DEBUG1, "parameters : initial vector", printVector(init_vector,
			pb->_dimension));

	if (args.data_loaded & NEIGHBOR_STEP_LOADED) {
		SPACE_WIDE = *((double *) getParameterProperty(args,
				NEIGHBOR_STEP_LOADED));
	}

	logMessage(DEBUG1, "parameters : space wide", doubleToString(SPACE_WIDE));

	logMessage(DEBUG2, "init", "random seed");
	//init for alea var
	srand(time(NULL));

	minima_vector = (double *) malloc(pb->_dimension * sizeof(double));
	minima_function_value = (double *) malloc(sizeof(double));

	memcpy(iteration, init_vector, pb->_dimension * sizeof(double));

	memcpy(minima_vector, iteration, pb->_dimension * sizeof(double));
	memcpy(minima_function_value, pb->compute(pb, iteration, pb->_dimension),
			sizeof(double));
	logMessage(DEBUG2, "init : current minima vector", printVector(
			minima_vector, pb->_dimension));
	logMessage(DEBUG2, "init : current function minima vector value",
			printVector(minima_function_value, 1));

	//init the tabou list
	for (i = 0; i < MAX_TABOU; i++) {
		tabouList.list[i].minima = (double *) malloc(pb->_dimension
				* sizeof(double));
		memset(tabouList.list[i].minima, 0.0, pb->_dimension * sizeof(double));
	}

	//init the neighbors list
	for (i = 0; i < neighbors_size; i++) {
		neighbors[i] = (double *) malloc(pb->_dimension * sizeof(double));
	}

	entryBuffer.minima = (double *) malloc(pb->_dimension * sizeof(double));

	logMessage(DEBUG2, "loop step", "start iterative actions");

	tabouList.cursor = 0;
	tabouList.size = 0;
	while (toContinue) {
		logMessage(DEBUG1, "counter", integerToString(counter + 1));
		logMessage(DEBUG1, "iteration", printVector(iteration, pb->_dimension));
		logMessage(DEBUG1, "tabou list", printTabouList(tabouList,
				pb->_dimension));

		generateNeighborSamples(iteration, &tabouList, pb->_dimension,
				neighbors, neighbors_size);
		logMessage(DEBUG2, "step 1 : generate all neighbors",
				printNeighborsList(neighbors, neighbors_size, pb->_dimension));

		selectedNeighbor = fetchMinima(neighbors, neighbors_size, pb);
		logMessage(DEBUG2, "step 2 : select the best neighbor (minima)",
				printVector(selectedNeighbor, pb->_dimension));

		if (!selectedNeighbor) {
			logMessage(INFO,
					"stopped at step 2 : no best neighbor can be found",
					"stuck and decide to finish and select the current best");
			break;
		}

		memcpy(entryBuffer.minima, selectedNeighbor, pb->_dimension
				* sizeof(double));
		addTabouVector(&tabouList, entryBuffer, pb->_dimension);
		logMessage(DEBUG2, "step 3 : add it to the tabouList", printTabouList(
				tabouList, pb->_dimension));

		memcpy(iteration, selectedNeighbor, pb->_dimension * sizeof(double));
		logMessage(DEBUG2, "step 4 : set the new iteration", printVector(
				iteration, pb->_dimension));

		boolean best = isBest(pb->compute(pb, iteration, pb->_dimension),
				minima_function_value);
		logMessage(DEBUG2,
				"step 5 : is the new iteration the current best vector",
				printBoolean(best));

		if (best) {
			logMessage(DEBUG2, "step 5.1 : old new minima", printVector(
					minima_vector, pb->_dimension));
			logMessage(DEBUG2, "step 5.2 : old new minima value", printVector(
					minima_function_value, 1));
			memcpy(minima_vector, iteration, pb->_dimension * sizeof(double));
			memcpy(minima_function_value, pb->compute(pb, iteration,
					pb->_dimension), sizeof(double));
			logMessage(DEBUG2, "step 5.3 : assign new minima", printVector(
					minima_vector, pb->_dimension));
			logMessage(DEBUG2, "step 5.4 : assign new minima value",
					printVector(minima_function_value, 1));

			//Calcul de l'erreur, si on la possède
			if (pb->_solution) {
				absolute_error[NEW] = error(pb->_dimension, minima_vector,
						pb->_solution);

				logMessage(DEBUG2, "error", doubleToString(absolute_error[NEW]));
				if (absolute_error[OLD] != -1)
					logMessage(DEBUG2, "error difference between iteration",
							doubleToString(fabs(absolute_error[NEW]
									- absolute_error[OLD])));

				// Actualisation statistique sur l'erreur
				absolute_error[OLD] = absolute_error[NEW];
			}
		}

		toContinue = aspirationCriteriaTest(); //FIXME Document about it

		logMessage(DEBUG2, "stop condition 1 : apsiration criteria test",
				printBoolean(toContinue));

		toContinue = toContinue && (++counter < patience);

		logMessage(DEBUG2, "stop condition 2 : counter < m->_max_iteration",
				printBoolean(counter < patience));
		logMessage(DEBUG2, "should we continue", printBoolean(toContinue));

	}

	saveAlgorithmData(
			m,
			pb,args,
			counter,
			minima_vector,
			minima_function_value[0],
			absolute_error[NEW]
	);

	free(minima_vector);
	free(minima_function_value);

	free(entryBuffer.minima);

	for (i = 0; i < MAX_TABOU; i++)
		free(tabouList.list[i].minima);

	//free the neighbors list
	for (counter = 0; counter < neighbors_size; counter++) {
		free(neighbors[counter]);
	}
	logMessage(DEBUG2, "clean step", "each neighbor free");
}

static
char* printNeighborsList(double **neighbors, size_t neighbors_size,
		size_t dimension) {
	static char output[MAX_STRING_INPUT] = { 0 };
	int i;

	logMessage(DEBUG3, "enter method", "printNeighborsList");

	strcpy(output, "");
	strcat(output, "\n\t-{\n");
	for (i = 0; i < neighbors_size; i++) {
		logMessage(DEBUG3, "test cursor value", printVector(neighbors[i],
				dimension));
		if (neighbors[i] != NULL)
			sprintf(output, "%s\t\t-%s\n", output, printVector(neighbors[i],
					dimension));
	}
	strcat(output, "\t-}");

	logMessage(DEBUG3, "exit method", output);

	return output;
}

static
char* printTabouList(TabouList list, size_t dimension) {
	static char output[MAX_STRING_INPUT] = { 0 };
	int i;

	strcpy(output, "");

	strcat(output, "\n\t-{\n");
	for (i = 0; i < list.size; i++) {
		sprintf(output, "%s\t\t-%s\n", output, printVector(list.list[i].minima,
				dimension));
	}
	strcat(output, "\t-}");

	return output;
}

static
double* fetchMinima(double **neighbors, int neighbor_size, Problema* pb) {
	double lowest_value;
	double compute_value;
	int i;
	double *selected = NULL;

	//logMessage(DEBUG3, "enter method", "fetchMinima");

	// init the default with the first non-null value
	for (i = 0; i < neighbor_size; i++) {
		if (neighbors[i] != NULL) {
			lowest_value = pb->compute(pb, neighbors[i], pb->_dimension)[0];
			selected = neighbors[i];
			//logMessage(DEBUG3, "default start minima", printVector(selected, dimension));
			//logMessage(DEBUG3, "start minima value", doubleToString(lowest_value));
			break;
		}
	}

	// if one found
	if (selected) {
		for (i = 0; i < neighbor_size; i++) {
			if (neighbors[i] != NULL) {
				//logMessage(DEBUG3, "iterate vector", printVector(neighbors[i], dimension));
				compute_value
						= pb->compute(pb, neighbors[i], pb->_dimension)[0];
				//logMessage(DEBUG3, "iterate vector value", doubleToString(compute_value));
				if (compute_value < lowest_value) {
					lowest_value = compute_value;
					//logMessage(DEBUG3, "found better value", doubleToString(lowest_value));
					selected = neighbors[i];
					//logMessage(DEBUG3, "new better minima", printVector(selected, dimension));
				}
			}
		}
	} else {
		logMessage(SEVERE, "problem : fetch minima", "neighbors list is empty");
	}

	//logMessage(DEBUG3, "exit method", printVector(selected, dimension));

	return selected;
}

static
void addNeighborVector(double **neighbors, double *current_neighbor,
		int dimension) {
	double **cursor = NULL;

	cursor = neighbors;

	while (*cursor)
		cursor++;

	*cursor = (double *) malloc(dimension * sizeof(double));
	memcpy(*cursor, current_neighbor, dimension * sizeof(double));
}

static
void recursivelyGenerateNeighbor(double **neighbors, TabouList *tabou,
		double *current_neighbor, int composant_index, int dimension) {
	double buffer[dimension];

	logMessage(DEBUG3, "recursion", "enter");
	logMessage(DEBUG3, "parameters : current_vector", printVector(
			current_neighbor, dimension));
	logMessage(DEBUG3, "parameters : composant index", integerToString(
			composant_index));
	logMessage(DEBUG3, "parameters : dimension", integerToString(dimension));

	int oldCursor = tabou->cursor;

	if (composant_index < dimension) {
		//reinit the vector
		memcpy(buffer, current_neighbor, dimension * sizeof(double));
		buffer[composant_index] += SPACE_WIDE;
		logMessage(DEBUG3, "first vector generated", printVector(buffer,
				dimension));

		recursivelyGenerateNeighbor(neighbors, tabou, buffer, composant_index
				+ 1, dimension);

		//reinit the vector
		memcpy(buffer, current_neighbor, dimension * sizeof(double));
		buffer[composant_index] -= SPACE_WIDE;
		logMessage(DEBUG3, "second vector generated", printVector(buffer,
				dimension));

		recursivelyGenerateNeighbor(neighbors, tabou, buffer, composant_index
				+ 1, dimension);

		//reinit the vector
		memcpy(buffer, current_neighbor, dimension * sizeof(double));
		logMessage(DEBUG3, "third vector generated", printVector(buffer,
				dimension));

		recursivelyGenerateNeighbor(neighbors, tabou, buffer, composant_index
				+ 1, dimension);
	} else {
		logMessage(DEBUG3, "recursion", "end recursion");
		if (!isIn(tabou, current_neighbor, dimension)) {
			logMessage(DEBUG3, "vector accepted", printVector(current_neighbor,
					dimension));
			addNeighborVector(neighbors, current_neighbor, dimension);
		}
	}

	tabou->cursor = oldCursor;
}

static
void generateNeighborSamples(double *currentVector, TabouList *tabou,
		size_t dimension, double **neighbors, int neighborsNumber) {

	int counter;
	logMessage(DEBUG3, "enter method", "generateNeighborSamples");

	logMessage(DEBUG3, "step 1", "initialize all neighbors");
	// init the neighbors list
	for (counter = 0; counter < neighborsNumber; counter++) {
		free(neighbors[counter]);
		neighbors[counter] = NULL;
	}

	logMessage(DEBUG3, "step 2", "recursively create neighbors");
	logMessage(DEBUG2, "tabou cursor before", integerToString(tabou->cursor));
	recursivelyGenerateNeighbor(neighbors, tabou, currentVector, 0, dimension);

	logMessage(DEBUG3,"cursor",integerToString(tabou->cursor));
	for (counter = 0; counter < neighborsNumber; counter++) {
		logMessage(DEBUG3, "check assignement : index",
				integerToString(counter));
		logMessage(DEBUG3, "check assignement : value", printVector(
				neighbors[counter], dimension));
		logMessage(DEBUG3,"cursor",integerToString(tabou->cursor));
	}

	logMessage(DEBUG2, "tabou cursor after", integerToString(tabou->cursor));

}

static
void addTabouVector(TabouList *list, Entry neighbor, size_t dimension) {
	logMessage(DEBUG3, "enter method", "addTabouVector");
	sprintf(buffer, "cursor %d, size %d, given vector %s", list->cursor,
			list->size, printVector(neighbor.minima, dimension));
	logMessage(DEBUG3, "initial", buffer);
	double *vector = list->list[list->cursor].minima;
	sprintf(buffer, "%p", vector);
	logMessage(DEBUG3, "vector", buffer);
	memcpy(vector, neighbor.minima, dimension * sizeof(double));

	list->cursor++;
	logMessage(DEBUG3, "vector add", "done");

	if (list->size < MAX_TABOU)
		list->size++;

	logMessage(DEBUG3, "size increment", "done");

	if (list->cursor == MAX_TABOU) {
		logMessage(DEBUG2, "maximum memory reached",
				"start to forget old entries");
		list->cursor = 0;
	}

	logMessage(DEBUG3, "cursor reinitialization", "done");
}

static boolean isBest(double *v1, double *v2) {
	return *v1 < *v2;
}

static boolean isIn(TabouList *list, double *neighbor, size_t dimension) {
	int i, j;

	logMessage(DEBUG3, "enter method", "isIn");
	logMessage(DEBUG3, "parameter : vector", printVector(neighbor, dimension));
	for (i = 0; i < list->size; i++) {
		logMessage(DEBUG3, "iterative", printVector(list->list[i].minima,
				dimension));
		boolean areEquals = TRUE;
		//compare two vectors
		for (j = 0; j < dimension; j++) {
			areEquals = areEquals && (list->list[i].minima[j] == neighbor[j]);
		}

		if (areEquals) {
			logMessage(DEBUG3, "event", "found that there are equals !");
			logMessage(DEBUG3, "exit method", printBoolean(TRUE));
			return TRUE;
		}
	}

	logMessage(DEBUG3, "exit method", printBoolean(FALSE));

	return FALSE;
}

static boolean aspirationCriteriaTest() {
	return TRUE;
}
