#include <stdio.h>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <vector>
#include <string>
#include <exception>


#include "matrix.h"
#include "neighbors.h"
#include "transformers.h"
#include "cross_validation.h"
#include "optimization.h"
#include "pipeline.h"

#include "loop_classifier.h"
#include "dataset.h"
#include "experimental.h"

#include "cxml\cxml.h"



using namespace optimization;
using namespace common;


void test_set_perfomance(const char* testset_name,
					     const char* loop_clf_params_name,
						 const char* dataset_name) {
	try {
		Dataset testset(testset_name);
		LoopClassifierParameters loop_clf_params(loop_clf_params_name);
		Dataset dataset(dataset_name);
		LoopClassifier loop_clf(loop_clf_params);
		loop_clf.train(dataset);

		std::pair<Matrix<data_t>, vector<label_t>> testXy = testset.samples_to_Xy(testset.get_enabled_samples());
		vector<label_t> y_pred = loop_clf.predict(testXy.first);
		vector<label_t> y_true = testXy.second;
		std::cout << metrics::classification::make_report(y_true, y_pred);
	}
	catch (std::exception& e) {
		std::cout << e.what();
	}
}

void cross_validate(const char* loop_clf_params_name,
					const char* loop_clf_opt_params_name,
				    const char* dataset_name) {
	try {

		LoopClassifierParameters loop_clf_params(loop_clf_params_name);
		LoopClassifierOptParameters loop_clf_opt_params(loop_clf_opt_params_name);
		Dataset dataset(dataset_name);
		LoopClassifier loop_clf(loop_clf_params);
		LoopClassifierOpt loop_clf_opt(loop_clf_opt_params);
		loop_clf.train(dataset);
		std::cout << "validating...";
		auto cv_res = loop_clf_opt.cross_validate(dataset, loop_clf, { nullptr, nullptr, nullptr, nullptr });

		vector<label_t> y;
		vector<label_t> y_pred;
		for (vector<label_t> v : cv_res.test_y_true)
			y.insert(y.end(), v.begin(), v.end());
		for (vector<label_t> v : cv_res.test_y_pred)
			y_pred.insert(y_pred.end(), v.begin(), v.end());
		std::cout << "Cross validations results:\n";
		std::cout << metrics::classification::make_report(y, y_pred);
		std::cout << "Dataset class mapping\n:";
		std::cout << dataset.info();
		
	}
	catch (std::exception& e) {
		std::cout << e.what();
	}
}

void optimize(const char* loop_clf_params_name,
			  const char* loop_clf_opt_params_name,
			  const char* dataset_name) {
	try {
		LoopClassifierParameters loop_clf_params(loop_clf_params_name);
		LoopClassifierOptParameters loop_clf_opt_params(loop_clf_opt_params_name);
		Dataset dataset(dataset_name);
		LoopClassifier loop_clf(loop_clf_params);
		LoopClassifierOpt loop_clf_opt(loop_clf_opt_params);
		loop_clf.train(dataset);

		InteropNegotiator negotiator = { nullptr, nullptr, nullptr, nullptr };
		negotiator.message = [](void*, const char* msg) { std::cout << msg << std::endl; };
		std::cout << "optimizing weights..." << std::endl;
		std::cout << "nearest neighbor score with opimized weights: " 
			<< loop_clf_opt.optimize_weights(dataset, loop_clf, negotiator);
		std::cout << "optimizing parameters..." << std::endl;
		std::cout << "classifiers score after opimizing parameters: " 
			<< loop_clf_opt.optimize_params(dataset, loop_clf, negotiator);

		string save_path = string(loop_clf_params_name);
		save_path = save_path.substr(0, save_path.length() - 4) + "_optimized.xml";
		std::cout << "saving to ..." << save_path;
		loop_clf_params.save_to_xml(save_path);
	}
	catch (std::exception& e) {
		std::cout << e.what();
	}
}


void optimize_parameters(const char* loop_clf_params_name,
					     const char* loop_clf_opt_params_name,
						 const char* dataset_name) {
	try {
		LoopClassifierParameters loop_clf_params(loop_clf_params_name);
		LoopClassifierOptParameters loop_clf_opt_params(loop_clf_opt_params_name);
		Dataset dataset(dataset_name);
		LoopClassifier loop_clf(loop_clf_params);
		LoopClassifierOpt loop_clf_opt(loop_clf_opt_params);
		loop_clf.train(dataset);

		InteropNegotiator negotiator = { nullptr, nullptr, nullptr, nullptr };
		negotiator.message = [](void*, const char* msg) { std::cout << msg << std::endl; };
		std::cout << "optimizing parameters..." << std::endl;
		std::cout << "classifiers score after opimizing parameters: "
			<< loop_clf_opt.optimize_params(dataset, loop_clf, negotiator);

		string save_path = string(loop_clf_params_name);
		save_path = save_path.substr(0, save_path.length() - 4) + "_optimized.xml";
		std::cout << "saving to ...: " << save_path;
		loop_clf_params.save_to_xml(save_path);
	}
	catch (std::exception& e) {
		std::cout << e.what();
	}
}


int main(int argc, char* argv[]) {
	if (argc < 2) {
		std::cout << "wrong parameters, see help, cppmodelconsole --help" << std::endl;
		return -1;
	}
	if (std::string(argv[1]) == "--help") {
		std::cout << "CppModelConsole.exe --evaluate_testset test_name.xml loopclassifier_parameters.xml trainset_name.xml" << std::endl;
		std::cout << "CppModelConsole.exe --cross_validate loopclassifier_parameters.xml loopclassifieropt_parameters.xml trainset_name.xml" << std::endl;
		std::cout << "CppModelConsole.exe --optimize loopclassifier_parameters.xml loopclassifieropt_parameters.xml trainset_name.xml" << std::endl;
	}
	if (std::string(argv[1]) == "--evaluate_testset") {
		//CppModelConsole.exe --testset ..\\.\\XMLData\\test_set_loop_scheme.xml ..\\.\\XMLData\\loopclfparameters_local_logit.xml ..\\.\\XMLData\\trainset_v3_loop_scheme.xml
		if (argc != 5) {
			std::cout << "wrong parameters, see help" << std::endl;
			return -1;
		}
		std::cout << "elapsed time: "
			<< benchmark([=] {
			test_set_perfomance(argv[2], argv[3], argv[4]); }) << "s" << std::endl;
		return 0;
	}
	if (std::string(argv[1]) == "--cross_validate") {
		//CppModelConsole.exe --cross_validate ..\\.\\XMLData\\loopclfparameters_local_logit.xml ..\\.\\XMLData\\loopclfopt.xml ..\\.\\XMLData\\trainset_v3_loop_scheme.xml  
		if (argc != 5) {
			std::cout << "wrong parameters, see help" << std::endl;
			return -1;
		}
		std::cout << "elapsed time: "
			<< benchmark([=] {
			cross_validate(argv[2], argv[3], argv[4]); }) << "s" << std::endl;
		return 0;
	}
	if (std::string(argv[1]) == "--optimize") {
		//CppModelConsole.exe --optimize ..\\.\\XMLData\\loopclfparameters_local_logit.xml ..\\.\\XMLData\\loopclfopt.xml ..\\.\\XMLData\\trainset_v3_loop_scheme.xml  
		if (argc != 5) {
			std::cout << "wrong parameters, see help" << std::endl;
			return -1;
		}
		std::cout << "elapsed time: "
			<< benchmark([=] {
			optimize(argv[2], argv[3], argv[4]); }) << "s" << std::endl;
		return 0;
	}
	if (std::string(argv[1]) == "--optimize_parameters") {
		//CppModelConsole.exe --optimize_parameters ..\\.\\XMLData\\loopclfparameters_local_logit.xml ..\\.\\XMLData\\loopclfopt.xml ..\\.\\XMLData\\trainset_v3_loop_scheme.xml  
		if (argc != 5) {
			std::cout << "wrong parameters, see help" << std::endl;
			return -1;
		}
		std::cout << "elapsed time: "
			<< benchmark([=] {
			optimize_parameters(argv[2], argv[3], argv[4]); }) << "s" << std::endl;
		return 0;
	}
	return 0;
}