#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "configuration.h"

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

#include "problema.h"
#include "persistentItem.h"

#define PROBLEM_TEMPLATE    "Problème : %s (id:%4ld)"
#define FUNCTION_TEMPLATE   "Fonction : %s\nDimension %3d"

// Private Functions
static
char* display(Problema * pb) {
	// Name presentation

	int nameSize = strlen(PROBLEM_TEMPLATE) + strlen(pb->name) + 4;
	int functionSize = strlen(FUNCTION_TEMPLATE) + strlen(
			pb->functionRepresentation) + 3;
	char name[nameSize + 1];
	char function[functionSize + 1];
	char constraints[MAX_STRING_INPUT] = { 0 };

	ConstraintList *iterator = NULL;

	// result
	static char output[3 * MAX_STRING_INPUT] = { 0 };

	memset(name, '\0', (nameSize + 1) * sizeof(char));
	memset(function, '\0', (functionSize + 1) * sizeof(char));
	memset(constraints, '\0', MAX_STRING_INPUT * sizeof(char));
	memset(output, '\0', (3 * MAX_STRING_INPUT) * sizeof(char));

	sprintf(name, PROBLEM_TEMPLATE, pb->name, pb->getId(pb));
	sprintf(function, FUNCTION_TEMPLATE, pb->functionRepresentation,
			pb->_dimension);

	strcat(constraints, "Contraintes :\n");
	for (iterator = pb->_constraints; iterator; iterator = iterator->next)
		sprintf(constraints, "%s\t-%s\n", constraints, iterator->text);

	sprintf(output, "\n%s\n%s\n%s", name, function, constraints);

	if (pb->_solution)
		sprintf(output, "%sSolution : %s\n", output, printVector(pb->_solution,
				pb->_dimension));

	return &output[0];
}

static
double* computeValue(struct _problema* pb, const double x[], size_t dimension) {
	ConstraintList *iterator = NULL;
	int i;

	for (i = 0; i < dimension; i++){
		if (x[i] >= 1000000000000) {
			sprintf(buffer,"value out of the bounds : %lf",x[i]);
			logMessage(WARNING,"mathematics error",buffer);
			//FIXME Signal error featrue here...
			return NULL;
		}
	}

	if (dimension != pb->_dimension) {
		sprintf(buffer, "input dimension (%d) <> problem dimension (%d)",
				(int) dimension, (int) pb->_dimension);
		logMessage(SEVERE, "input dimension error", buffer);
		return NULL;
	}

	for (iterator = pb->_constraints; iterator; iterator = iterator->next)
		if (!iterator->constraint(x)) {
			sprintf(buffer, "constraint '%s' unverified : %s", iterator->text,
					printVector((double *)&x[0], dimension));
			logMessage(SEVERE, "constraint error", buffer);
			return NULL;
		}

	return pb->_function(x);
}

static
void assignSolution(struct _problema* pb, const double *solution) {
	free(pb->_solution);

	if (solution) {
		pb->_solution = (double *) malloc(pb->_dimension * sizeof(double *));

		memcpy(pb->_solution, solution, pb->_dimension * sizeof(double));
	} else {
		pb->_solution = NULL;
	}
}

static
void setDatabaseId(struct _problema* pb, unsigned long id) {
	if (!pb->_db_item)
		pb->_db_item = createPersistentItem(id);
	else
		pb->_db_item->_db_id = id;
}

static
unsigned long getDatabaseId(struct _problema* pb) {
	if (pb->_db_item)
		return pb->_db_item->getId(pb->_db_item);
	else
		return 0;
}

static ConstraintList* createConstraintCell(char* text, ConstraintType type,
		problem_constraint function) {
	ConstraintList *cell = (ConstraintList *) malloc(sizeof(ConstraintList));
	cell->constraint = function;
	cell->text = (char *) malloc(strlen(text) * sizeof(char));
	strcpy(cell->text, text);
	cell->type = type;
	cell->next = NULL;

	return cell;
}

static
void destroyConstraintList(ConstraintList *list) {
	if (list) {
		if (list->next)
			destroyConstraintList(list->next);
		free(list->text);
		free(list);
	}
}

// Constructeur par défaut
Problema* createProblemaSkeleton() {
	Problema * entity = (Problema *) malloc(sizeof(Problema));

	// error
	handleError(
			!entity,
			NULL,
			"problem creation",
			"error during pointer allocation");

	// initialize
	// - dummy id
	entity->_db_item = NULL;
	// - clear name
	entity->name = NULL;
	// - clear name
	entity->functionRepresentation = NULL;
	// - dummy dimension
	entity->_dimension = 0;
	// - objective function
	entity->_function = NULL;
	// - constraints
	entity->_constraints = NULL;
	// - derivee
	memset(entity->_derivees, 0/*NULL*/, MAX_ITEM);
	// - solution
	entity->_solution = NULL;

	// methods
	entity->compute = computeValue;
	entity->display = display;
	entity->setSolution = assignSolution;
	entity->setId = setDatabaseId;
	entity->getId = getDatabaseId;

	return entity;
}

// Public Functions

Problema* createProblema(unsigned long id, char* name,
		char* function_representation, unsigned int dimension,
		problem_function function) {
	Problema* pb = NULL;

	pb = createProblemaSkeleton();

	pb->_db_item = createPersistentItem(id);
	pb->name = (char *) malloc(strlen(name) * sizeof(char));
	strcpy(pb->name, name);
	pb->functionRepresentation = (char *) malloc(
			strlen(function_representation) * sizeof(char));
	strcpy(pb->functionRepresentation, function_representation);
	pb->_dimension = dimension;
	pb->_function = function;

	return pb;
}

void destroyProblema(Problema * pb) {
	destroyPersistentItem(pb->_db_item);
	free(pb->name);
	free(pb->functionRepresentation);
	destroyConstraintList(pb->_constraints);
	//free(pb->_derivees);
	free(pb->_solution);
	free(pb);
}

Problema* addConstraint(Problema * pb, char* text, ConstraintType type,
		problem_constraint function) {
	sprintf(buffer, "constraint pointer : %p", pb->_constraints);
	logMessage(DEBUG3, "entering : addConstraint", buffer);
	if (!pb->_constraints) {
		pb->_constraints = createConstraintCell(text, type, function);
		logMessage(DEBUG3, "cell data : type", integerToString(
				pb->_constraints->type));
		logMessage(DEBUG3, "cell data : text", pb->_constraints->text);
		sprintf(buffer, "%p", pb->_constraints->constraint);
		logMessage(DEBUG3, "cell data : constraint", buffer);
		sprintf(buffer, "%p", pb->_constraints->next);
		logMessage(DEBUG3, "cell data : next", buffer);
		sprintf(buffer, "%p", pb->_constraints);
		logMessage(DEBUG3, "exiting method", buffer);
	} else {
		ConstraintList *cursor = NULL;
		cursor = pb->_constraints;
		sprintf(buffer, "%p", (cursor->next));
		logMessage(DEBUG3, "iterate constraint", buffer);
		while (cursor->next) {
			cursor = cursor->next;
			sprintf(buffer, "%p", cursor->next);
			logMessage(DEBUG3, "iterate constraint", buffer);
		}
		cursor->next = createConstraintCell(text, type, function);
		logMessage(DEBUG3, "cell data : type", integerToString(cursor->type));
		logMessage(DEBUG3, "cell data : text", cursor->text);
		sprintf(buffer, "%p", cursor->constraint);
		logMessage(DEBUG3, "cell data : constraint", buffer);
		sprintf(buffer, "%p", cursor->next);
		logMessage(DEBUG3, "cell data : next", buffer);
		sprintf(buffer, "%p", cursor);
		logMessage(DEBUG3, "exiting method", buffer);
	}

	return pb;
}

Problema* addDerivee(Problema * pb, problem_function function,
		unsigned int order) {
	handleError(
			(order - 1)>= MAX_ITEM,
			NULL,
			"derivee adding",
			"given order is to high, you can't add such a derivee");

	handleError(
			(order - 1)< 0,
			NULL,
			"derivee adding",
			"minimal order is 1");

	pb->_derivees[order - 1] = function;

	return pb;
}
