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

#include <string.h>

#include <math.h>

#include <libxml/parser.h>
#include <libxml/xpath.h>

#include "configuration.h"

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

static long currentSessionId = 0L;

long getSessionId(){
    return currentSessionId;
}

void setSessionId(long sessionId)
{
	currentSessionId = sessionId;
}

double homeRandom(double a, double b) {
    return ( rand() / (double) RAND_MAX) * (b - a) + a;
}

char* printVector(
        double *vector,
        size_t dimension) {
    static char value[40];
    char buffer[20]      = {0};
    unsigned int counter = 0;

    memset(value, '\0', 40);

    strcat(value, "{");

    if (vector) {
        while (counter < dimension) {

            sprintf(buffer, "% .5lf", vector[counter]);
            if (counter + 1 < dimension) strcat(buffer, ", ");

            strcat(value, buffer);
            
            counter++;
        }
    } else {
        strcat(value,"NULL");
    }

    strcat(value, "}");

    return value;
}

char* printMatrix(double **matrix, size_t row, size_t column) {
    int i, j;
    static char value[80];
    char buffer[80];

    memset(value, '\0', 80 * sizeof (char));

    for (i = 0; i < row; i++) {
        strcat(value, "\n[");
        for (j = 0; j < column; j++) {
            sprintf(buffer, "%.4lf", matrix[i][j]);
            strcat(value, buffer);
            if (j != row - 1)
                strcat(value, ", ");
        }
        strcat(value, "]");
    }

    return &(value[0]);
}

char* printBoolean(
        boolean b) {
    static char* booleanValue[] = {
        "TRUE",
        "FALSE"
    };

    return (b != 0) ? booleanValue[0] : booleanValue[1];
}

/**
 * XXX Free the result
 * @param function
 * @param x
 * @param dimension
 * @param delta
 * @return
 */
double* approximateGradient(
        problem_function function,
        double x[],
        size_t dimension,
        double delta) {
    double buffer[dimension];
    double* result;
    double operation_buffer;
    int counter;

    logMessage(DEBUG3, "presentation", "approximateGradient");
    logMessage(DEBUG3, "init vector", printVector(x, dimension));
    logMessage(DEBUG3, "init dimension", integerToString(dimension));

    result = (double *) malloc(dimension * sizeof (double));

    for (counter = 0; counter < dimension; counter++) {
        memcpy(buffer, x, dimension * sizeof (double)); //copie du vecteur
        logMessage(DEBUG3, "init buffer", printVector(buffer, dimension));
        buffer[counter] += delta;
        logMessage(DEBUG3, "delta buffer", printVector(buffer, dimension));


        logMessage(DEBUG3, "function value w/ buffer", printVector((*function)(buffer), 1));
        logMessage(DEBUG3, "function value w/ initial vector", printVector((*function)(x), 1));
        logMessage(DEBUG3, "operation value", "");

        operation_buffer = (*function)(buffer)[0];
        operation_buffer -= (*function)(x)[0];

        result[counter] = (operation_buffer) / delta;
    }

    logMessage(DEBUG3, "result", printVector(result, dimension));

    return result;
}

double normeEuclidienne(
        const double x[],
        const size_t dimension) {
    int i;
    double temp = 0;
    for (i = 0; i < dimension; i++)
        temp += x[i] * x[i];

    return (double) sqrt(temp);
}

double error(
        unsigned int dimension,
        double x[],
        double solution[]) {
    double buffer[dimension];
    int i;

    for (i = 0; i < dimension; i++)
        buffer[i] = x[i] - solution[i];

    return normeEuclidienne(buffer, dimension);
}

char* integerToString(int val) {
    static char buf[32] = {0};

    sprintf(buf,"%d",val);

    return &buf[0];
}

char* doubleToString(double val) {
    static char buf[32] = {0};

    sprintf(buf, "%.6lf", val);

    return &buf[0];
}

boolean fileExists(
        char * filepath) {
    FILE *f = NULL;
    f = fopen(filepath, "r");

    logMessage(DEBUG3, "file existency : filepath", filepath);

    if (!f) {
        return FALSE;
    } else {
        fclose(f);
        return TRUE;
    }
}


/**
 * Libération de la mémoire pour la structure xmlConfig_t
 **/
void destroyConfig(xmlConfig_t *conf) {
    xmlXPathFreeContext(conf->ctxt);
    xmlFreeDoc(conf->doc);
    free(conf->fichier);
    free(conf);
}

/**
 * Initialisation et chargement du fichier XML en mémoire
 **/
xmlConfig_t *loadConfiguration(const char *fichier) {
    xmlConfig_t *conf, null = { 0 };

    if ((conf = malloc(sizeof *conf)) == NULL) {
        return NULL;
    }
    memcpy(conf, &null, sizeof(*conf));
    // Copie du nom du fichier
    if ((conf->fichier = strdup(fichier)) == NULL) {
        destroyConfig(conf);
        return NULL;
    }
    // Création de l'arbre DOM à partir du fichier XML
    xmlKeepBlanksDefault(0);
    if ((conf->doc = xmlParseFile(conf->fichier)) == NULL) {
        destroyConfig(conf);
        return NULL;
    }
    // Récupération de la racine
    conf->racine = xmlDocGetRootElement(conf->doc);
    if (conf->racine != NULL && xmlStrcasecmp(conf->racine->name, (const xmlChar *)"configuration")) {
        destroyConfig(conf);
        return NULL;
    }
    // Initialisation de l'environnement XPath
    xmlXPathInit();
    // Création d'un contexte pour les requêtes XPath
    conf->ctxt = xmlXPathNewContext(conf->doc);
    if (conf->ctxt == NULL) {
        destroyConfig(conf);
        return NULL;
    }
    return conf;
}

/**
 * Fonction privée retournant l'élément <directive> via XPath correspondant
 * à l'attribut nom
 **/
static
xmlNodePtr _get_node_by_xpath(xmlConfig_t *conf, const char *directive) {
    xmlXPathObjectPtr xpathRes;
    xmlNodePtr n = NULL;
    char *path = (char*)malloc(
    		( strlen("/configuration/directive[@nom=\"%s\"]") + strlen(directive) )
    	    * sizeof(char));

    sprintf(path, "/configuration/directive[@nom=\"%s\"]", directive);
    // Evaluation de l'expression XPath
    xpathRes = xmlXPathEvalExpression((const xmlChar *)path, conf->ctxt);
    free(path);
    if (xpathRes && xpathRes->type == XPATH_NODESET && xpathRes->nodesetval->nodeNr == 1) {
        n = xpathRes->nodesetval->nodeTab[0];
    }
    xmlXPathFreeObject(xpathRes);

    return n;
}

/**
 * Renvoie la valeur d'une directive
 **/
char *getProperty(xmlConfig_t *conf, const char *directive) {
    xmlNodePtr n = _get_node_by_xpath(conf, directive);

    if (n != NULL) {
    	xmlChar* buffer = xmlNodeGetContent(n);
    	char *value = (char *)malloc(strlen((const char *)buffer)*sizeof(char));
    	strcpy(value,(const char *)buffer);
    	xmlFree(buffer);
    	return value;
    }
    return NULL;
}

void loadMethodParameters(MethodParameters *bundle,MethodParameters parameters){
	if ( parameters.data_loaded & PATIENCE_LOADED )
	{
		free(bundle->parameters[INDEX_PATIENCE]);
		bundle->parameters[INDEX_PATIENCE] = (void *)malloc(sizeof(int));
		memcpy(bundle->parameters[INDEX_PATIENCE],parameters.parameters[INDEX_PATIENCE],sizeof(int));
	}
	if( parameters.data_loaded & EPSILON_LOADED )
	{
		free(bundle->parameters[INDEX_EPSILON]);
		bundle->parameters[INDEX_EPSILON] = (void *)malloc(sizeof(double));
		memcpy(bundle->parameters[INDEX_EPSILON],parameters.parameters[INDEX_EPSILON],sizeof(double));
	}
	if ( parameters.data_loaded & BOUNDS_MIN_LOADED )
	{
		free(bundle->parameters[INDEX_BOUNDS_MIN]);
		bundle->parameters[INDEX_BOUNDS_MIN] = (void *)malloc(sizeof(double));
		memcpy(bundle->parameters[INDEX_BOUNDS_MIN],parameters.parameters[INDEX_BOUNDS_MIN],sizeof(double));
	}
	if ( parameters.data_loaded & BOUNDS_MAX_LOADED )
	{
		free(bundle->parameters[INDEX_BOUNDS_MAX]);
		bundle->parameters[INDEX_BOUNDS_MAX] = (void *)malloc(sizeof(double));
		memcpy(bundle->parameters[INDEX_BOUNDS_MAX],parameters.parameters[INDEX_BOUNDS_MAX],sizeof(double));
	}
	if ( parameters.data_loaded & STEP_LOADED )
	{
		free(bundle->parameters[INDEX_STEP]);
		bundle->parameters[INDEX_STEP] = (void *)malloc(sizeof(double));
		memcpy(bundle->parameters[INDEX_STEP],parameters.parameters[INDEX_STEP],sizeof(double));
	}
	if ( parameters.data_loaded & TEMPERATURE_LOADED )
	{
		free(bundle->parameters[INDEX_TEMPERATURE]);
		bundle->parameters[INDEX_TEMPERATURE] = (void *)malloc(sizeof(double));
		memcpy(bundle->parameters[INDEX_TEMPERATURE],parameters.parameters[INDEX_TEMPERATURE],sizeof(double));
	}
	if ( parameters.data_loaded & NEIGHBOR_STEP_LOADED )
	{
		free(bundle->parameters[INDEX_NEIGHBOR_STEP]);
		bundle->parameters[INDEX_NEIGHBOR_STEP] = (void *)malloc(sizeof(double));
		memcpy(bundle->parameters[INDEX_NEIGHBOR_STEP],parameters.parameters[INDEX_NEIGHBOR_STEP],sizeof(double));
	}
	if ( parameters.data_loaded & LEVEL_LIMIT_LOADED )
	{
		free(bundle->parameters[INDEX_LEVEL_LIMIT]);
		bundle->parameters[INDEX_LEVEL_LIMIT] = (void *)malloc(sizeof(int));
		memcpy(bundle->parameters[INDEX_LEVEL_LIMIT],parameters.parameters[INDEX_LEVEL_LIMIT],sizeof(int));
	}
	if ( parameters.data_loaded & POPULATION_SIZE_LOADED )
	{
		free(bundle->parameters[INDEX_POPULATION_SIZE]);
		bundle->parameters[INDEX_POPULATION_SIZE] = (void *)malloc(sizeof(int));
		memcpy(bundle->parameters[INDEX_POPULATION_SIZE],parameters.parameters[INDEX_POPULATION_SIZE],sizeof(int));
	}
	if ( parameters.data_loaded & CROSS_PROBA_LOADED )
	{
		free(bundle->parameters[INDEX_CROSS_PROBA]);
		bundle->parameters[INDEX_CROSS_PROBA] = (void *)malloc(sizeof(double));
		memcpy(bundle->parameters[INDEX_CROSS_PROBA],parameters.parameters[INDEX_CROSS_PROBA],sizeof(double));
	}
	if ( parameters.data_loaded & MUTATION_PROBA_LOADED )
	{
		free(bundle->parameters[INDEX_MUTATION_PROBA]);
		bundle->parameters[INDEX_MUTATION_PROBA] = (void *)malloc(sizeof(double));
		memcpy(bundle->parameters[INDEX_MUTATION_PROBA],parameters.parameters[INDEX_MUTATION_PROBA],sizeof(double));
	}

	bundle->data_loaded = parameters.data_loaded;
}

void setParameterProperty(MethodParameters *parameters, int propertyFlag, void * propertyValue) {
	switch (propertyFlag) {
	case PATIENCE_LOADED:
		if (!parameters->parameters[INDEX_PATIENCE])
			parameters->parameters[INDEX_PATIENCE] = malloc(
					sizeof(void *));
		memcpy(parameters->parameters[INDEX_PATIENCE],
				propertyValue, sizeof(int));
		break;
	case EPSILON_LOADED:
		if (!parameters->parameters[INDEX_EPSILON])
			parameters->parameters[INDEX_EPSILON] = malloc(
					sizeof(void *));
		memcpy(parameters->parameters[INDEX_EPSILON],
				propertyValue, sizeof(double));
		break;
	case STEP_LOADED:
		if (!parameters->parameters[INDEX_STEP])
			parameters->parameters[INDEX_STEP] = malloc(
					sizeof(void *));
		memcpy(parameters->parameters[INDEX_STEP],
				propertyValue, sizeof(double));
		break;
	case TEMPERATURE_LOADED:
		if (!parameters->parameters[INDEX_TEMPERATURE])
			parameters->parameters[INDEX_TEMPERATURE] = malloc(
					sizeof(void *));
		memcpy(parameters->parameters[INDEX_TEMPERATURE],
				propertyValue, sizeof(double));
		break;
	case NEIGHBOR_STEP_LOADED:
		if (!parameters->parameters[INDEX_NEIGHBOR_STEP])
			parameters->parameters[INDEX_NEIGHBOR_STEP]
					= malloc(sizeof(void *));
		memcpy(parameters->parameters[INDEX_NEIGHBOR_STEP],
				propertyValue, sizeof(double));
		break;
	case LEVEL_LIMIT_LOADED:
		if (!parameters->parameters[INDEX_LEVEL_LIMIT])
			parameters->parameters[INDEX_LEVEL_LIMIT] = malloc(
					sizeof(void *));
		memcpy(parameters->parameters[INDEX_LEVEL_LIMIT],
				propertyValue, sizeof(int));
		break;
	case POPULATION_SIZE_LOADED:
		if (!parameters->parameters[INDEX_POPULATION_SIZE])
			parameters->parameters[INDEX_POPULATION_SIZE]
					= malloc(sizeof(void *));
		memcpy(parameters->parameters[INDEX_POPULATION_SIZE],
				propertyValue, sizeof(int));
		break;
	case CROSS_PROBA_LOADED:
		if (!parameters->parameters[INDEX_CROSS_PROBA])
			parameters->parameters[INDEX_CROSS_PROBA] = malloc(
					sizeof(void *));
		memcpy(parameters->parameters[INDEX_CROSS_PROBA],
				propertyValue, sizeof(double));
		break;
	case MUTATION_PROBA_LOADED:
		if (!parameters->parameters[INDEX_MUTATION_PROBA])
			parameters->parameters[INDEX_MUTATION_PROBA]
					= malloc(sizeof(void *));
		memcpy(parameters->parameters[INDEX_MUTATION_PROBA],
				propertyValue, sizeof(double));
		break;
	case BOUNDS_MIN_LOADED:
		if (!parameters->parameters[INDEX_BOUNDS_MIN])
			parameters->parameters[INDEX_BOUNDS_MIN] = malloc(
					sizeof(void *));
		memcpy(parameters->parameters[INDEX_BOUNDS_MIN],
				propertyValue, sizeof(double));
		break;
	case BOUNDS_MAX_LOADED:
		if (!parameters->parameters[INDEX_BOUNDS_MAX])
			parameters->parameters[INDEX_BOUNDS_MAX] = malloc(
					sizeof(void *));
		memcpy(parameters->parameters[INDEX_BOUNDS_MAX],
				propertyValue, sizeof(double));
		break;
	}

	parameters->data_loaded |= propertyFlag;
	sprintf(buffer, "given : %#X | loaded : %#X", propertyFlag,
			(unsigned int) parameters->data_loaded);
	logMessage(DEBUG3, "result data loaded", buffer);
}

void* getParameterProperty(MethodParameters parameters, int propertyFlag) {
	switch (propertyFlag) {
	case PATIENCE_LOADED:
		return parameters.parameters[INDEX_PATIENCE];
	case EPSILON_LOADED:
		return parameters.parameters[INDEX_EPSILON];
	case STEP_LOADED:
		return parameters.parameters[INDEX_STEP];
	case TEMPERATURE_LOADED:
		return parameters.parameters[INDEX_TEMPERATURE];
	case NEIGHBOR_STEP_LOADED:
		return parameters.parameters[INDEX_NEIGHBOR_STEP];
	case LEVEL_LIMIT_LOADED:
		return parameters.parameters[INDEX_LEVEL_LIMIT];
	case POPULATION_SIZE_LOADED:
		return parameters.parameters[INDEX_POPULATION_SIZE];
	case CROSS_PROBA_LOADED:
		return parameters.parameters[INDEX_CROSS_PROBA];
	case MUTATION_PROBA_LOADED:
		return parameters.parameters[INDEX_MUTATION_PROBA];
	case BOUNDS_MIN_LOADED:
		return parameters.parameters[INDEX_BOUNDS_MIN];
	case BOUNDS_MAX_LOADED:
		return parameters.parameters[INDEX_BOUNDS_MAX];
	}

	return NULL;
}
