#pragma once
#ifndef LOOP_CLASSIFIER_H
#define LOOP_CLASSIFIER_H


#include <thread>
#include <mutex>
#include "local_model.h"
#include "cross_validation.h"
#include "optimization.h"
#include "dataset.h"
#include "dll_api\interop_negotiator.h"



using namespace metrics::pairwise;
using namespace optimization;
using namespace neighbors;


struct WeightsCDSettings : public CoordinateDescentSettings, public XmlSerializableABC {
	const static string xml_root_name;
	const static string xml_x_init_name;
	const static string xml_x_grid_name;
	const static string xml_n_iter_name;
	const static string xml_random_order_name;

	vector<string> features_;
    
    WeightsCDSettings(const vector<string>& features);
	TCXMLnode* to_node(void) const;
	WeightsCDSettings& from_node(TCXMLnode* node);
};


struct LoopNeighbor : public Neighbor, public Sample {
	LoopNeighbor() {}
	LoopNeighbor(const Neighbor& neighbor, const Sample& sample);
};

struct LoopClassifierOptParameters : public XmlSerializableABC {
    friend class LoopClassifierOpt;
	const static string xml_root_name;
	const static string xml_score_type_name;
	const static string xml_n_neighbors_vec_name;
	const static string xml_logit_C_vec_name;
	const static string xml_dist_tf_vec_name;

	metrics::classification::Score score_type = metrics::classification::F1_MACRO;
	StratifiedKFoldParameters cv_parameters;
	WeightsCDSettings w_optimizer_parameters;

	//grid search values
	vector<size_t>  n_neighbors_vec;
	vector<double> logit_C_vec;
	vector<DistTfMethod> dist_tf_vec;

	TCXMLnode* to_node(void) const;
	LoopClassifierOptParameters& from_node(TCXMLnode* node);

	LoopClassifierOptParameters(vector<string> features);
	LoopClassifierOptParameters(const string& filename);

protected:
    LoopClassifierOptParameters() : w_optimizer_parameters(vector<string>()) {}
};


struct LoopClassifierParameters : public LocalModelParameters, public XmlSerializableABC {
    friend class LoopClassifier;
	const static string xml_root_name;
	const static string xml_weights_name;
	const static string xml_n_neighbors_name;
	const static string xml_metric_name;
	const static string xml_use_min_max_name;
	const static string xml_use_local_logit_name;
	const static string xml_logit_eps_name;
	const static string xml_logit_C_name;
	const static string xml_dist_tf_name;

	vector<string> features_;

	TCXMLnode* to_node(void) const;
	LoopClassifierParameters& from_node(TCXMLnode* node);
	LoopClassifierParameters(const vector<string>& features);
	LoopClassifierParameters(const string& filename);

protected:
    LoopClassifierParameters() {}
};


class LoopClassifier : public LocalModel, public XmlSerializableABC {
    friend class LooperClassifier;
	LoopClassifierParameters _params;
	Dataset _dataset;
	vector<Sample> _enabled_samples;

public:
	const static std::string xml_root_name;
	
	LoopClassifier(const std::string& filename);
	LoopClassifier() : LocalModel() {}
	LoopClassifier(size_t) : LocalModel() {}
	LoopClassifier::LoopClassifier(const LoopClassifierParameters& params);

	void train(const Dataset& dataset);
	vector<LoopNeighbor> get_neighbors(const vector<data_t>& sample, size_t n_neighbors = 10) const;
	string classification_info(const vector<data_t>& x);

	TCXMLnode* to_node(void) const;
	LoopClassifier& from_node(TCXMLnode* node);
	string info() const;

	LoopClassifier& set_parameters(const LoopClassifierParameters& parameters);
	const LoopClassifierParameters& parameters() const { return _params; }

	const Dataset& dataset() const { return _dataset; };
};

struct Fitness;

class LoopClassifierOpt : public XmlSerializableABC{
    friend class LooperClassifier;
	LoopClassifierOptParameters _params;
	//allocates a deallocates while predicting
	Fitness* _fitness = nullptr;
    StratifiedKFold* _skf = nullptr;
    optimization::CoordinateDescent* _optimizer = nullptr;
    CrossValCancellable _cross_validate;

protected: LoopClassifierOpt() {}
public:
    bool cancellation_pending = false;
    const static string xml_root_name;
    LoopClassifierOpt(const string& filename);
	LoopClassifierOpt(const LoopClassifierOptParameters& parameters);

	score_t cross_val_score(const Dataset& dataset, const LoopClassifier& loopClf);
	std::pair<vector<label_t>, vector<label_t> > cross_val_predict(const Dataset& dataset, const LoopClassifier& loopClf,  const InteropNegotiator& negotiator);
	CrossValDetails cross_validate(const Dataset& dataset, const LoopClassifier& loopClf, const InteropNegotiator& negotiator);
	score_t optimize_weights(const Dataset& dataset, LoopClassifier& loopClf, const InteropNegotiator& negotiator);
	score_t optimize_params(const Dataset& dataset, LoopClassifier& loopClf, const InteropNegotiator& negotiator);

    TCXMLnode* to_node(void) const;
	LoopClassifierOpt& from_node(TCXMLnode* node);

	LoopClassifierOpt& set_parameters(const LoopClassifierOptParameters& parameters);
    const LoopClassifierOptParameters& parameters() const;
    void cancel();
	void reset_cancel();
};


struct Fitness : public FitnessABC {
	CrossValidationABC* skf = nullptr;
	LoopClassifier* clf = nullptr;
	metrics::classification::Score score_type;
	score_t func(const vector<data_t>& x);
};


class LooperClassifier : public XmlSerializableABC{
	std::thread  _thread;
	LoopClassifier _loop_clf;
	LoopClassifierOpt _loop_clf_opt;
	LooperClassifier(const LooperClassifier& obj);
    InteropNegotiator _negotiator;

public:
    const static string xml_root_name;
    LooperClassifier(const string& filename);
	LooperClassifier(const LoopClassifier& loop_clf, const LoopClassifierOpt& loop_clf_opt);
	void set_negotiator(const InteropNegotiator& negotiator);
	const InteropNegotiator& negotiator() const;

    label_t predict(const vector<data_t>& x);
	vector<label_t> predict(const Matrix<data_t>& data);

    TCXMLnode* to_node(void) const;
	LooperClassifier& from_node(TCXMLnode* node);
    void optimize_weights();
	void optimize_params();
	void cross_val_report();
    void cancel();
    std::string get_info();

	LoopClassifier& loop_clf();
	LoopClassifierOpt& loop_clf_opt();
	size_t n_features();
	~LooperClassifier();
};



#endif