#include "loopclf_dllapi.h"

#include "..\loop_classifier.h"
#include "..\cross_validation.h"
#include "..\optimization.h"
#include "..\metrics.h"
#include "..\dataset.h"

//LooperClassifier
#pragma region LooperClassifier

LOOPER_CLF_H init_LooperClf(const char* filename) {
	try {
		return static_cast<LOOPER_CLF_H>(new LooperClassifier(filename));
	}
	catch (...) {
		return nullptr;
	}
}

int kill_LooperClf(LOOPER_CLF_H LooperClf) {
    if(!LooperClf) return -1;
    LooperClassifier* looper_clf = static_cast<LooperClassifier*>(LooperClf);
    InteropNegotiator negotiator = looper_clf->negotiator();
    try {
        delete looper_clf;
        return 0;
    }
    catch(const std::exception &e) {
        if(negotiator.message)
            negotiator.message(negotiator.control_frame_handle, e.what());
        if(negotiator.finished)
            negotiator.finished(negotiator.control_frame_handle);
        return -1;
    }
}

int LooperClf_predict(LOOPER_CLF_H LooperClf, const double *data, unsigned n_samples, int* y_pred) {
    if(!LooperClf) return -1;
    LooperClassifier* looper_clf = static_cast<LooperClassifier*>(LooperClf);
    InteropNegotiator negotiator = looper_clf->negotiator();
    try {
		size_t n_features = looper_clf->n_features();
		Matrix<double> test_data(vector<data_t>(data, data + n_samples*n_features), n_samples, n_features);
		vector<int> targets_pred = looper_clf->predict(test_data);
		for (size_t i = 0; i < targets_pred.size(); i++)
			y_pred[i] = targets_pred[i];
		return 0;
    }
	catch (const std::exception &e) {
		if (negotiator.message)
			negotiator.message(negotiator.control_frame_handle, e.what());
		if (negotiator.finished)
			negotiator.finished(negotiator.control_frame_handle);
		return -1;
    }
}
int LooperClf_save_to(LOOPER_CLF_H LooperClf, const char* filename) {
    if(!LooperClf) return -1;
    LooperClassifier* looper_clf = static_cast<LooperClassifier*>(LooperClf);
    InteropNegotiator negotiator = looper_clf->negotiator();
	try {
		looper_clf->save_to_xml(filename);
		return 0;
	}
	catch (const std::exception &e) {
		if (negotiator.message)
			negotiator.message(negotiator.control_frame_handle, e.what());
		if (negotiator.finished)
			negotiator.finished(negotiator.control_frame_handle);
		return -1;
	}
}

int LooperClf_optimize_weights(LOOPER_CLF_H LooperClf) {
    if(!LooperClf) return -1;
    LooperClassifier* looper_clf = static_cast<LooperClassifier*>(LooperClf);
    InteropNegotiator negotiator = looper_clf->negotiator();
    try {
	    looper_clf->optimize_weights();
		return 0;
    }
	catch (const std::exception &e) {
		if (negotiator.message)
			negotiator.message(negotiator.control_frame_handle, e.what());
		if (negotiator.finished)
			negotiator.finished(negotiator.control_frame_handle);
		return -1;
	}
}


int LooperClf_optimize_params(LOOPER_CLF_H LooperClf) {
    if(!LooperClf) return -1;
    LooperClassifier* looper_clf = static_cast<LooperClassifier*>(LooperClf);
    InteropNegotiator negotiator = looper_clf->negotiator();
	try {
		looper_clf->optimize_params();
		return 0;
	}
	catch (const std::exception &e) {
		if (negotiator.message)
			negotiator.message(negotiator.control_frame_handle, e.what());
		if (negotiator.finished)
			negotiator.finished(negotiator.control_frame_handle);
		return -1;
	}
}

int LooperClf_cross_val_report(LOOPER_CLF_H LooperClf) {
    if(!LooperClf) return -1;
    LooperClassifier* looper_clf = static_cast<LooperClassifier*>(LooperClf);
    InteropNegotiator negotiator = looper_clf->negotiator();
	try {
		looper_clf->cross_val_report();
		return 0;
	}
	catch (const std::exception &e) {
		if (negotiator.message)
			negotiator.message(negotiator.control_frame_handle, e.what());
		if (negotiator.finished)
			negotiator.finished(negotiator.control_frame_handle);
		return -1;
	}
}

int LooperClf_get_info(LOOPER_CLF_H LooperClf, char* buffer) {
    if(!LooperClf) return -1;
    LooperClassifier* looper_clf = static_cast<LooperClassifier*>(LooperClf);
    InteropNegotiator negotiator = looper_clf->negotiator();
	try {
		string info = looper_clf->get_info();
		for (size_t i = 0; i < info.size(); i++)
			buffer[i] = info[i];
		buffer[info.size()] = '\0';
		return 0;
	}
	catch (const std::exception &e) {
		if (negotiator.message)
			negotiator.message(negotiator.control_frame_handle, e.what());
		if (negotiator.finished)
			negotiator.finished(negotiator.control_frame_handle);
		return -1;
	}
}

int LooperClf_n_features(LOOPER_CLF_H LooperClf, unsigned* n_features) {
    if(!LooperClf) return -1;
    LooperClassifier* looper_clf = static_cast<LooperClassifier*>(LooperClf);
    InteropNegotiator negotiator = looper_clf->negotiator();
	try {
		*n_features = looper_clf->n_features();
		return 0;
	}
	catch (const std::exception &e) {
		if (negotiator.message)
			negotiator.message(negotiator.control_frame_handle, e.what());
		if (negotiator.finished)
			negotiator.finished(negotiator.control_frame_handle);
		return -1;
	}
}

int LooperClf_set_negotiator(LOOPER_CLF_H LooperClf, const InteropNegotiator* negotiator) {
    if(!LooperClf) return -1;
    LooperClassifier* looper_clf = static_cast<LooperClassifier*>(LooperClf);
    looper_clf->set_negotiator(*negotiator);
    return 0;
}

int LooperClf_cancel(LOOPER_CLF_H LooperClf) {
    if(!LooperClf) return -1;
    LooperClassifier* looper_clf = static_cast<LooperClassifier*>(LooperClf);
    looper_clf->cancel();
    return 0;
}

#pragma endregion LooperClassifier

//LoopClassifier
#pragma region LoopClassifier


CLF_H
init_LoopClf(const char* filename){
	return static_cast<CLF_H>(new LoopClassifier(filename));
}

CLF_H
copy_LoopClf(CLF_H LoopClf){
	return static_cast<CLF_H>(new LoopClassifier(*static_cast<LoopClassifier*>(LoopClf)));
}

void kill_LoopClf(CLF_H LoopClf){
	if (LoopClf)
		delete static_cast<LoopClassifier*>(LoopClf);
}

void LoopClf_save_to(CLF_H LoopClf, const char* filename) {
    static_cast<LoopClassifier*>(LoopClf)->save_to_xml(filename);
}

void
LoopClf_train(CLF_H LoopClf, DATASET_H dataset){
	static_cast<LoopClassifier*>(LoopClf)->train(*static_cast<Dataset*>(dataset));
}

void 
LoopClf_predict(CLF_H LoopClf, const double *data, const unsigned n_samples, int* y_pred){
    size_t n_features = static_cast<LoopClassifier*>(LoopClf)->n_features();
	Matrix<double> test_data(vector<data_t>(data, data + n_samples*n_features), n_samples, n_features);
	vector<int> targets_pred = static_cast<LoopClassifier*>(LoopClf)->predict(test_data);
	for (size_t  i = 0; i < targets_pred.size(); i++)
		y_pred[i] = targets_pred[i];
}

void
LoopClf_get_neighbors(CLF_H LoopClf, 
                      const double* sample,
                      unsigned n_neighbors, 
                      double* out_data, 
                      int* out_targets, 
                      char** out_labels, 
                      double* out_distances, 
                      unsigned* out_ranks, 
                      char** out_pathes)
{
	size_t n_features = static_cast<LoopClassifier*>(LoopClf)->n_features();
	vector<data_t> sample_v(sample, sample + n_features);
	vector<LoopNeighbor> neighbors = static_cast<LoopClassifier*>(LoopClf)->get_neighbors(sample_v, n_neighbors);
	for (size_t i = 0; i < neighbors.size(); i++){
		for (size_t j = 0; j < n_features; j++)
			out_data[i*n_features + j] = neighbors[i].x[j];
		out_targets[i] = neighbors[i].y;
		out_distances[i] = neighbors[i].distance;
		out_ranks[i] = neighbors[i].rank;
        for(size_t j = 0; j < neighbors[i].path.size(); j++)
		    out_pathes[i][j] = neighbors[i].path[j];
        out_pathes[i][neighbors[i].path.size()] = '\0';
        for(size_t j = 0; j < neighbors[i].label.size(); j++)
            out_labels[i][j] = neighbors[i].label[j];
        out_labels[i][neighbors[i].label.size()] = '\0';
	}
}


void LoopClf_classification_info(CLF_H LoopClf, const double* sample, char* buffer) {
	size_t n_features = static_cast<LoopClassifier*>(LoopClf)->n_features();
	string info = static_cast<LoopClassifier*>(LoopClf)->classification_info(std::vector<data_t>(sample, sample + n_features));
	for (size_t i = 0; i < info.size(); i++)
		buffer[i] = info[i];
	buffer[info.size()] = '\0';
}

void LoopClf_get_probs(CLF_H LoopClf, const double* sample, double* probs, int* classes) {
    size_t n_features = static_cast<LoopClassifier*>(LoopClf)->n_features();
    auto res = static_cast<LoopClassifier*>(LoopClf)->get_classif_details(vector<data_t>(sample, sample + n_features));
    size_t i = 0;
    for(label_t label: res.local_classes) {
        classes[i] = label;
        probs[i] = res.local_classes_probs[i];
        i++;
    }
    return;
}

unsigned LoopClf_n_features(CLF_H LoopClf) {
	return static_cast<LoopClassifier*>(LoopClf)->n_features();
}

const DATASET_H LoopClf_dataset(CLF_H LoopClf) {
	return static_cast<const void*>(&static_cast<LoopClassifier*>(LoopClf)->dataset());
}


#pragma endregion

//Classsifier optimizer
#pragma region LoopClassifierOpt


CLF_OPT_H init_LoopClfOpt(const char* filename){
	return static_cast<CLF_OPT_H>(new LoopClassifierOpt(filename));
}

double LoopClfOpt_cross_val_score(CLF_OPT_H loopClfOpt,  DATASET_H dataset, CLF_H loopClf){
	return static_cast<LoopClassifierOpt*>(loopClfOpt)->cross_val_score(
		*static_cast<Dataset*>(dataset),
		*static_cast<LoopClassifier*>(loopClf));
}

double LoopClfOpt_optimize_weights(CLF_OPT_H loopClfOpt, DATASET_H dataset, CLF_H loopClf){
    InteropNegotiator negotiator = {nullptr, nullptr, nullptr, nullptr};
	return static_cast<LoopClassifierOpt*>(loopClfOpt)->optimize_weights(
		*static_cast<Dataset*>(dataset),
		*static_cast<LoopClassifier*>(loopClf),
        negotiator);
}

void kill_LoopClfOpt(CLF_OPT_H loopClfOpt){
	delete static_cast<LoopClassifierOpt*>(loopClfOpt);
}

#pragma endregion



#pragma region Dataset

DATASET_H create_Dataset() {
    return static_cast<DATASET_H>(new Dataset());
}

DATASET_H init_Dataset(const char* filename){
	return static_cast<DATASET_H>(new Dataset(filename));
}

void kill_Dataset(DATASET_H dataset) {
    delete static_cast<Dataset*>(dataset);
}

int Dataset_add_sample(DATASET_H dataset,
    const char* label,
    const char* path,
    const double* data,
    unsigned n_points) {
    Sample s;
    s.label = label;
    s.path = path;
    s.data = vector<data_t>(data, data + n_points);
    return static_cast<Dataset*>(dataset)->add_sample(s);
}

LOOP_CLASSF_MODEL_API unsigned Dataset_n_classes(DATASET_H dataset) {
	return static_cast<Dataset*>(dataset)->get_enabled_class_codes().size();
}
LOOP_CLASSF_MODEL_API unsigned Dataset_get_classes(DATASET_H dataset, char** class_names, int* class_codes) {
	Dataset* datasetp = static_cast<Dataset*>(dataset);
	//print class mapping
	int i = 0;
	for (label_t label : datasetp->get_enabled_class_codes()) {
		class_codes[i] = label;
        size_t offset = 0;
		for (const string& class_str : datasetp->get_classes(label)) {
            size_t k = 0;
			for (k = 0; k < class_str.size(); k++) {
				class_names[i][offset + k] = class_str[k];
			}
            offset += k;
            class_names[i][offset++] = ',';
            class_names[i][offset++] = ' ';
		}
        class_names[i][offset-2] = '\0';
		i++;
	}
	return i;
}

int Dataset_remove_sample(DATASET_H dataset, const char* path){
	return static_cast<Dataset*>(dataset)->remove_sample(path);
}
void Dataset_save_to(DATASET_H dataset, const char* filename) {
    static_cast<Dataset*>(dataset)->save_to_xml(filename);
}

#pragma endregion