#include "loop_classifier.h"


#include <sstream> 
#include <cstdlib>
#include "common.h"
#include "dataset.h"



using std::to_string;
using namespace common;

#pragma region XML_NAMES

const string WeightsCDSettings::xml_root_name = "WeightsCDSettings";
const string WeightsCDSettings::xml_x_init_name = "x_init";
const string WeightsCDSettings::xml_x_grid_name = "x_grid";
const string WeightsCDSettings::xml_n_iter_name = "n_iter";
const string WeightsCDSettings::xml_random_order_name = "random_order";

const string LoopClassifierParameters::xml_root_name = "LoopClassifierParameters";
const string LoopClassifierParameters::xml_weights_name = "weights";
const string LoopClassifierParameters::xml_n_neighbors_name = "n_neighbors";
const string LoopClassifierParameters::xml_metric_name = "metric";
const string LoopClassifierParameters::xml_use_min_max_name = "use_min_max";
const string LoopClassifierParameters::xml_use_local_logit_name = "use_local_logit";
const string LoopClassifierParameters::xml_logit_eps_name = "logit_eps";
const string LoopClassifierParameters::xml_logit_C_name = "logit_C_";
const string LoopClassifierParameters::xml_dist_tf_name = "dist_tf";

const string LoopClassifierOptParameters::xml_root_name = "LoopClassifierOptParameters";
const string LoopClassifierOptParameters::xml_score_type_name = "score_type";
const string LoopClassifierOptParameters::xml_n_neighbors_vec_name = "n_neighbors_vec";
const string LoopClassifierOptParameters::xml_logit_C_vec_name = "logit_C_vec";
const string LoopClassifierOptParameters::xml_dist_tf_vec_name = "dist_tf_vec";

const std::string LoopClassifier::xml_root_name = "LoopClassifier";
const std::string LoopClassifierOpt::xml_root_name = "LoopClassifierOpt";
const std::string LooperClassifier::xml_root_name = "LooperClassifier";

#pragma endregion XML_NAMES

WeightsCDSettings::WeightsCDSettings(const vector<string>& features) 
		:CoordinateDescentSettings(features.size()), features_(features) { }

TCXMLnode*
WeightsCDSettings::to_node(void) const {
	vector<string> features_old = features_;
	if (x_init.size() != x_grid.size()) {
		throw std::invalid_argument("x_grid has wrong size");
	}
	if (features_.size() != x_init.size()) {
		throw std::invalid_argument("features and x_init size disagree");
	}
	TCXMLnode * rootnode = CxmlCreateNode(xml_root_name.c_str(), ntElement, NULL);
	CxmlAddChild(rootnode, CxmlCreateNode(xml_n_iter_name.c_str(), ntAttribute, std::to_string(n_iter).c_str()));
	CxmlAddChild(rootnode, CxmlCreateNode(xml_random_order_name.c_str(), ntAttribute, common::bool_to_str(random_order).c_str()));
	//x_init
	TCXMLnode * n;
	CxmlAddChild(rootnode, n = CxmlCreateNode(xml_x_init_name.c_str(), ntElement, NULL));
	for (size_t i = 0; i < x_init.size(); i++) {
		TCXMLnode * x;
		CxmlAddChild(n, x = CxmlCreateNode(features_[i].c_str(), ntElement, nullptr));
		CxmlAddChild(x, CxmlCreateNode(nullptr, ntText, float_to_str(x_init[i]).c_str()));
	}

	//x_grid
	CxmlAddChild(rootnode, n = CxmlCreateNode(xml_x_grid_name.c_str(), ntElement, NULL));
	for (size_t i = 0; i < x_grid.size(); i++) {
		std::string x_gridk_str = common::vec_to_str(x_grid[i], ';');
		TCXMLnode * x;
		CxmlAddChild(n, x = CxmlCreateNode(features_[i].c_str(), ntElement, nullptr));
		CxmlAddChild(x, CxmlCreateNode(nullptr, ntText, x_gridk_str.c_str()));
	}
	return rootnode;
}

WeightsCDSettings&
WeightsCDSettings::from_node(TCXMLnode* node) {
	features_.clear();
	x_init.clear();
	x_grid.clear();
	char* c_str = nullptr;
	c_str = CxmlGetAttribute(node, &(string(xml_n_iter_name)[0]));
	if (c_str != nullptr) {
		n_iter = std::stoi(c_str);
	}
	else {
		throw std::invalid_argument(xml_n_iter_name + " attribute missing");
	}
	c_str = CxmlGetAttribute(node, &(string(xml_random_order_name)[0]));
	if (c_str != nullptr) {
		random_order = common::str_to_bool(c_str);
	}
	else {
		throw std::invalid_argument(xml_random_order_name + "attribute missing");
	}
	TCXMLnode* x_init_node = CxmlGetSubElemnetN(node, &(string(xml_x_init_name)[0]));
	size_t n = CxmlGetNumElements(x_init_node);
	features_.resize(n);
	x_init.resize(n);
	x_grid.resize(n);
	for (size_t i = 0; i < n; i++) {
		TCXMLnode* init_val_node = CxmlGetSubElemnetI(x_init_node, i);
		features_[i] = init_val_node->Name;
		x_init[i] = str_to_float(CxmlGetSubTextIText(init_val_node, 0));
	}
	TCXMLnode* x_grid_node = CxmlGetSubElemnetN(node, &(string(xml_x_grid_name)[0]));
	if (x_grid_node == nullptr) {
		throw std::invalid_argument(xml_x_grid_name + " missing");
	}
	n = CxmlGetNumElements(x_grid_node);
	for (size_t i = 0; i < n; i++) {
		TCXMLnode* init_val_node = CxmlGetSubElemnetI(x_grid_node, i);
		size_t idx = std::find(features_.begin(), features_.end(), init_val_node->Name) - features_.begin();
		if (idx == features_.size()) {
			throw std::invalid_argument(string(init_val_node->Name) +  "missing");
		}
		c_str = CxmlGetSubTextIText(init_val_node, 0);
		if (c_str != nullptr) {
			x_grid[idx] = common::str_to_vec_double(c_str, ';');
        }
	}
	return *this;
}


LoopNeighbor::LoopNeighbor(const Neighbor& neighbor, const Sample& sample) {
	distance = neighbor.distance;
	rank = neighbor.rank;
	x = neighbor.x;
	y = neighbor.y;

	label = sample.label;
	data = sample.data;
	path = sample.path;
}


LoopClassifierOptParameters::LoopClassifierOptParameters(vector<string> features) 
    : w_optimizer_parameters(features) { }

LoopClassifierOptParameters::LoopClassifierOptParameters(const string& filename) 
    : w_optimizer_parameters(vector<string>()){
	from_xml_file(filename);
}

LoopClassifierParameters::LoopClassifierParameters(const vector<string>& features) : features_(features) {
	feature_weights = vector<data_t>(features.size());
	for (size_t i = 0; i < features.size(); i++)
		feature_weights[i] = 1.0;
}

TCXMLnode*
LoopClassifierParameters::to_node(void) const {
	TCXMLnode * rootnode = CxmlCreateNode(xml_root_name.c_str(), ntElement, NULL);
	TCXMLnode * n;
	CxmlAddChild(rootnode, n = CxmlCreateNode(xml_weights_name.c_str(), ntElement, nullptr));
	CxmlAddChild(n, CxmlCreateNode("count", ntAttribute, std::to_string(feature_weights.size()).c_str()));
	for (size_t i = 0; i < feature_weights.size(); i++) {
		TCXMLnode * w;
		CxmlAddChild(n, w = CxmlCreateNode("W", ntElement, nullptr));
		CxmlAddChild(w, CxmlCreateNode("index", ntAttribute, to_string(i + 1).c_str()));
		CxmlAddChild(w, CxmlCreateNode("value", ntAttribute, float_to_str(feature_weights[i]).c_str()));
		CxmlAddChild(w, CxmlCreateNode("name", ntAttribute, features_[i].c_str()));
	}


	CxmlAddChild(rootnode, CxmlCreateNode(xml_n_neighbors_name.c_str(), ntAttribute, to_string(n_neighbors).c_str()));
	CxmlAddChild(rootnode, CxmlCreateNode(xml_metric_name.c_str(), ntAttribute, to_string(metric).c_str()));
	CxmlAddChild(rootnode, CxmlCreateNode(xml_use_min_max_name.c_str(), ntAttribute, bool_to_str(use_min_max).c_str()));
	CxmlAddChild(rootnode, CxmlCreateNode(xml_use_local_logit_name.c_str(), ntAttribute, bool_to_str(use_local_logit).c_str()));
	CxmlAddChild(rootnode, CxmlCreateNode(xml_logit_eps_name.c_str(), ntAttribute, float_to_str(logit_eps).c_str()));
	CxmlAddChild(rootnode, CxmlCreateNode(xml_logit_C_name.c_str(), ntAttribute, float_to_str(logit_C).c_str()));
	CxmlAddChild(rootnode, CxmlCreateNode(xml_dist_tf_name.c_str(), ntAttribute, std::to_string(dist_tf).c_str()));
	return rootnode;
}

LoopClassifierParameters&
LoopClassifierParameters::from_node(TCXMLnode* node) {
	feature_weights.clear();
	features_.clear();
	char* c_str = nullptr;

	TCXMLnode* weights_node = CxmlGetSubElemnetN(node, &string(xml_weights_name)[0]);
	if (weights_node == nullptr) {
		throw std::invalid_argument(xml_weights_name + "missing");
	}
	size_t weight_count = std::stoi(CxmlGetAttribute(weights_node, "count"));
	feature_weights.resize(weight_count);
	features_.resize(weight_count);
	for (size_t i = 0; i < feature_weights.size(); i++) {
		TCXMLnode* wnode = CxmlGetSubElemnetI(weights_node, i);
		size_t idx = std::stoi(CxmlGetAttribute(wnode, "index"));
		double val = str_to_float(CxmlGetAttribute(wnode, "value"));
		string fname = CxmlGetAttribute(wnode, "name");
		feature_weights[idx - 1] = val;
		features_[idx - 1] = fname;
	}
	c_str = CxmlGetAttribute(node, &string(xml_n_neighbors_name)[0]);
	if (c_str == nullptr) throw std::invalid_argument(xml_n_neighbors_name + " missing");
	n_neighbors = std::stoi(c_str);
	c_str = CxmlGetAttribute(node, &string(xml_metric_name)[0]);
	if (c_str == nullptr) throw std::invalid_argument(xml_metric_name + " missing");
	metric = static_cast<Metric>(std::stoi(c_str));
	c_str = CxmlGetAttribute(node, &string(xml_use_min_max_name)[0]);
	if (c_str == nullptr) throw std::invalid_argument(xml_use_min_max_name + " missing");
	use_min_max = str_to_bool(c_str);
	c_str = CxmlGetAttribute(node, &string(xml_use_local_logit_name)[0]);
	if(c_str == nullptr) throw std::invalid_argument(xml_use_local_logit_name + " missing");
	use_local_logit = str_to_bool(c_str);
	c_str = CxmlGetAttribute(node, &string(xml_logit_eps_name)[0]);
	if (c_str == nullptr) throw std::invalid_argument(xml_logit_eps_name + " missing");
	logit_eps = str_to_float(c_str);
	c_str = CxmlGetAttribute(node, &string(xml_logit_C_name)[0]);
	if (c_str == nullptr) throw std::invalid_argument(xml_logit_C_name + " missing");
	logit_C = str_to_float(c_str);
	c_str = CxmlGetAttribute(node, &string(xml_dist_tf_name)[0]);
	if (c_str == nullptr) throw std::invalid_argument(xml_dist_tf_name + " missing");
	dist_tf = static_cast<DistTfMethod>(std::stoi(c_str));
	return *this;
}


TCXMLnode*
LoopClassifierOptParameters::to_node(void) const {
	TCXMLnode * rootnode = CxmlCreateNode(xml_root_name.c_str(), ntElement, NULL);
	CxmlAddChild(rootnode,
				 CxmlCreateNode(xml_score_type_name.c_str(), ntAttribute, std::to_string(score_type).c_str()));
	CxmlAddChild(rootnode, cv_parameters.to_node());
	CxmlAddChild(rootnode, w_optimizer_parameters.to_node());
	//grid search
	TCXMLnode * x;
	CxmlAddChild(rootnode, x = CxmlCreateNode(xml_n_neighbors_vec_name.c_str(), ntElement, nullptr));
	CxmlAddChild(x, CxmlCreateNode(nullptr, ntText, common::vec_to_str(n_neighbors_vec).c_str()));
	CxmlAddChild(rootnode, x = CxmlCreateNode(xml_logit_C_vec_name.c_str(), ntElement, nullptr));
	CxmlAddChild(x, CxmlCreateNode(nullptr, ntText, common::vec_to_str(logit_C_vec).c_str()));
	CxmlAddChild(rootnode, x = CxmlCreateNode(xml_dist_tf_vec_name.c_str(), ntElement, nullptr));
	vector<size_t> dist_tf_vec_int;
	for (size_t i = 0; i < dist_tf_vec.size(); i++) 
		dist_tf_vec_int.push_back(dist_tf_vec[i]);
	CxmlAddChild(x, CxmlCreateNode(nullptr, ntText, common::vec_to_str(dist_tf_vec_int).c_str()));
	return rootnode;
}

LoopClassifierOptParameters&
LoopClassifierOptParameters::from_node(TCXMLnode* node) {
	char* c_str = nullptr;
	TCXMLnode* node_ptr = nullptr;

	c_str = CxmlGetAttribute(node, &string(xml_score_type_name)[0]);
	if (c_str == nullptr) throw std::invalid_argument(xml_score_type_name + " missing");
	score_type = static_cast<metrics::classification::Score>(std::stoi(c_str));

	node_ptr = CxmlGetSubElemnetN(node, &string(StratifiedKFoldParameters::xml_root_name)[0]);
	if (node_ptr == nullptr) throw std::invalid_argument(StratifiedKFoldParameters::xml_root_name + "missing");
	cv_parameters.from_node(node_ptr);

	node_ptr = CxmlGetSubElemnetN(node, &string(WeightsCDSettings::xml_root_name)[0]);
	if (node_ptr == nullptr) throw std::invalid_argument(WeightsCDSettings::xml_root_name + "missing");
	w_optimizer_parameters.from_node(node_ptr);

	n_neighbors_vec.clear();
	string str = CxmlGetSubTextIText(CxmlGetSubElemnetN(node, &string(xml_n_neighbors_vec_name)[0]), 0);
	vector<string> vec_str = common::split(str, ';');
	for (size_t i = 0; i < vec_str.size(); i++)
		n_neighbors_vec.push_back(std::stoi(vec_str[i]));

	str = CxmlGetSubTextIText(CxmlGetSubElemnetN(node, &string(xml_logit_C_vec_name)[0]), 0);
	logit_C_vec = common::str_to_vec_double(str, ';');

	dist_tf_vec.clear();
	str = CxmlGetSubTextIText(CxmlGetSubElemnetN(node, &string(xml_dist_tf_vec_name)[0]), 0);
	vector<unsigned int> dist_tf_vec_ = common::str_to_vec_uint(str, ';');
	for (size_t i = 0; i < dist_tf_vec_.size(); i++)
		dist_tf_vec.push_back(static_cast<DistTfMethod>(dist_tf_vec_[i]));
	return *this;
}


LoopClassifier::LoopClassifier(const std::string& filename){
	from_xml_file(filename);

}

void
LoopClassifier::train(const Dataset& dataset) {
	_dataset = dataset;
	//enabled samples to X,y
	_enabled_samples = dataset.get_enabled_samples();
	auto Xy = _dataset.samples_to_Xy(_enabled_samples);
	fit(Xy.first, Xy.second);
}

LoopClassifier::LoopClassifier(const LoopClassifierParameters& params) 
    : LocalModel(&params), _params(params) {

}

vector<LoopNeighbor>
LoopClassifier::get_neighbors(const vector<data_t>& sample, size_t n_neighbors) const {
	vector<Neighbor> neighbors = LocalModel::get_neighbors(sample, n_neighbors);
	vector<LoopNeighbor> loop_neighbors(neighbors.size());
	for (size_t i = 0; i < neighbors.size(); i++) {
		loop_neighbors[i] = LoopNeighbor(neighbors[i], _enabled_samples[neighbors[i].idx]);
	}
	return loop_neighbors;
}

string LoopClassifier::classification_info(const vector<data_t>& x) {
	auto details = LocalModel::get_classif_details(x);
	std::stringstream ss;

	ss << "class probabilities: " << std::endl;
	size_t i = 0;
	for (auto it = details.local_classes.begin(); it != details.local_classes.end(); it++){
		ss << "class label: " << *it << "  probability: " << details.local_classes_probs[i++] << std::endl;
	}
	return ss.str();
}


LoopClassifier& 
LoopClassifier::set_parameters(const LoopClassifierParameters& parameters) {
	LocalModel::set_parameters(&parameters);
	_params = parameters;
	return *this;
}

TCXMLnode*
LoopClassifier::to_node(void) const {
	TCXMLnode * rootnode = CxmlCreateNode(xml_root_name.c_str(), ntElement, NULL);
	CxmlAddChild(rootnode, _params.to_node());
	CxmlAddChild(rootnode, _dataset.to_node());
	return rootnode;
}

LoopClassifier&
LoopClassifier::from_node(TCXMLnode* node) {
	TCXMLnode * params_node = CxmlGetSubElemnetN(node, &string(LoopClassifierParameters::xml_root_name)[0]);
	_params.from_node(params_node);
	TCXMLnode * dataset_node = CxmlGetSubElemnetN(node, &string(Dataset::xml_root_name)[0]);
	_dataset.from_node(dataset_node);
	set_parameters(_params);
	LocalModel::set_parameters(&_params);
	train(_dataset);
	return *this;
}

string 
LoopClassifier::info() const {
	std::stringstream ss;
	ss << "Classifier parameters: " << std::endl;
	ss << "metric: " + METRIC_NAMES[static_cast<size_t>(parameters().metric)] << std::endl;
	ss << "number of neighbors: " + std::to_string(parameters().n_neighbors) << std::endl;
	ss << "using min-max normalization: " + common::bool_to_str(parameters().use_min_max) << std::endl;
	ss << "feature weights: ";
	for (double v : parameters().feature_weights) ss << v << ", ";
	ss.seekp(-2, ss.cur); ss << std::endl;
	ss << "using local logistic regression: " + common::bool_to_str(parameters().use_local_logit) << std::endl;
	ss << "logistic regression C: " + std::to_string(parameters().logit_C) << std::endl;
	ss << "logistic regression eps: " + std::to_string(parameters().logit_eps) << std::endl;
	ss << "kernel function, dist_tf: " + DIST_TF_NAMES[static_cast<int>(parameters().dist_tf)] << std::endl;
	ss << "Dataset class mapping: " << std::endl;
	ss << dataset().info();
	return ss.str();
}

LoopClassifierParameters::LoopClassifierParameters(const string& filename) {
	from_xml_file(filename);
}

#pragma region LooperClassifierOpt

score_t
LoopClassifierOpt::cross_val_score(const Dataset& dataset, const LoopClassifier& loopClf) {
	auto Xy = dataset.samples_to_Xy(dataset.get_enabled_samples());
	CrossValidationABC* skf = new StratifiedKFold(Xy.first, Xy.second, _params.cv_parameters);
	LoopClassifier clf_(loopClf);
	score_t res = ::cross_val_score(&clf_, skf, metrics::classification::get_score_func(_params.score_type));
	delete skf;
	return res;
}

std::pair<vector<label_t>, vector<label_t> >
LoopClassifierOpt::cross_val_predict(const Dataset& dataset, const LoopClassifier& loopClf,  const InteropNegotiator& negotiator) {
	auto Xy = dataset.samples_to_Xy(dataset.get_enabled_samples());
	StratifiedKFold* skf = new StratifiedKFold(_params.cv_parameters);
	skf->cmp_folds(Xy.first, Xy.second, negotiator);
	LoopClassifier clf_(loopClf);
	auto res = ::cross_val_predict(&clf_, skf);
	delete skf;
	return res;
}

CrossValDetails
LoopClassifierOpt::cross_validate(const Dataset& dataset, const LoopClassifier& loopClf, const InteropNegotiator& negotiator) {
	auto Xy = dataset.samples_to_Xy(dataset.get_enabled_samples());
	StratifiedKFold* skf = new StratifiedKFold(_params.cv_parameters);
	skf->cmp_folds(Xy.first, Xy.second, negotiator);
	//copy classifier
	LoopClassifier clf_(loopClf);
	CrossValDetails res = _cross_validate.cross_val(&clf_, skf, negotiator, false);
	delete skf;
	_cross_validate.cancellation_pending = false;
	return res;
}

score_t
LoopClassifierOpt::optimize_weights(const Dataset& dataset, LoopClassifier& loopClf, const InteropNegotiator& negotiator) {
    _fitness = nullptr;
	_optimizer = nullptr;
	_skf = nullptr;
	cancellation_pending = false;
    try {
		//init all we need 
        _optimizer = new optimization::CoordinateDescent(_params.w_optimizer_parameters);
        _fitness = new Fitness();
        _fitness->clf = new LoopClassifier(loopClf);  //don't change input clf yet, may be cancelled
        auto Xy = dataset.samples_to_Xy(dataset.get_enabled_samples());
        _skf = new StratifiedKFold(_params.cv_parameters);
        _skf->cmp_folds(Xy.first, Xy.second, negotiator);
        _fitness->skf = _skf;
        _fitness->score_type = _params.score_type;
		//optimizer maybe cancelled
        _optimizer->run(_fitness, negotiator);
		//if optimizer finished set new feature weights
		if (!cancellation_pending) {
			LoopClassifierParameters p = loopClf.parameters();
			p.feature_weights = _optimizer->xopt();
			loopClf.set_parameters(p);
		}
		cancellation_pending = false;
        delete _fitness->clf;
        delete _fitness->skf;
        delete _fitness;
        double fopt = _optimizer->fopt();
        delete _optimizer;
        _optimizer = nullptr;
        return fopt;
    }
    catch(std::exception &e) {
		cancellation_pending = false;
		if (negotiator.message) {
			negotiator.message(negotiator.control_frame_handle, e.what());
		}
        if(_fitness != nullptr) {
            if(_fitness->skf != nullptr) delete _fitness->skf;
            if(_fitness->clf != nullptr) delete _fitness->clf;
        }
        delete _fitness;
        if(_optimizer) {
            delete _optimizer;
        }
        return -1.0;
    }
}

score_t
LoopClassifierOpt::optimize_params(const Dataset& dataset, LoopClassifier& loopClf, const InteropNegotiator& negotiator) {
	if (negotiator.message)
		negotiator.message(negotiator.control_frame_handle, "optimizing parameters...");

	_skf = nullptr;
	cancellation_pending = false;

	LoopClassifier clf(loopClf); 	//don't change input yet, may be cancelled
	auto Xy = dataset.samples_to_Xy(dataset.get_enabled_samples());
	_skf = new StratifiedKFold(_params.cv_parameters);
	_skf->cmp_folds(Xy.first, Xy.second, negotiator);
	
	score_t cur_score = std::numeric_limits<score_t>::max();
	score_t best_score = cur_score;

	LoopClassifierParameters best_parameters = clf.parameters();
	LoopClassifierParameters cur_parameters = clf.parameters();

	auto msg_params = [&](LoopClassifierParameters& params, score_t score) {
		std::stringstream ss;
		ss << "dist_tf:" << params.dist_tf;
		ss << ", logit_C: " << params.logit_C;
		ss << ", neighbors: " << params.n_neighbors;
		ss << ", score: " << score; 
		if (negotiator.message)
			negotiator.message(negotiator.control_frame_handle, ss.str().c_str());
	};

	//precompute number of fitness evaluations
	//will test all combinations of parameters
	size_t total_evals = _params.n_neighbors_vec.size() * _params.logit_C_vec.size() * _params.dist_tf_vec.size();
	size_t evals = 0;
	for (DistTfMethod dist_tf : _params.dist_tf_vec) {
		cur_parameters.dist_tf = dist_tf;
		clf.set_parameters(cur_parameters);
		for (size_t n_neighbors : _params.n_neighbors_vec) {
			cur_parameters.n_neighbors = n_neighbors;
			clf.set_parameters(cur_parameters);
			for (double logit_C : _params.logit_C_vec) {
				cur_parameters.logit_C = logit_C;
				clf.set_parameters(cur_parameters);

				if (cancellation_pending) {
					if (_skf) {
						delete _skf;
					}
                    cancellation_pending=false;
					return best_score;
				}
				evals++;
				if (negotiator.report_progress)
					negotiator.report_progress(negotiator.control_frame_handle, evals / static_cast<float> (total_evals));

				cur_score = -::cross_val_score(&clf, _skf, metrics::classification::get_score_func(_params.score_type));
				msg_params(cur_parameters, cur_score);

				if (cur_score < best_score) {
					if (negotiator.message)
						negotiator.message(negotiator.control_frame_handle, "==================================== \n new best score: ");
					best_score = cur_score;
					best_parameters = cur_parameters;
					msg_params(best_parameters, best_score);
					if (negotiator.message)
						negotiator.message(negotiator.control_frame_handle, "==================================== ");
				}
			}
		}
	}

	msg_params(best_parameters, best_score);
	loopClf.set_parameters(best_parameters);
	return best_score;
}

LoopClassifierOpt::LoopClassifierOpt(const string& filename) 
  : _params(std::vector<string>()) {
    from_xml_file(filename);
}

LoopClassifierOpt::LoopClassifierOpt(const LoopClassifierOptParameters& parameters) 
    : _params(parameters) { }

LoopClassifierOpt& 
LoopClassifierOpt::set_parameters(const LoopClassifierOptParameters& parameters) {
	_params = parameters;
	return *this;
}

TCXMLnode*
LoopClassifierOpt::to_node(void) const {
	TCXMLnode * rootnode = CxmlCreateNode(xml_root_name.c_str(), ntElement, NULL);
	CxmlAddChild(rootnode, _params.to_node());
	return rootnode;
}
LoopClassifierOpt&
LoopClassifierOpt::from_node(TCXMLnode* node) {
	TCXMLnode * params_node = CxmlGetSubElemnetN(node, &string(LoopClassifierOptParameters::xml_root_name)[0]);
	_params.from_node(params_node);
	return *this;
}

const LoopClassifierOptParameters&
LoopClassifierOpt::parameters() const {
    return _params; 
}

void 
LoopClassifierOpt::cancel() {
    if(_optimizer) {
        _optimizer->cancellation_pending = true;
    }
	_cross_validate.cancellation_pending = true;
    cancellation_pending = true;
}
void 
LoopClassifierOpt::reset_cancel() {
	if (_optimizer) {
		_optimizer->cancellation_pending = false;
	}
	_cross_validate.cancellation_pending = false;
	cancellation_pending = false;
}

#pragma endregion LooperClassifierOpt

score_t 
Fitness::func(const vector<data_t>& x) {
	LoopClassifierParameters p = clf->parameters();
	p.feature_weights = x;
	return -cross_val_score(&(clf->set_parameters(p)), 
                           skf,
                           metrics::classification::get_score_func(score_type));
}


#pragma region LooperClassifier

LooperClassifier::LooperClassifier(const string& filename) {
    _negotiator.control_frame_handle = nullptr;
    _negotiator.finished = nullptr;
    _negotiator.message = nullptr;
    _negotiator.report_progress = nullptr;
	from_xml_file(filename);
}

LooperClassifier::LooperClassifier(const LoopClassifier& loop_clf, const LoopClassifierOpt& loop_clf_opt) 
	: _loop_clf(loop_clf), _loop_clf_opt(loop_clf_opt)
{ }

void 
LooperClassifier::set_negotiator(const InteropNegotiator& negotiator) {
	_negotiator = negotiator;
}

const InteropNegotiator&
LooperClassifier::negotiator() const {
	return _negotiator;
}

label_t
LooperClassifier::predict(const vector<data_t>& x)  {
    if (_thread.joinable()) _thread.join();
	return _loop_clf.predict(x);
}

vector<label_t> 
LooperClassifier::predict(const Matrix<data_t>& data) {
    if (_thread.joinable()) _thread.join();	
	return _loop_clf.predict(data);
}


TCXMLnode*
LooperClassifier::to_node(void) const{
	TCXMLnode * rootnode = CxmlCreateNode(xml_root_name.c_str(), ntElement, NULL);
	CxmlAddChild(rootnode, _loop_clf_opt.to_node());
	CxmlAddChild(rootnode, _loop_clf.to_node());
	return rootnode;
}

std::string 
LooperClassifier::get_info() {
	if (_thread.joinable()) _thread.join();
	return loop_clf().info();
}

LooperClassifier&
LooperClassifier::from_node(TCXMLnode* node) {
	if (_thread.joinable()) _thread.join();
	TCXMLnode * _loop_clf_node = CxmlGetSubElemnetN(node, &string(LoopClassifier::xml_root_name)[0]);
	TCXMLnode * _loop_clf_opt_node = CxmlGetSubElemnetN(node, &string(LoopClassifierOpt::xml_root_name)[0]);
	_loop_clf_opt.from_node(_loop_clf_opt_node);
	_loop_clf.from_node(_loop_clf_node);
	return *this;
}


void
LooperClassifier::optimize_weights() {
	if (_thread.joinable()) _thread.join();
	_thread = std::thread([&] {		
		_loop_clf_opt.reset_cancel();
		LoopClassifierParameters params_origin = _loop_clf.parameters();
		LoopClassifierParameters params = params_origin;
		params.n_neighbors = 1;
		params.use_local_logit = false;
		//force 1 neighbor
		_loop_clf.set_parameters(params);
		_loop_clf_opt.optimize_weights(_loop_clf.dataset(), _loop_clf, _negotiator);
		//optimized weights
		auto w = _loop_clf.parameters().feature_weights;
		//set optimized weights
		params_origin.feature_weights = w;
		//get params back
		_loop_clf.set_parameters(params_origin);
		if (_negotiator.finished)
			_negotiator.finished(_negotiator.control_frame_handle);
	});
}

void 
LooperClassifier::optimize_params() {
	if (_thread.joinable()) _thread.join();
	_thread = std::thread([&] {
		_loop_clf_opt.reset_cancel();
		_loop_clf_opt.optimize_params(_loop_clf.dataset(), _loop_clf, _negotiator);
		if (_negotiator.finished)
			_negotiator.finished(_negotiator.control_frame_handle);
	});
}

void 
LooperClassifier::cross_val_report() {
	if (_thread.joinable()) _thread.join();
	_thread = std::thread([&] {
		_loop_clf_opt.reset_cancel();
		auto cv_res = _loop_clf_opt.cross_validate(_loop_clf.dataset(), _loop_clf, _negotiator);
		if (_loop_clf_opt.cancellation_pending) {
			if (_negotiator.finished)
				_negotiator.finished(_negotiator.control_frame_handle);
			return;
		}
		std::stringstream ss;
		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());

		ss << metrics::classification::make_report(y, y_pred);
		//ss << _loop_clf.dataset().info();
		if (_negotiator.message)
			_negotiator.message(_negotiator.control_frame_handle, ss.str().c_str());
		if (_negotiator.finished)
			_negotiator.finished(_negotiator.control_frame_handle);
	});
}

void 
LooperClassifier::cancel() {
    _loop_clf_opt.cancel();
}

LoopClassifier& 
LooperClassifier::loop_clf(){
	if (_thread.joinable()) _thread.join();
	return _loop_clf;
}

LoopClassifierOpt& 
LooperClassifier::loop_clf_opt() {
	if (_thread.joinable()) _thread.join();
	return _loop_clf_opt;
}

size_t 
LooperClassifier::n_features() { 
	if (_thread.joinable()) _thread.join();
    return _loop_clf.n_features(); 
}

LooperClassifier::~LooperClassifier() {
	//controls may not exist now (closing window), do not use negotiator
	_negotiator = { nullptr, nullptr, nullptr, nullptr };
    this->cancel();
	_sleep(0);
	if (_thread.joinable()) { 
		_thread.join();
	}
}

#pragma endregion LooperClassifier