#include <FaceClassifier/miscellaneous.h>
#include <FaceClassifier/SVMFaceClassifier.h>

#include <opencv/cvaux.h>

namespace face {

void SVMFaceClassifier::init(const int width, const int height)
{
	this->width = width;
	this->height = height;
	this->svmParams = NULL;
}

SVMFaceClassifier::SVMFaceClassifier()
{
}

SVMFaceClassifier::SVMFaceClassifier(const std::string name, const std::map<float, std::string> classLabel, const int width, const int height) : FaceClassifier(name, classLabel)
{
	init(width, height);
	this->svm = NULL;
}

SVMFaceClassifier::SVMFaceClassifier(const std::string name, const std::map<float, std::string> classLabel, const int width, const int height, const std::string modelFile) : FaceClassifier(name, classLabel)
{
	init(width, height);
	LoadModelFile(modelFile);
}

SVMFaceClassifier::~SVMFaceClassifier()
{
	if (svm) cvFree(&svm);
	if (svmParams) cvFree(&svmParams);
}

void SVMFaceClassifier::LoadModelFile(const std::string modelFile) {
	if (!face::utils::FileSystemUtils::exists(modelFile)) {
		std::runtime_error(std::string("Error! Model file not found: ") + modelFile + ".\n");
	}
	std::cout << "Loading modelfile " << modelFile << ": ";
	this->modelFile = modelFile;
	svm = new CvSVM();
	svm->load(modelFile.c_str());
	std::cout << "done\n";
}

void SVMFaceClassifier::setSVMParams(const int kernelType, const double degree, const double gamma, const double coef0)
{
	if (svmParams) cvFree(&svmParams);
	svmParams = new CvSVMParams(CvSVM::C_SVC, kernelType, degree, gamma, coef0, 1.0, 0.5, 0.1, NULL, cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 1000, 1e-6));
}

float SVMFaceClassifier::Classify(const IplImage * const img)
{
	if (this->svm == NULL) throw std::runtime_error("Error! No model file loaded.");
	float response = this->svm->predict(CreateFeatureVector(img));
	return response;
}

std::string SVMFaceClassifier::getModelFile()
{
	return this->modelFile;
}

CvSVMParams SVMFaceClassifier::getSVMParams()
{
	return this->getSVM()->get_params();
}

CvSVM *SVMFaceClassifier::getSVM()
{
	return this->svm;
}

int SVMFaceClassifier::getWidth()
{
	return this->width;
}

int SVMFaceClassifier::getHeight()
{
	return this->height;
}

void SVMFaceClassifier::Train(const std::string modelFile, const std::list<std::string> trainFiles, const std::list<float> trainResponse)
{
	if (svmParams == NULL) throw std::runtime_error("Error! SVM parameters are not set.");

	int i, nOfSamples = trainFiles.size();
	CvMat *dataMtx = cvCreateMat(nOfSamples, nOfFeatures, CV_32FC1);
	CvMat *responseMtx = cvCreateMat(nOfSamples, 1, CV_32FC1);
	CvMat *fv = cvCreateMatHeader(1, nOfFeatures, CV_32FC1);
	IplImage *img;
	float *row;
	std::cout << "Creating training data...";
	std::list<std::string>::const_iterator fileItr;
	std::list<float>::const_iterator responseItr;
	for (i = 0, fileItr = trainFiles.begin(), responseItr = trainResponse.begin(); i < nOfSamples; i++, fileItr++, responseItr++) {
		img = cvLoadImage(fileItr->c_str());
		if (img == NULL) {
			std::cout << "Error! File is not an image: " << fileItr->c_str() << ".\n";
			exit(-1);
		}
		cvmSet(responseMtx, i, 0, *responseItr);
		row = (float*)cvPtr2D(dataMtx, i, 0);
		cvSetData(fv, row, dataMtx->step);
		CreateFeatureVector(img, fv);
		cvReleaseImage(&img);
		std::cout << ".";
	}
	cvReleaseMatHeader(&fv);
//		misc::mtxToString("DATAMTX:", dataMtx);		system("pause");
	std::cout << "\nTraining svm... ";
	this->svm = new CvSVM(dataMtx, responseMtx, 0, 0, *svmParams);
	std::cout << "done\nSaving svm... ";
	svm->save(modelFile.c_str());
	std::cout << "done\n";
	cvReleaseMat(&dataMtx);
	cvReleaseMat(&responseMtx);
	cvReleaseMat(&fv);
}

void SVMFaceClassifier::Test(const std::string modelFile, const std::list<std::string> testFiles, const std::list<float> testResponses)
{
	int i, nOfSamples = testFiles.size(), error = 0;
	float response;
	IplImage *img;
	std::list<std::string>::const_iterator fileItr;
	std::list<float>::const_iterator responseItr;
	std::cout << "\nLoading modelfile...";
	if (svm) cvFree(&svm);
	this->svm = new CvSVM();
	this->svm->load(modelFile.c_str());
	std::cout << "done\nTesting svm...\n";
	nOfSamples = testFiles.size();
	for (i = 0, fileItr = testFiles.begin(), responseItr = testResponses.begin(); i < nOfSamples; i++, fileItr++, responseItr++) {
		std::cout << (*fileItr).c_str() << ": ";
		img = cvLoadImage((*fileItr).c_str());
		response = Classify(img);
		if (response != *responseItr) {
			error++;
			std::cout << "ERROR!\n";
		} else std::cout << "OK.\n";
		cvReleaseImage(&img);
	}
	std::cout << "\nAccuracy: " << 100.0 - (100.0 * error / nOfSamples) << "%\n";
	std::cout << "Ready.\n";
}

}
