#include "stdafx.h"

#include <random>
#include "entry.h"
#include "external.h"
#include "parameter.h"
#include "parser.h"
#include "print.h"
#include "operation.h"
#include "dtw.h"
#include "lcss.h"
#include "pdtw.h"
#include "help.h"
#include "calcul.h"
#include "draw.h"
#include "preprocess.h"

using namespace std;

///Main entry point of the application.
///@param[in] args application arguments
void entry::entryBase(vtr<string> const &args)
{
	if (args.size() < 1)
	{
		cout << "error: Invalid number of arguments." << endl;
		exit(0);
	}

	auto script = parser::parseScript(args);
	parameter params;
	
	if (script.size() == 0)
	{
		params.setParameters(args);
		entryLogic(params);
	}
	else
	{
		for (size_t i = 0; i < script.size(); i++)
		{
			params.setParameters(params.applyParameter(script[i], args));
			entryLogic(params);
		}
	}
}

///Main logic function of the application.
///@param[in] params parameters
///@param[in] unit if true: called from unit test application
///@return operation results
resultOperation entry::entryLogic(parameter const &params, bool unit)
{
	resultTime times;
	auto begin = chrono::steady_clock::now();
	auto data = parseData(params);
	times.parsing = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - begin).count();

	begin = chrono::steady_clock::now();
	preprocessData(data, params);
	times.preprocesing = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - begin).count();

	if (params.debugInfo)
	{
		cout << print::input(data.input, 30) << endl; // print for debug comment or delete if not needed
		cout << print::input(data.query, 30) << endl; // print for debug comment or delete if not needed
	}

	begin = chrono::steady_clock::now();
	auto result = runOperation(data, params);
	times.opeartion = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - begin).count();
	
	result.time = times;

	if(!unit)
	{
		printResult(result, params);
		writeResult(data, result, times, params);
	}

	return result;
}

///Loads input data.
///@param[in] params parameters
///@return input data
inputData entry::parseData(parameter const &params)
{
	inputData data;

	try {
		data.files.input = parser::getAllFileNames(params.inPath);				//file path of all input files
		data.files.query = parser::getAllFileNames(params.inQuery);				//file path of all input files
		data.files.keyInput = parser::getAllFileNames(params.inKeyInput);		//file path of all input files
		data.files.keyQuery = parser::getAllFileNames(params.inKeyQuery);		//file path of all input files
		data.files.sort();

		data.input = parser::readData<double>(data.files.input);				//parsing
		if (data.input.size() < 1)
			throw runtime_error("problem occurred when loading input data");

		if (params.distance == 4)
			data.keyInput = parser::readData<int>(data.files.keyInput);

		if (params.isQuery())
		{
			data.query = parser::readData<double>(data.files.query);			//parsing
			if (data.query.size() < 1)
				throw runtime_error("problem occurred when loading query data");

			if (params.distance == 4)
				data.keyQuery = parser::readData<int>(data.files.keyQuery);
		}

		if (params.isGroundTruth())	//parsing clusters file 
		{
			auto clusterTmp = parser::parseGroundTruth(data.files.query, params.inGroundTruthPath);
			data.clusters = parser::parseGroundTruth(data.files.input, params.inGroundTruthPath);

			int clusterSize = (int)data.clusters.ids.size();
			for (size_t i = 0; i < clusterTmp.ids.size(); i++)
			{
				data.clusters.ids[(int)i + clusterSize + 1] = clusterTmp.ids.at((int)i + 1);
			}
		}
	}
	catch (const std::exception &e)
	{
		cout << e.what() << endl;
		exit(0);
	}

	if (params.debugInfo)
	{
		cout << print::vector(data.files.query);
		cout << print::vector(data.files.input);

		cout << print::input(data.query, 30) << endl;			//print for debug comment or delete if not needed
		cout << print::input(data.input, 30) << endl;			//print for debug comment or delete if not needed
		cout << print::inputStats(data.input, 30);
		cout << print::inputStats(data.query, 30);
	}

	return data;
}

///Executes chosen operation.
///@param[in] data input data
///@param[in] params parameters
///@return operation results
resultOperation entry::runOperation(inputData const &data, parameter const &params)
{
	resultOperation result;

	if (data.isQuery() && !params.forceSingle)
	{
		result = operation::operationDual(data, params);
	}
	else
	{
		auto single = data.convertInputDataSingle();
		result = operation::operationSingle(single, params);
	}

	return result;
}

///Main logic for external calls. EXPERIMENT
///@param[in] input input data
///@param[in] args application arguments
///@return operation results
resultOperation entry::externLogic(vtr3<double> const &input, vtr<string> const &args)
{
	parameter params;
	params.setParameters(args);

	//INPUT DATA PARSING
	inputData data;
	resultTime times;

	data.input = input;

	vtr<string> inputFiles(input.size());
	for (size_t i = 0; i < input.size(); i++)
	{
		inputFiles[i] = "ext" + to_string((int)i + 1);
	}
	data.files.input = inputFiles;

	auto begin = chrono::steady_clock::now();
	preprocessData(data, params);
	times.preprocesing = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - begin).count();

	if (params.debugInfo)
		cout << print::input(data.input, 30) << endl; // print for debug comment or delete if not needed

	begin = chrono::steady_clock::now();
	auto result = runOperation(data, params);
	times.opeartion = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - begin).count();

	result.time = times;

	printResult(result, params);
	writeResult(data, result, times, params);

	return result;
}

///Pre-processes input data (depends on used switches).
///@param[in] data input data
///@param[in] params parameters
void entry::preprocessData(inputData &data, parameter const& params)
{
	//DATA PREPROCESSING
	
	if (params.isReduce()) 
	{
		preprocess::reduce<double>(data.input, params.preReduce);
		preprocess::reduce<double>(data.query, params.preReduce);
		preprocess::reduce<int>(data.keyInput, params.preReduce);
		preprocess::reduce<int>(data.keyQuery, params.preReduce);
	}

	if (params.isPaa())
	{
		preprocess::paa<double>(data.input, params.prePaa);
		preprocess::paa<double>(data.query, params.prePaa);
		preprocess::paa<int>(data.keyInput, params.prePaa);
		preprocess::paa<int>(data.keyQuery, params.prePaa);
	}
	
	if (params.isSax())
	{
		preprocess::sax(data.input, params.preSax);
		preprocess::sax(data.query, params.preSax);
	}
	
	if (params.preProlong > 0)
	{
		preprocess::prolong<double>(data.input, params.preProlong);
		preprocess::prolong<double>(data.query, params.preProlong);
		preprocess::prolong<int>(data.keyInput, params.preProlong);
		preprocess::prolong<int>(data.keyQuery, params.preProlong);
	}

	if (params.preInterpolate) 
	{
		preprocess::interpolate(data.input);
		preprocess::interpolate(data.query);
	}

	if (params.isNormalizeBy()) 
	{
		preprocess::normalizeBy(data.input, params.preNormalizeBy);
		preprocess::normalizeBy(data.query, params.preNormalizeBy);
	}

	if (params.preSmooth)
	{
		preprocess::smooth(data.input, params.preSmooth);
		preprocess::smooth(data.query, params.preSmooth);
	}
}

///Prints final results to the console.
///@param[in] result operation results
///@param[in] params parameters
void entry::printResult(resultOperation &result, parameter const &params)
{
	if (!result.dtw.score.empty())
		cout << print::vector(result.dtw.score);

	if (params.printOutput)
	{
		if (!result.matrixSimilarity.empty())
			cout << print::matrix(result.matrixSimilarity[params.scoreType], params);
		
		if (!result.matrixCluster.empty())
			cout << print::matrix(result.matrixCluster[params.scoreType], params);
	}

	if (params.operation == 3)
		cout << print::scoresClustering(result, params.precision);

	if (params.time)
		cout << print::timeMeasures(result.time);
}

///Writes final results to the disk.
///@param[in] data input data
///@param[in] result operation results
///@param[in] times elapsed times measurements during various phases
///@param[in] params parameters
void entry::writeResult(inputData const &data, resultOperation &result, resultTime &times, parameter const &params)
{
	//WRITE LOG FILES
	if (params.isWriteOutput()) 
	{
		print::write(print::matrix(result.matrixSimilarity[params.scoreType], params), params.outputPath + ".matrix", false);
		print::write(print::scoresClustering(result, params.precision), params.outputPath + ".score", false);
	}

	if (params.outHtml)
	{
		print::write(
			/*print::printParameterString(args) + "</br>" +*/
			print::scoresClustering(result, params.precision) +
			print::htmlClusters(data.input, result.matrixCluster[0], data.clusters), params.outputPath + ".html", true);
	}

	if (params.outGdf)
		print::write(print::gdf(data.files.input, data.input, result.matrixSimilarity[params.scoreType], data.clusters), params.outputPath + ".gdf", false);

	if (params.isWriteOutput())
		print::write(print::args(params.arguments) + print::timeMeasures(times), params.outputPath + ".info", false);
}

///Contains predefined constat data structures.
namespace cstruct
{
	///Contains 15 basic RGB colors.
	extern const float colorsBase[15][3] = {
		{ 0, 255, 0 },				//green
		{ 190, 190, 0 },			//darker yellow
		{ 255, 0, 0 },				//red
		{ 180, 180, 180 },			//gray
		{ 255, 170, 200 },			//pink
		{ 255, 128, 0 },			//orange
		{ 255, 255, 255 },			//white
		{ 0, 128, 255 },			//lighter blue
		{ 128, 255, 0 },			//lime
		{ 128, 64, 0 },				//brown
		{ 255, 255, 0 },			//yellow
		{ 0, 0, 255 },				//blue
		{ 0, 255, 255 },			//teal (light blue)
		{ 255, 0, 255 },			//purple
		{ 0, 155, 155 }				//darker teal
	};

	///Contains hex codes for colors.
	extern const vtr<std::string> colorsMass = {
		"000000", "FFFF00", "1CE6FF", "FF34FF", "FF4A46", "008941", "006FA6", "A30059",
		"FFDBE5", "7A4900", "0000A6", "63FFAC", "B79762", "004D43", "8FB0FF", "997D87",
		"5A0007", "809693", "FEFFE6", "1B4400", "4FC601", "3B5DFF", "4A3B53", "FF2F80",
		"61615A", "BA0900", "6B7900", "00C2A0", "FFAA92", "FF90C9", "B903AA", "D16100",
		"DDEFFF", "000035", "7B4F4B", "A1C299", "300018", "0AA6D8", "013349", "00846F",
		"372101", "FFB500", "C2FFED", "A079BF", "CC0744", "C0B9B2", "C2FF99", "001E09",
		"00489C", "6F0062", "0CBD66", "EEC3FF", "456D75", "B77B68", "7A87A1", "788D66",
		"885578", "FAD09F", "FF8A9A", "D157A0", "BEC459", "456648", "0086ED", "886F4C",
		"34362D", "B4A8BD", "00A6AA", "452C2C", "636375", "A3C8C9", "FF913F", "938A81",
		"575329", "00FECF", "B05B6F", "8CD0FF", "3B9700", "04F757", "C8A1A1", "1E6E00",
		"7900D7", "A77500", "6367A9", "A05837", "6B002C", "772600", "D790FF", "9B9700",
		"549E79", "FFF69F", "201625", "72418F", "BC23FF", "99ADC0", "3A2465", "922329",
		"5B4534", "FDE8DC", "404E55", "0089A3", "CB7E98", "A4E804", "324E72", "6A3A4C",
		"83AB58", "001C1E", "D1F7CE", "004B28", "C8D0F6", "A3A489", "806C66", "222800",
		"BF5650", "E83000", "66796D", "DA007C", "FF1A59", "8ADBB4", "1E0200", "5B4E51",
		"C895C5", "320033", "FF6832", "66E1D3", "CFCDAC", "D0AC94", "7ED379", "012C58",
		"7A7BFF", "D68E01", "353339", "78AFA1", "FEB2C6", "75797C", "837393", "943A4D",
		"B5F4FF", "D2DCD5", "9556BD", "6A714A", "001325", "02525F", "0AA3F7", "E98176",
		"DBD5DD", "5EBCD1", "3D4F44", "7E6405", "02684E", "962B75", "8D8546", "9695C5",
		"E773CE", "D86A78", "3E89BE", "CA834E", "518A87", "5B113C", "55813B", "E704C4",
		"00005F", "A97399", "4B8160", "59738A", "FF5DA7", "F7C9BF", "643127", "513A01",
		"6B94AA", "51A058", "A45B02", "1D1702", "E20027", "E7AB63", "4C6001", "9C6966",
		"64547B", "97979E", "006A66", "391406", "F4D749", "0045D2", "006C31", "DDB6D0",
		"7C6571", "9FB2A4", "00D891", "15A08A", "BC65E9", "FFFFFE", "C6DC99", "203B3C",
		"671190", "6B3A64", "F5E1FF", "FFA0F2", "CCAA35", "374527", "8BB400", "797868",
		"C6005A", "3B000A", "C86240", "29607C", "402334", "7D5A44", "CCB87C", "B88183",
		"AA5199", "B5D6C3", "A38469", "9F94F0", "A74571", "B894A6", "71BB8C", "00B433",
		"789EC9", "6D80BA", "953F00", "5EFF03", "E4FFFC", "1BE177", "BCB1E5", "76912F"
	};

	///Contains chord scale (used in the CSI chroma distance, switch: -dist 3).
	extern const vtr2<bool> scaleChord {
		{ 1,0,0,0,1,0,0,1,0,0,0,0 },	//12 major triad scale vectors
		{ 0,1,0,0,0,1,0,0,1,0,0,0 },
		{ 0,0,1,0,0,0,1,0,0,1,0,0 },
		{ 0,0,0,1,0,0,0,1,0,0,1,0 },
		{ 0,0,0,0,1,0,0,0,1,0,0,1 },
		{ 1,0,0,0,0,1,0,0,0,1,0,0 },
		{ 0,1,0,0,0,0,1,0,0,0,1,0 },
		{ 0,0,1,0,0,0,0,1,0,0,0,1 },
		{ 1,0,0,1,0,0,0,0,1,0,0,0 },
		{ 0,1,0,0,1,0,0,0,0,1,0,0 },
		{ 0,0,1,0,0,1,0,0,0,0,1,0 },
		{ 0,0,0,1,0,0,1,0,0,0,0,1 },

		{ 1,0,0,1,0,0,0,1,0,0,0,0 },	//12 minor triad scale vectors
		{ 0,1,0,0,1,0,0,0,1,0,0,0 },
		{ 0,0,1,0,0,1,0,0,0,1,0,0 },
		{ 0,0,0,1,0,0,1,0,0,0,1,0 },
		{ 0,0,0,0,1,0,0,1,0,0,0,1 },
		{ 1,0,0,0,0,1,0,0,1,0,0,0 },
		{ 0,1,0,0,0,0,1,0,0,1,0,0 },
		{ 0,0,1,0,0,0,0,1,0,0,1,0 },
		{ 0,0,0,1,0,0,0,0,1,0,0,1 },
		{ 1,0,0,0,1,0,0,0,0,1,0,0 },
		{ 0,1,0,0,0,1,0,0,0,0,1,0 },
		{ 0,0,1,0,0,0,1,0,0,0,0,1 },
	};

	///Contains key scale (used in the CSI chord distance, switch: -dist 4).
	extern const vtr2<bool> scaleKey {
		{ 1,0,1,0,1,1,0,1,0,1,0,1 },  //12 major triad scale vectors
		{ 1,1,0,1,0,1,1,0,1,0,1,0 },
		{ 0,1,1,0,1,0,1,1,0,1,0,1 },
		{ 1,0,1,1,0,1,0,1,1,0,1,0 },
		{ 0,1,0,1,1,0,1,0,1,1,0,1 },
		{ 1,0,1,0,1,1,0,1,0,1,1,0 },
		{ 0,1,0,1,0,1,1,0,1,0,1,1 },
		{ 1,0,1,0,1,0,1,1,0,1,0,1 },
		{ 1,1,0,1,0,1,0,1,1,0,1,0 },
		{ 0,1,1,0,1,0,1,0,1,1,0,1 },
		{ 1,0,1,1,0,1,0,1,0,1,1,0 },
		{ 0,1,0,1,1,0,1,0,1,0,1,1 },

		//{ 1,0,1,1,0,1,0,1,1,0,1,0 },  //12 minor triad scale vectors
		//{ 0,1,0,1,1,0,1,0,1,1,0,1 },
		//{ 1,0,1,0,1,1,0,1,0,1,1,0 },
		//{ 0,1,0,1,0,1,1,0,1,0,1,1 },
		//{ 1,0,1,0,1,0,1,1,0,1,0,1 },
		//{ 1,1,0,1,0,1,0,1,1,0,1,0 },
		//{ 0,1,1,0,1,0,1,0,1,1,0,1 },
		//{ 1,0,1,1,0,1,0,1,0,1,1,0 },
		//{ 0,1,0,1,1,0,1,0,1,0,1,1 },
		//{ 1,0,1,0,1,1,0,1,0,1,0,1 },
		//{ 1,1,0,1,0,1,1,0,1,0,1,0 },
		//{ 0,1,1,0,1,0,1,1,0,1,0,1 },
	};

	///Contains circle of fifth distances (used in the chord distance, switch: -dist 4). 
	extern const std::map<int, int> cofDistance = {
		{ 0,0 },
		{ 1,5 },
		{ 2,2 },
		{ 3,3 },
		{ 4,4 },
		{ 5,1 },
		{ 6,6 },
		{ 7,1 },
		{ 8,4 },
		{ 9,3 },
		{ 10,2 },
		{ 11,5 },
	};
}