/*
 * Calibrator.cpp
 *
 *  Created on: Aug 4, 2015
 *      Author: jjpalacios
 */


#include "Calibrator.h"

namespace clbDEB {


//=============================================================================
//
//	Abstract class Evaluation
//
//=============================================================================
//=============================================================================
//		CONSTRUCTORS / INITIALIZERS
//=============================================================================
//-----  Main constructor  ----------------------------------------------------
Calibrator::Calibrator(const char* clbSetup, const char* algorithmSetup) {
	clbDEB::ClassRegister::registerClasses();
	this->numRuns = 0;
	this->algorithm = NULL;
	this->calibratorSetup = NULL;
	this->seed = 1;

	this->loadConfiguration(clbSetup, algorithmSetup);
}


//-----  Destructor  ----------------------------------------------------------
Calibrator::~Calibrator() {
	delete this->algorithm;
	for(unsigned int i=0; i < this->paramDefinition.size(); i++)
		delete this->paramDefinition[i];
}





//=============================================================================
//		MAIN METHODS
//=============================================================================
//-----  Calibrate method  ----------------------------------------------------
std::vector< std::pair<std::string, double> > Calibrator::calibrate() {
	Results * solutionSet;
	std::string signature = this->generateSignature();

	std::cout << "Experiment " << signature << std::endl << std::endl;

	// Initialize the algorithm
	this->algorithm->prepareToRun();

	// Prepare the evaluation function
	Evaluation *evaluator = this->algorithm->evaluator;

	if(dynamic_cast<EvaluationMatlab *>(evaluator) != NULL) {
		EvaluationMatlab * matlabEval = dynamic_cast<EvaluationMatlab *>(evaluator);
		matlabEval->init(this->calibratorSetup, this->paramDefinition);
	}

	// Prepare average data
	avgTimes.clear();
	avgStats.clear();
	avgEvolStats.clear();

	// Open output file for sets of solutions
	std::ofstream outputFile;
	std::string outputName = this->logFolder + FSEP;
	outputName += CLB_OUTPUT_FILE;
	outputName += "_" + signature + "_" + CLB_OUTPUT_SETS + ".csv";

	outputFile.open(outputName.c_str());
	if(!outputFile.is_open()) {
		std::string err = "It was impossible to generate the output ";
		err += "files. They may be opened or the logFolder does ";
		err += "not exist";
		throw ClbException(err);
	}

	Solution * bestSolution;
	for(int run=0; run < numRuns; run++) {
		// Run the algorithm
		solutionSet = this->algorithm->run(signature+valueToString(run),
				this->logFolder, this->seed+run);

		// Look for the best solution
		Solution *sol = solutionSet->getBest(0,
				this->algorithm->sharedVariables);
		if(run == 0
				|| this->algorithm->sharedVariables->hasBetterFitness
					(sol->fitness, bestSolution->fitness)) {
			bestSolution = sol->clone();
		}

		// Print setup
		this->avgTimes.push_back(algorithm->getRuntime());

		// Print basic statistics
		this->avgStats.push_back(algorithm->getStatistics());

		// Information about the set of solutions
		outputFile << "Run " << run << std::endl;
		outputFile << "------------------------------------------------------";
		outputFile << std::endl;
		this->printSetData(outputFile, solutionSet);
		outputFile << std::endl;

		// Print evolution data
		std::vector< std::vector<double> > evolStats;
		evolStats = algorithm->getEvolution(this->avgEvolLabels);
		this->avgEvolStats.push_back(evolStats);

		// Close file and clear
		delete solutionSet;
	}
	outputFile.close();

	// Open output file
	outputName = this->logFolder + FSEP;
	outputName += CLB_OUTPUT_FILE;
	outputName += "_" + signature + ".csv";

	outputFile.open(outputName.c_str());
	if(!outputFile.is_open()) {
		std::string err = "It was impossible to generate the output ";
		err += "files. They may be opened or the logFolder does ";
		err += "not exist";
		throw ClbException(err);
	}

	algorithm->printSetupTree(outputFile);
	outputFile << std::endl;
	this->printRuntimes(outputFile);
	outputFile << std::endl;
	this->printStatistics(outputFile);
	outputFile << std::endl;
	this->printEvolutionTrace(outputFile);

	outputFile.close();

	return this->formatSolution(bestSolution);
}





//=============================================================================
//		CONFIGURATION METHODS
//=============================================================================
//-----  loadConfiguration method  --------------------------------------------
void Calibrator::loadConfiguration(const char* clbSetup,
		const char* algorithmSetup) {
	this->calibratorSetup = new ParameterDB(clbSetup);

	// Read the seed
	this->seed = this->calibratorSetup->getInteger(CLB_SEED, time(NULL));
	if(this->seed < 0) {
		throw ClbException("The seed must be a positive value");
	}

	// Read the number of runs for the algorithm
	this->numRuns = this->calibratorSetup->getInteger(CLB_RUNS, 1);
	if(this->numRuns <= 0) {
		std::string err = "The number of runs is not valid. It must be a ";
		err += "positive value";
		throw ClbException(err);
	}

	// Read and create the path to the log folder
	this->logFolder = this->calibratorSetup->getString(CLB_LOG_FOLDER);
	if(this->logFolder.length() == 0) {
		std::string err = "The path to the log folder has not been found";
		throw ClbException(err);
	}
	makeDir(this->logFolder.c_str());

	// Read the parameters to tune from the file
	std::string paramsFile = this->calibratorSetup->getString(CLB_PARAM_PATH);
	if(paramsFile.length() == 0) {
		std::string err = "The path to the file of parameters has not been found";
		throw ClbException(err);
	}
	this->readParameters(paramsFile.c_str());



	// Read the name of the algorithm to use
	std::string algorithmName = this->calibratorSetup->getString(CLB_ALGORITHM);
	if(algorithmName.length() == 0) {
		std::string err = "The name of the algorithm to use has not been found";
		throw ClbException(err);
	}

	this->setupAlgorithm(algorithmName, algorithmSetup);
}



//-----  readParameters method  -----------------------------------------------
void Calibrator::readParameters(const char* paramsFile) {
	std::ifstream inputFile;
	inputFile.open(paramsFile);

	// Read the data from the parameters file
	if(!inputFile.is_open()) {
		std::string err = "The parameters file has not been found";
		throw ClbException(err);
	}

	std::string line;
	std::vector< std::string > fields;

	// Omit headings
	getline(inputFile, line);
	while(!inputFile.eof()) {
		getline(inputFile, line);
		fields.clear();
		fields = fieldsFromCSV(line);

		if(fields.size() >= 4) {
			ParameterDefinition *param;
			param = new ParameterDefinition();
			param->setValues(atof(fields[2].c_str()), atof(fields[3].c_str()),
					atof(fields[1].c_str()));
			param->name = fields[0];
			this->paramDefinition.push_back(param);
		}

	}
	inputFile.close();
}



//-----  setupAlgorithm method  -----------------------------------------------
void Calibrator::setupAlgorithm(const std::string name,
		const char* setupFile) {
	// Niching Genetic algorithm
	if(toUpper(name).compare("NGA") == 0)
		this->algorithm = new NicheGenetic(setupFile);
	else {
		std::string err = "Calibration algorithm \'" + name + "\' unknown";
		throw ClbException(err);
	}

	// Set parameters for the algorithm
	this->algorithm->sharedVariables->parameters->setParameter(
			MMA_INDIVIDUAL_LENGTH,
			valueToString(this->paramDefinition.size()));

	for(unsigned int i=0; i < paramDefinition.size(); i++) {
		this->algorithm->sharedVariables->parameters->setParameter(
			MMA_BOUNDS_MIN + valueToString(i),
			valueToString(this->paramDefinition[i]->getMinValue()));
		this->algorithm->sharedVariables->parameters->setParameter(
			MMA_BOUNDS_MAX + valueToString(i),
			valueToString(this->paramDefinition[i]->getMaxValue()));
	}

	// Set the evaluation function
	this->algorithm->sharedVariables->parameters->setParameter(
			EVAL_FUNCTION, "deb");

}



//-----  generateSignature method  --------------------------------------------
std::string Calibrator::generateSignature() const {
	time_t now;
	time(&now);
	struct tm * timeinfo = localtime(&now);

	int bufferSize = 80;
	char buffer[bufferSize];
	strftime(buffer, bufferSize, "%Y%m%d%H%M%S",timeinfo);
	return std::string(buffer);
}





//=============================================================================
//		LOG METHODS
//=============================================================================
//-----  formatSolution method  -----------------------------------------------
std::vector< std::pair<std::string, double> >
	Calibrator::formatSolution(Solution *solution) const {

	std::vector< std::pair<std::string, double> > formatedSolution;
	std::pair<std::string, double> parameterValue;

	std::vector<double> array = solution->toArray();
	for(unsigned int i=0; i < array.size(); i++) {
		parameterValue.first = this->paramDefinition[i]->name;
		parameterValue.second =
				this->paramDefinition[i]->getOriginalValue(array[i]);
		formatedSolution.push_back(parameterValue);
	}

	return formatedSolution;
}



//-----  printRuntimes method  ------------------------------------------------
void Calibrator::printRuntimes(std::ofstream &outputFile) {
	if(this->avgTimes.size() <= 0) return;
	std::string separator = "------------------------------------------------";
	outputFile << std::endl << "Runtimes" << std::endl;
	outputFile << separator << std::endl;

	outputFile << "Time;Average;Best;Worst";
	for(unsigned int i=0; i < avgTimes.size(); i++) {
		outputFile << ";Run " << i+1;
	}
	outputFile << std::endl;

	std::vector<double> bestValues(avgTimes[0].size(), Infd);
	std::vector<double> worstValues(avgTimes[0].size(), -Infd);
	std::vector<double> avgValues(avgTimes[0].size(), 0.0);

	for(unsigned int i=0; i < avgTimes[0].size(); i++) {
		for(unsigned int j=0; j < avgTimes.size(); j++) {
			if(avgTimes[j][i].second < bestValues[i])
				bestValues[i] = avgTimes[j][i].second;
			if(avgTimes[j][i].second > worstValues[i])
				worstValues[i] = avgTimes[j][i].second;
			avgValues[i] += avgTimes[j][i].second;
		}
		avgValues[i] /= avgTimes.size();
	}

	for(unsigned int i=0; i < avgTimes[0].size(); i++) {
		outputFile << avgTimes[0][i].first;
		outputFile << ";" << avgValues[i];
		outputFile << ";" << bestValues[i];
		outputFile << ";" << worstValues[i];
		for(unsigned int j=0; j < avgTimes.size(); j++)
			outputFile << ";" << avgTimes[j][i].second;
		outputFile << std::endl;
	}
}



//-----  printStatistics method  ----------------------------------------------
void Calibrator::printStatistics(std::ofstream &outputFile) {
	if(this->avgStats.size() <= 0) return;
	std::string separator = "------------------------------------------------";
	outputFile << std::endl << "Basic statistics" << std::endl;
	outputFile << separator << std::endl;

	outputFile << "Field;Average;Best;Worst";
	for(unsigned int i=0; i < avgStats.size(); i++) {
		outputFile << ";Run " << i+1;
	}
	outputFile << std::endl;

	std::vector<double> bestValues(avgStats[0].size(), Infd);
	std::vector<double> worstValues(avgStats[0].size(), -Infd);
	std::vector<double> avgValues(avgStats[0].size(), 0.0);

	for(unsigned int i=0; i < avgStats[0].size(); i++) {
		for(unsigned int j=0; j < avgStats.size(); j++) {
			if(avgStats[j][i].second < bestValues[i])
				bestValues[i] = avgStats[j][i].second;
			if(avgStats[j][i].second > worstValues[i])
				worstValues[i] = avgStats[j][i].second;
			avgValues[i] += avgStats[j][i].second;
		}
		avgValues[i] /= avgStats.size();
	}

	outputFile << avgStats[0][0].first;
	outputFile << ";" << avgValues[0];
	outputFile << ";" << bestValues[0];
	outputFile << ";" << worstValues[0];
	for(unsigned int j=0; j < avgStats.size(); j++)
		outputFile << ";" << avgStats[j][0].second;
	outputFile << std::endl;

	for(unsigned int i=1; i < avgStats[0].size(); i++) {
		outputFile << avgStats[0][i].first;
		outputFile << ";" << avgValues[i];
		outputFile << ";" << bestValues[i];
		outputFile << ";" << worstValues[i];
		for(unsigned int j=0; j < avgStats.size(); j++)
			outputFile << ";" << avgStats[j][i].second;
		outputFile << std::endl;
	}
}


//-----  printStatistics method  ----------------------------------------------
void Calibrator::printEvolutionTrace(std::ofstream &outputFile) {
	if(this->avgEvolStats.size() <= 0) return;
	std::string separator = "------------------------------------------------";
	outputFile << std::endl << "Evolution" << std::endl;
	outputFile << separator << std::endl;

	outputFile << "Average Evolution";
	for(unsigned int i=0; i < avgEvolStats.size(); i++) {
		for(unsigned int j=0; j < avgEvolStats[i][0].size()-1; j++)
			outputFile << ";";
		outputFile << ";Run " << i;
	}
	outputFile << std::endl << "Step";

	for(unsigned int i=0; i <= avgEvolStats.size(); i++) {
		for(unsigned int j=0; j < avgEvolLabels.size(); j++)
			if(i > 0 || j > 0)
				outputFile << ";" << avgEvolLabels[j];
	}
	outputFile << std::endl;

	std::vector< std::vector<double> > avgValues(avgEvolStats[0].size());

	for(unsigned int i=0; i < avgEvolStats[0].size(); i++) {
		avgValues[i].resize(avgEvolStats[0][i].size(), 0.0);
		for(unsigned int j=0; j < avgEvolStats[0][i].size(); j++) {
			for(unsigned int k=0; k < avgEvolStats.size(); k++) {
				avgValues[i][j] += avgEvolStats[k][i][j];
			}
			avgValues[i][j] /= avgEvolStats.size();
		}
	}

	for(unsigned int i=0; i < avgEvolStats[0].size(); i++) {
		outputFile << avgEvolStats[0][i][0];

		for(unsigned int j=1; j < avgEvolStats[0][i].size(); j++) {
			outputFile << ";" << avgValues[i][j];
		}
		for(unsigned int k=0; k < avgEvolStats.size(); k++) {
			for(unsigned int j=0; j < avgEvolStats[0][i].size(); j++) {
				outputFile << ";" << avgEvolStats[k][i][j];
			}
		}
		outputFile << std::endl;
	}
}


//-----  printSetData method  -------------------------------------------------
void Calibrator::printSetData(std::ofstream &outputFile, Results *solutionSet)
const {
	double bestFitness, worstFitness, avgFitness;

	// Compute values to show
	bestFitness = solutionSet->getBest(0,
			this->algorithm->sharedVariables)->fitness;
	worstFitness = solutionSet->getBest(solutionSet->size()-1,
			this->algorithm->sharedVariables)->fitness;
	avgFitness = solutionSet->getAverage();

	double minDistance = Infd;
	double maxDistance = -Infd;
	double avgDistance = 0.0;
	int count = 0;
	for(int i=0; i < solutionSet->size(); i++) {
		for(int j=i+1; j < solutionSet->size(); j++) {
			double distance = euclideanDistance(
					this->algorithm->sharedVariables,
					solutionSet->individual(i), solutionSet->individual(j));
			if(distance < minDistance) minDistance = distance;
			if(distance > maxDistance) maxDistance = distance;
			avgDistance += distance;
			count++;
		}
	}
	avgDistance /= count;


	// Print information about the set of solutions
	outputFile << "Fitness:;;;Distance;;;Num." << std::endl;
	outputFile << "Avg;Best;Worst;Avg;Best;Worst;Solutions" << std::endl;

	outputFile << avgFitness << ";" << bestFitness << ";" << worstFitness << ";";
	if(solutionSet->size() > 1)
		outputFile << avgDistance << ";" << minDistance << ";" << maxDistance << ";";
	else
		outputFile << "0.0;0.0;0.0;";
	outputFile << valueToString(solutionSet->size()) << std::endl;

	// Print the set of solutions
	outputFile << std::endl << "Set of solutions" << std::endl;
	std::vector< std::pair<std::string, double> > formatted;
	outputFile << "Fitness";
	for(unsigned int i=0; i < this->paramDefinition.size(); i++)
		outputFile << ";" << this->paramDefinition[i]->name;
	outputFile << std::endl;
	for(int i=0; i < solutionSet->size(); i++) {
		formatted = this->formatSolution(solutionSet->getBest(i, NULL));
		outputFile << solutionSet->getBest(i, NULL)->fitness;
		for(int j=0; j < (int)formatted.size(); j++)
			outputFile << ";" << formatted[j].second;
		outputFile << std::endl;
	}
}


}
