
//#include "usingneuralnet.h"
#include "usingpca.h"
#include <omp.h>
#include "pca/TrainPCA.h"
#include <KLib/misc/gnuplot/Gnuplot.h>
#include <KLib/misc/gnuplot/GnuplotSplot.h>
#include <KLib/misc/gnuplot/GnuplotSplotElementLines.h>

#include "pca/KNN.h"
#include "pca/aKNN.h"

#include <vector>

std::vector<std::string> COLORS = {"#000000", "#0000ff", "#00ff00", "#ff0000", "#00ffff"};

std::string getClass(const std::vector<ClassifiedFeature>& nns) {
	std::unordered_map<std::string, int> map;
	for(const ClassifiedFeature& nn : nns) { map[nn.className] += 1; }
	for (auto& it : map) {
		if (it.second > nns.size() * 0.75) {return it.first;}
	}
	return "";
}

struct ClassStats {
	int counts[6] = {};
};

struct Stats{
	int match;
	int error;
	int unknown;
	Stats() : match(0), error(0), unknown(0) {;}
	float getSum() {return match+error+unknown;}
};



std::vector<ClassifiedPattern> removePatterns(const std::vector<ClassifiedPattern>& patAll, const std::string& fileName) {
	std::vector<ClassifiedPattern> res;
	for (const ClassifiedPattern& pat : patAll) {
		if (pat.belongsToFile(fileName)) {
			continue;
		} else {
			res.push_back(pat);
		}
	}
	return res;
}

template <int numFeatures> struct PCA {

	aKNN<ClassifiedFeature, numFeatures> knn;
	TrainPCA::Matrices m;

};

class Plot {

	K::Gnuplot gp;
	K::GnuplotSplot splot;
	K::GnuplotSplotElementLines lines[5];


public:

	Plot() {
		for (int i = 0; i < 5; ++i) {lines[i].setColorHex(COLORS[i]);}
		for (int i = 0; i < 5; ++i) {splot.add(&lines[i]);}
	}

	void add(int idx, std::vector<float>& vec) {
		K::GnuplotPoint3 p3(vec[0], vec[1], vec[2]);
		lines[idx].add(p3);
	}

	void clear() {
		for (int i = 0; i < 5; ++i) {lines[i].clear();}
	}

	void show() {
		gp.setDebugOutput(false);
		gp.draw(splot);
		gp.flush();
	}

};

int main(void) {

	omp_set_dynamic(false);
	omp_set_num_threads(3);

	const int numFeatures = 10;

	TrainPCA::Settings setTrain;
	TrainPCA::Settings setClass; setClass.regionStart_ms += 25;

	Data::getAllDataFiles();
	Plot p;

	// convert all provided datasets into patterns
	std::vector<ClassifiedPattern> srcTrain = TrainPCA::getAllData(setTrain);
	std::vector<ClassifiedPattern> srcClass = TrainPCA::getAllData(setClass);
	std::cout << "windows: " << srcTrain.size() << std::endl;

	// error calculation
	std::unordered_map<std::string, Stats> stats;
	std::unordered_map<std::string, ClassStats> classStats;
	//int xx = 0;
	std::unordered_map<std::string, PCA<numFeatures>*> pcas;

	// try to classify each pattern
	for (const ClassifiedPattern& patClassify : srcClass) {

		// construct knn search for this leave-one-out ONLY ONCE
		if (pcas.find(patClassify.fileName) == pcas.end()) {

			std::cout << "constructing PCA for all files but " << patClassify.fileName << std::endl;

			// remove all training patterns belonging to the same source file as the to be classifed pattern
			std::vector<ClassifiedPattern> srcTrainLOO = removePatterns(srcTrain, patClassify.fileName);

			// sanity check (have we removed all patterns?)
			int diff =  srcTrain.size() - srcTrainLOO.size();
			if (diff < 200) {throw 1;}

			p.clear();
			PCA<numFeatures>* pca = new PCA<numFeatures>();
			pcas[patClassify.fileName] = pca;

			// train PCA using all pattern without those belonging to the same source file as the to-be-classified one
			pca->m = TrainPCA::getMatrices(srcTrainLOO, numFeatures);

			// calculate features and add them to the KNN
			for (const ClassifiedPattern& pat : srcTrainLOO) {
				K::DynColVector<float> vec = pca->m.A1 * K::PCAHelper<float>::toVector(pat.pattern);
				std::vector<float> arr;
				for (int i = 0; i < numFeatures; ++i) {arr.push_back(vec(i));}
				pca->knn.add(ClassifiedFeature(pat.className, arr));

				const int idx = Settings::classToInt(pat.className);
				p.add(idx, arr);

			}
			pca->knn.build();
			//p.show();
			//sleep(100);

		}

		{

			PCA<numFeatures>* pca = pcas[patClassify.fileName];

			// calculate features for the to-be-classified pattern
			//const int idx = Settings::classToInt(pat.className);
			K::DynColVector<float> vec = pca->m.A1 * K::PCAHelper<float>::toVector(patClassify.pattern);

			// get KNN's answer
			std::vector<float> arr;
			for (int i = 0; i < numFeatures; ++i) {arr.push_back(vec(i));}
			std::vector<ClassifiedFeature> neighbors = pca->knn.get(arr.data(), 5);
			std::string gotClass = getClass(neighbors);

			if		(patClassify.className == gotClass) {stats["all"].match++;		stats[patClassify.fileName].match++;	stats[patClassify.className].match++;}
			else if	(gotClass == "")					{stats["all"].unknown++;	stats[patClassify.fileName].unknown++;	stats[patClassify.className].unknown++;}
			else										{stats["all"].error++;		stats[patClassify.fileName].error++;	stats[patClassify.className].error++;}

			int gotIdx = (gotClass == "") ? (5) : Settings::classToInt(gotClass);
			++classStats[patClassify.className].counts[gotIdx];

		}


	}

	for (auto& it : stats) {
		Stats& stats = it.second;
		std::cout << "'" <<it.first << "',";
		std::cout << stats.match/stats.getSum() << ",";
		std::cout << stats.error/stats.getSum() << ",";
		std::cout << stats.unknown/stats.getSum();
		std::cout << std::endl;
	}

	for (auto& it : classStats) {
		ClassStats& stats = it.second;
		std::cout << "'" << it.first << "',";
		for (int i = 0; i < 6; ++i) {
			std::cout << stats.counts[i] << ",";
		}
		std::cout << std::endl;
	}


	/*
	std::vector<ClassifiedPattern> patTrain = TrainPCA::getTrainData();
	TrainPCA::Matrices m = TrainPCA::getMatrices(patTrain, numFeatures);

	std::vector<ClassifiedPattern> patTest = TrainPCA::getTestData();

	// construct knn
	aKNN<ClassifiedFeature, numFeatures> knn;
	for (const ClassifiedPattern& pat : patTrain) {
		K::DynColVector<float> vec = m.A1 * K::PCAHelper<float>::toVector(pat.pattern);
		std::vector<float> arr;
		for (int i = 0; i < numFeatures; ++i) {arr.push_back(vec(i));}
		knn.add(ClassifiedFeature(pat.className, arr));
	}
	knn.build();

	K::Gnuplot gp;
	K::GnuplotSplot splot;
	K::GnuplotSplotElementLines lines[5];


	Stats stats;
	int xx = 0;
	for (const ClassifiedPattern& pat : patTest) {

		const int idx = Settings::classToInt(pat.className);
		K::DynColVector<float> vec = m.A1 * K::PCAHelper<float>::toVector(pat.pattern);

		// get KNN's answer
		std::vector<float> arr;
		for (int i = 0; i < numFeatures; ++i) {arr.push_back(vec(i));}
		std::vector<ClassifiedFeature> neighbors = knn.get(arr.data(), 10);
		std::string gotClass = getClass(neighbors);

		if		(pat.className == gotClass) {stats.match++;}
		else if	(gotClass == "")			{stats.unknown++;}
		else								{stats.error++;}

		if (++xx % 16 == 0) {
			std::cout << pat.className << " -> " << gotClass << std::endl;
			std::cout << stats.getSum() << ":" << stats.match << ":" << stats.error << ":" << stats.unknown << std::endl;
			std::cout << stats.match/stats.getSum() << ":" << stats.error/stats.getSum() << ":" << stats.unknown/stats.getSum() << std::endl;
		}

		// plot
		K::GnuplotPoint3 p3(vec(0), vec(1), vec(2));
		lines[idx].add(p3);

	}

	for (int i = 0; i < 5; ++i) {lines[i].setColorHex(COLORS[i]);}
	for (int i = 0; i < 5; ++i) {splot.add(&lines[i]);}
	gp.setDebugOutput(false);
	gp.draw(splot);
	gp.flush();

	sleep(10000);

*/





//	std::vector<std::vector<float>> vecs = {vec1, vec2};
//	std::cout << K::PCAHelper<float>::getR(vecs) << std::endl;
//	std::cout << K::PCAHelper<float>::getM(vecs) << std::endl;

//	K::PCAHelper<float>::R r;
//	r.add(vec1); r.add(vec2); std::cout << r.get() << std::endl;


//	Eigen::Vector3f v1; v1 << 1,2,3;
//	Eigen::Vector3f v2; v2 << 3,4,5;
//	std::vector<Eigen::Vector3f> vecs2 = {v1, v2};
//	std::cout << K::PCAHelper<float>::getR(vecs2) << std::endl;
//	std::cout << K::PCAHelper<float>::getM(vecs2) << std::endl;

//	UsingNeuralNet::run();

	//UsingPCA::run();



//	UsingNeuralNet::debugPlot(
//				Practice {
//					PracticeType::KNEEBEND,
//					SensorReader::read("/mnt/firma/kunden/HandyGames/daten/kneebend/kneebend_gl_0_subject_0_right.txt"),
//					{2650, 4750, 6750, 8800, 10800, 12800}
//					//{3500, 5000, 8300, 9900, 11550}
//				}

//	);


	//sleep(1000);

}
