/*
 * classifier_trainer.cpp
 *
 *  Created on: 16.11.2011
 *      Author: isvoboda
 */

#include "classifier_svm.hpp"

namespace ppd
{

void ShuffleIndexes(const unsigned int size, cv::Mat& output)
{
	cv::Mat indices = cv::Mat_<unsigned int>(size,1);
	#pragma omp parallel for schedule(dynamic)
	for(unsigned int i = 0; i < size; i++)
	{
		indices.at<unsigned int>(i) = i;
	}
	cv::randShuffle(indices, 3., NULL);
	output = indices;


}

void ShuffleMat(const cv::Mat& indexes, cv::Mat& input)
{
	cv::Mat tmp = cv::Mat(input);
	unsigned int size = static_cast<unsigned int>(indexes.rows);
	#pragma omp parallel for schedule(dynamic)
	for(unsigned int i = 0; i < size; i++)
	{
		cv::Mat tmp_row = input(cv::Rect(0,i,input.cols,1));
		tmp.row(indexes.at<unsigned int>(i)).copyTo(tmp_row);
	}
	tmp.release();
//		cv::FileStorage fs("pokus.xml", cv::FileStorage::WRITE);
//		fs << "descriptors";
//		fs << input;
//		fs.release();
}

void ReadBOW_features(const std::string& file_name, const std::string& name, cv::Mat& bow_features)
{
	cv::FileStorage bow_fs(file_name, cv::FileStorage::READ);
	cv::FileNode bow_node = bow_fs[name.c_str()];
	cv::FileNodeIterator it = bow_node.begin(), it_end = bow_node.end();
	for( ; it != it_end; ++it)
	{
		cv::Mat feature;
		(*it)>>feature;
		bow_features.push_back(feature);
	}
}

//void TrainSVM(const CvSVMParams& svm_params, const std::string& file_name_bow_feature_storage, const std::string& file_name_bg_bow_feature_storage, const std::string& classifier_file_name, const std::string& classifier_name )
//{
//	cv::Mat bow_f;
//
//	cv::Mat responses;
//	cv::FileStorage fs_bow_f(file_name_bow_feature_storage, cv::FileStorage::READ);
//	cv::FileNode bow_f_node = fs_bow_f["BowFeatures"];
//	cv::FileNodeIterator it = bow_f_node.begin(), it_end = bow_f_node.end();
//	for( ; it != it_end; ++it)
//	{
//		cv::Mat feature;
//		(*it)>>feature;
//		bow_f.push_back(feature);
//		cv::Mat res = (cv::Mat_<float>(1, 1) << 1.0);
//		responses.push_back(res);
//	}
//
//	cv::FileStorage fs_bow_f_bg(file_name_bg_bow_feature_storage, cv::FileStorage::READ);
//	bow_f_node = fs_bow_f_bg["BowFeatures"];
//	it = bow_f_node.begin(), it_end = bow_f_node.end();
//	for( ; it != it_end; ++it)
//	{
//		cv::Mat feature;
//		(*it)>>feature;
//		bow_f.push_back(feature);
//		cv::Mat res = (cv::Mat_<float>(1, 1) << 0.0);
//		responses.push_back(res);
//	}
//
//	cv::Mat indexes, shuffledMat;
//	ShuffleIndexes(static_cast<unsigned int>(responses.rows), indexes);
//	ShuffleMat(indexes, responses);
//	ShuffleMat(indexes, bow_f);
//
//	CvSVM classifier;
//	CvSVMParams params = CvSVMParams();
//	params.kernel_type = CvSVM::C_SVC;
//	params.kernel_type = CvSVM::RBF;
//	params.C = 1.0;
//	params.gamma = 0.001;
//
//	classifier.train_auto(bow_f, responses, cv::Mat(), cv::Mat(), params, 10);
//	classifier.save("svm_klasifikator.xml", "pp_classifier");
//
//
//	//	CvParamGrid gammaParamGrid = CvParamGrid(0.001, 1000, 1.5);
//////	CvParamGrid nuParamGrid = CvParamGrid(0.01, 0.99, 1.01);
////	CvTermCriteria criteria = cvTermCriteria(CV_TERMCRIT_ITER, 1000, 0.1);
////	CvSVMParams params = CvSVMParams(); //CvSVMParams(CvSVM::C_SVC, CvSVM::RBF, .0, 0.001, .0, 0.5, .0, .0, NULL, criteria);
//////	classifier.train_auto(bow_f, responses, cv::Mat(), cv::Mat(), )
////	CvSVMParams par;
////	classifier.train_auto(bow_f, responses, cv::Mat(), cv::Mat(), params, 3);
//////	classifier.train(bow_f, responses, cv::Mat(), cv::Mat(), svm_params);
////	classifier.save(classifier_file_name.c_str(), classifier_name.c_str());
//}

void Predict(const std::string& classfier_file_name, const std::string& classifier_name)
{
//	BOW bow;
//	bow.read_voc(settingsReader.get_voc_file_name());
//int tmp, tmnp2;
//tmp = tmp2 = 0;
//	CvSVM svm;
//	svm.load("svm_klasifikator.xml","pp_classifier");
////	int tmp = 0;
////	int tmp2 = 0;
//	reader.work_fg();
//	unsigned int size = reader.get_number_of_objects();
//	for(unsigned int i = 0; i < size; i++, tmp2++)
//	{
//		cv::Mat image, bow_feature;
//		image = reader[i];
//		if(image.cols > 640 || image.rows > 480)
//				{
//					int width = image.cols;
//					int height = image.rows;
//					float ratio = 0.0;
//					if(width > height)
//						ratio = 640.0 / width;
//					else
//						ratio = 480.0 / height;
//					cv::Mat tmpImg = cv::Mat(static_cast<int>(height*ratio), static_cast<int>(width*ratio),CV_8UC3);
//		//Potentially expensive operation
//					cv::resize(image, tmpImg, tmpImg.size(), 0.0, 0.0, cv::INTER_LANCZOS4);
//					image = tmpImg;
//		//		std::cerr << "Image: " << reader.get_name_of_actual_image() << " was resized" << std::endl;
//				}
//		bow.ExtractBowFeature(image, bow_feature);
//		if (bow_feature.data == NULL)
//		{
//			tmp2--;
//			continue;
//		}
//		float result = svm.predict(bow_feature, false);
//		std::cout << "Image " << i << "/" << size << " : class = " << reader.get_class(i) << " predicted = " << result <<std::endl;
//		if(result > 0)
//			tmp++;
//		std::cout << tmp <<" | " << tmp2 << std::endl;
//	}
}

Scaler::Scaler()
{
	_lower = 0.f;
	_upper = 1.f;

}

void Scaler::read_scale_parameters(const std::string& file_name, float& lower, float& upper, std::vector<float>& min_feature, std::vector<float>& max_feature) const
{
	std::ifstream inFile;
	inFile.open(file_name.c_str(), std::ios::in);
	if(!inFile.is_open())
	{
	 throw new ppd::Exception("Can't open file: "+file_name);
	 return;
	}

	unsigned int index;
	float min, max;
	inFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
	inFile >> lower >> upper;
	while(!inFile.eof())
	{
		inFile >> index >> min >> max;
		if(inFile.eof())
			break;
		min_feature.push_back(min);
		max_feature.push_back(max);
		//Libsvm is indexing from 1: index == size of vector indexing from 0
		if(index != min_feature.size())
		{
			throw new ppd::Exception("Problem occurred during reading the scale parameters file: " + file_name);
			inFile.close();
			return;
		}
	}

	inFile.close();
}

void Scaler::read_scale_parameters(const std::string& file_name)
{
	std::ifstream inFile;
	inFile.open(file_name.c_str(), std::ios::in);
	if(!inFile.is_open())
	{
	 throw new ppd::Exception("Can't open file: "+file_name);
	 return;
	}

	unsigned int index;
	float min, max;
	inFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
	inFile >> _lower >> _upper;
	while(!inFile.eof())
	{
		inFile >> index >> min >> max;
		if(inFile.eof())
			break;
		_min_f.push_back(min);
		_max_f.push_back(max);
		//Libsvm is indexing from 1: index == size of vector indexing from 0
		if(index != _min_f.size())
		{
			throw new ppd::Exception("Problem occurred during reading the scale parameters file: " + file_name);
			inFile.close();
			return;
		}
	}

	inFile.close();

}

void Scaler::save_scale_parameters(const std::string& file_name, const float lower, const float upper, const std::vector<float>& min_feature, const std::vector<float>& max_feature) const
{
	if(min_feature.size() != max_feature.size())
	{
		throw new ppd::Exception("min_features's size is not equal to max_feature's size!");
		return;
	}

	std::fstream output_file;
	output_file.open(file_name.c_str(), std::fstream::out);
	if(output_file.is_open())
	{
		output_file << "x" << std::endl;
		output_file << lower << " " << upper << std::endl;

		for(int i = 0; i < static_cast<int>(min_feature.size()); i++)
		{
			output_file << i+1 << " " << min_feature[i] << " " << max_feature[i] << std::endl;
		}
		output_file << std::endl;
		output_file.close();
	}
	else
	{
		throw new ppd::Exception("Failed to open file: " + file_name);
		return;
	}
}

void Scaler::save_scale_parameters(const std::string& file_name) const
{
	if(_min_f.size() != _max_f.size())
	{
		throw new ppd::Exception("min_features's size is not equal to max_feature's size!");
		return;
	}

	std::fstream output_file;
	output_file.open(file_name.c_str(), std::fstream::out);
	if(output_file.is_open())
	{
		output_file << "x" << std::endl;
		output_file << _lower << " " << _upper << std::endl;

		for(int i = 0; i < static_cast<int>(_min_f.size()); i++)
		{
			output_file << i+1 << " " << _min_f[i] << " " << _max_f[i] << std::endl;
		}
		output_file << std::endl;
		output_file.close();
	}
	else
	{
		throw new ppd::Exception("Failed to open file: " + file_name);
		return;
	}
}

void Scaler::scale(const cv::Mat& input, cv::Mat& output, const float lower, const float upper, const std::vector<float>& min_feature, const std::vector<float>& max_feature) const
{
	input.copyTo(output);

	for(int row = 0; row < input.rows; row++)
	{
		for(int col = 0; col < input.cols; col++)
		{
			float value = input.at<float>(row,col);
//			if(max_feature[col] == min_feature[col])
//					return;
			if(value == min_feature[col])
					value = lower;
			else if(value == max_feature[col])
					value = upper;
			else
				value = lower + (upper-lower) * (value-min_feature[col]) / (max_feature[col]-min_feature[col]);

			output.at<float>(row,col) = value;
		}
	}
}

void Scaler::scale(const cv::Mat& input, cv::Mat& output) const
{
	input.copyTo(output);

	for(int row = 0; row < input.rows; row++)
	{
		for(int col = 0; col < input.cols; col++)
		{
			float value = input.at<float>(row,col);
//			if(max_feature[col] == min_feature[col])
//					return;
			if(value == _min_f[col])
					value = _lower;
			else if(value == _max_f[col])
					value = _upper;
			else
				value = _lower + (_upper-_lower) * (value-_min_f[col]) / (_max_f[col]-_min_f[col]);

			output.at<float>(row,col) = value;
		}
	}
}

void Scaler::find_min_max_feature(const cv::Mat& input, std::vector<float>& min_feature, std::vector<float>& max_feature) const
{
	for(int col = 0; col < input.cols; col++)
	{

		float min = std::numeric_limits<float>::max();
		float max = std::numeric_limits<float>::min();

		for(int row = 0; row < input.rows; row++)
		{
			float value = input.at<float>(row,col);
			min = (value < min) ? value : min;
			max = (value > max) ? value : max;
		}

		min_feature.push_back(min);
		max_feature.push_back(max);
	}
}

void Scaler::find_min_max_feature(const cv::Mat& input)
{
	for(int col = 0; col < input.cols; col++)
	{

		float min = std::numeric_limits<float>::max();
		float max = std::numeric_limits<float>::min();

		for(int row = 0; row < input.rows; row++)
		{
			float value = input.at<float>(row,col);
			min = (value < min) ? value : min;
			max = (value > max) ? value : max;
		}

		_min_f.push_back(min);
		_max_f.push_back(max);
	}
}

SVM::SVM(void)
{
	this->classifier_file_name = "";
	this->classifier_name = "";
	this->set_params();
	this->c_grid = CvSVM::get_default_grid(CvSVM::C);
	this->gamma_grid = CvSVM::get_default_grid(CvSVM::GAMMA);
	this->model = NULL;

}

SVM::SVM(const std::string& file_name, const std::string& name)
{
	this->set_params();
	this->c_grid = CvSVM::get_default_grid(CvSVM::C);
	this->gamma_grid = CvSVM::get_default_grid(CvSVM::GAMMA);
	classifier_file_name = file_name;
	classifier_name = name;
	this->model = NULL;
}

void SVM::set_classifier_name(const std::string& file_name, const std::string& name)
{
	this->classifier_file_name = file_name;
	this->classifier_name = name;
}

void SVM::load_classifier()
{
	this->classifier.load(this->classifier_file_name.c_str(), this->classifier_name.c_str());
}

void SVM::load_classifier(const std::string& file_name, const std::string& name)
{
	this->set_classifier_name(file_name, name);
	this->load_classifier();
}

void SVM::load_model(const std::string file_name)
{
	this->model = svm_load_model(file_name.c_str());
	if(model == NULL)
		throw new ppd::Exception("Failed to load the svm model: "+file_name);
}

void SVM::set_params(void)
{
	//Default values
	this->params = CvSVMParams();
	this->params.svm_type = CvSVM::C_SVC;
	this->params.kernel_type = CvSVM::RBF;
	this->params.C = 1.0;
	this->params.gamma = 0.001;
}

void SVM::set_train_params(const CvParamGrid& c_grid, const CvParamGrid& gamma_grid)
{
	this->c_grid = CvParamGrid(c_grid);
	this->gamma_grid = CvParamGrid(gamma_grid);
}

float SVM::predict(const cv::Mat& feature)const
{
	struct svm_node* feature_vec = this->libsvm_format(feature);
	double  predict_label = svm_predict(this->model, feature_vec);
	delete[] feature_vec;
	return static_cast<float>(predict_label);
}

float SVM::predict_probability(const cv::Mat& feature, std::vector<float>& probabilities) const
{

	struct svm_node* feature_vec = this->libsvm_format(feature);

	double *prob_estimates = new double[this->model->nr_class];

	double  predict_label = svm_predict_probability(this->model, feature_vec, prob_estimates);

	for(int i = 0; i < this->model->nr_class; i++)
		probabilities.push_back(prob_estimates[i]);

	delete[] feature_vec;
	delete[] prob_estimates;

	return static_cast<float>(predict_label);
}

void SVM::ShuffleMat(const cv::Mat& indexes, cv::Mat& input) const
{
	cv::Mat tmp = cv::Mat(input);
	unsigned int size = static_cast<unsigned int>(indexes.rows);
	#pragma omp parallel for schedule(dynamic)
	for(unsigned int i = 0; i < size; i++)
	{
		cv::Mat tmp_row = input(cv::Rect(0,i,input.cols,1));
		tmp.row(indexes.at<unsigned int>(i)).copyTo(tmp_row);
	}
	tmp.release();
}

void SVM::ShuffleIndexes(const unsigned int size, cv::Mat& output) const
{
	cv::Mat indices = cv::Mat_<unsigned int>(size,1);
	#pragma omp parallel for schedule(dynamic)
	for(unsigned int i = 0; i < size; i++)
	{
		indices.at<unsigned int>(i) = i;
	}
	cv::randShuffle(indices, 3., NULL);
	output = indices;
}

struct svm_node* SVM::libsvm_format(const cv::Mat& feature) const
{
	svm_node *feature_vec = new svm_node[feature.cols+1];
	int i;
	for(i = 0; i < feature.cols; i++)
	{
		feature_vec[i].index = i+1;
		feature_vec[i].value = feature.at<float>(0,i);
	}
	feature_vec[i].index = -1;
	return feature_vec;
}

void SVM::train(cv::Mat& features, cv::Mat& labels)
{
	cv::Mat indexes;
	this->ShuffleIndexes(static_cast<unsigned int>(features.rows), indexes);
	this->ShuffleMat(indexes, labels);
	this->ShuffleMat(indexes, features);

//	(mín_val, min_val * step, min_val * step^2, ... ,min_val * step^n)
//	Default values from OpenCv
//	CvParamGrid CvSVM::get_default_grid( int param_id )
//	{
//		CvParamGrid grid;
//		if( param_id == CvSVM::C )
//		{
//			grid.min_val = 0.1;
//			grid.max_val = 500;
//			grid.step = 5; // total iterations = 5
//		}
//		else if( param_id == CvSVM::GAMMA )
//		{
//			grid.min_val = 1e-5;
//			grid.max_val = 0.6;
//			grid.step = 15; // total iterations = 4
//		}
//		else if( param_id == CvSVM::P )
//		{
//			grid.min_val = 0.01;
//			grid.max_val = 100;
//			grid.step = 7; // total iterations = 4
//		}
//		else if( param_id == CvSVM::NU )
//		{
//			grid.min_val = 0.01;
//			grid.max_val = 0.2;
//			grid.step = 3; // total iterations = 3
//		}
//		else if( param_id == CvSVM::COEF )
//		{
//			grid.min_val = 0.1;
//			grid.max_val = 300;
//			grid.step = 14; // total iterations = 3
//		}
//		else if( param_id == CvSVM::DEGREE )
//		{
//			grid.min_val = 0.01;
//			grid.max_val = 4;
//			grid.step = 7; // total iterations = 3
//		}
//		else
//			cvError( CV_StsBadArg, "CvSVM::get_default_grid", "Invalid type of parameter "
//				"(use one of CvSVM::C, CvSVM::GAMMA et al.)", __FILE__, __LINE__ );
//		return grid;
//}

//	CvParamGrid c = CvParamGrid(.1, 1000., 2.);			// cca 13x
//	CvParamGrid gamma = CvParamGrid(0.00001, 100., 5.);	// cca 6x

	this->classifier.train_auto(features, labels, cv::Mat(), cv::Mat(), this->params, 10, this->c_grid, this->gamma_grid );
	this->classifier.save(this->classifier_file_name.c_str(), this->classifier_name.c_str());

}

//SVM::~SVM()
//{
//	std::cout << "SVM Classifier destructor." << std::endl;
//}

}
