 // SVMUtil.cpp : main project file.

#include <FaceClassifier/miscellaneous.h>
#include <FaceClassifier/WholeSVMFaceClassifier.h>
#include <FaceClassifier/LBPSVMFaceClassifier.h>

#include <iostream>
#include <fstream>

#include <utils/configuration.h>
#include <utils/stringutils.h>
#include <utils/fsutils.h>


void loadData(char *positive, char *negative, std::list<std::string> &files, std::list<float> &responses)
{
	std::string line;
	ifstream filepos;
	filepos.open(positive);
	while (filepos>>line){
		files.push_back(line);
		responses.push_back(1);
	}
	filepos.close();

	ifstream fileneg;
	fileneg.open(negative);
	while (fileneg>>line){
		files.push_back(line);
		responses.push_back(0);
	}
	fileneg.close();
}

face::FaceClassifier *createClassifier(face::utils::Configuration config, bool training = false)
{
	std::string classifierType = config["classifierType"].get();
	std::map<float, std::string> classLabel;
	classLabel[0.0] = "no";
	classLabel[1.0] = "yes";

	if (classifierType == "WholeSVMFaceClassifier") {
		face::WholeSVMFaceClassifier *classifier = new face::WholeSVMFaceClassifier(
			"",
			classLabel,
			face::utils::StringUtils::parse<int>(config["image"]["width"].get()),
			face::utils::StringUtils::parse<int>(config["image"]["height"].get())
		);
		if (training) classifier->setSVMParams(
			face::utils::StringUtils::parse<int>(config["svmparameters"]["kernelType"].get()),
			face::utils::StringUtils::parseDouble(config["svmparameters"]["degree"].get()),
			face::utils::StringUtils::parseDouble(config["svmparameters"]["gamma"].get()),
			face::utils::StringUtils::parseDouble(config["svmparameters"]["coef0"].get())
		);
		return classifier;
	}
	if (classifierType == "LBPSVMFaceClassifier") {
		std::vector<std::string> tokens;
		
		face::utils::StringUtils::tokenize(
			config["lbpparameters"]["splits"].get(),
			tokens,
			" ,",
			true,
			" \t,()"
			);

		std::list<SPLIT> split;
		unsigned int j = 0;

		SPLIT currentSplit;

		for (j = 0; j < tokens.size() / 2; j++) {
			currentSplit.w = face::utils::StringUtils::parse<int>(tokens[j * 2]);
			currentSplit.h = face::utils::StringUtils::parse<int>(tokens[j * 2 + 1]);
			split.push_back(currentSplit);
		}

		face::LBPSVMFaceClassifier *classifier = new face::LBPSVMFaceClassifier(
			"",
			classLabel,
			face::utils::StringUtils::parse<int>(config["image"]["width"].get()),
			face::utils::StringUtils::parse<int>(config["image"]["height"].get()),
			split,
			face::utils::StringUtils::parse<int>(config["lbpparameters"]["p"].get()),
			face::utils::StringUtils::parseDouble(config["lbpparameters"]["r"].get())
		);
		if (training) classifier->setSVMParams(
			face::utils::StringUtils::parse<int>(config["svmparameters"]["kernelType"].get()),
			face::utils::StringUtils::parseDouble(config["svmparameters"]["degree"].get()),
			face::utils::StringUtils::parseDouble(config["svmparameters"]["gamma"].get()),
			face::utils::StringUtils::parseDouble(config["svmparameters"]["coef0"].get())
		);
		return classifier;
	}

	return 0;
}

void saveDescriptor(face::utils::Configuration config, std::string descrFile)
{
	std::string classifierType = config["classifierType"].get();

	if (classifierType == "WholeSVMFaceClassifier") {
		face::utils::Configuration descr;

		descr["classifierType"].set(config["classifierType"].get() );
		descr["image"]["width"].set(config["image"]["width"].get() );
		descr["image"]["height"].set(config["image"]["height"].get() );

		descr.save(descrFile);
	}
	if (classifierType == "LBPSVMFaceClassifier") {
		face::utils::Configuration descr;

		descr["classifierType"].set(config["classifierType"].get() );
		descr["image"]["width"].set(config["image"]["width"].get() );
		descr["image"]["height"].set(config["image"]["height"].get() );
		descr["lbpparameters"]["splits"].set(config["lbpparameters"]["splits"].get() );
		descr["lbpparameters"]["p"].set(config["lbpparameters"]["p"].get() );
		descr["lbpparameters"]["r"].set(config["lbpparameters"]["r"].get() );

		descr.save(descrFile);
	}
}

void printUsage(){
	std::cout << "SVMUtil v1.0" << std::endl
			<< "usage: " << std::endl
			<< std::endl
			<< "  SVMUtil train positive-train-files negative-train-files model-output descr-output config-file" << std::endl
			<< "  SVMUtil test positive-test-files negative-test-files model-file descr-file" << std::endl
			<< "  SVMUtil lbphist test-files model-file descr-file out-dir" << std::endl
			<< std::endl;
}

// command args
// train d:\work\f.a.c.e\.scripts\FaceClassifier\gender+.train d:\work\f.a.c.e\.scripts\FaceClassifier\gender-.train d:\work\f.a.c.e\.config\svmutil.w_svm.conf d:\work\f.a.c.e\.scripts\FaceClassifier\gender.w_svm.model d:\work\f.a.c.e\.scripts\FaceClassifier\gender.w_svm.descr
// test d:\work\f.a.c.e\.scripts\FaceClassifier\gender+.test d:\work\f.a.c.e\.scripts\FaceClassifier\gender-.test d:\work\f.a.c.e\.scripts\FaceClassifier\gender.w_svm.model d:\work\f.a.c.e\.scripts\FaceClassifier\gender.w_svm.descr

// train d:\work\f.a.c.e\.scripts\FaceClassifier\gender+.train d:\work\f.a.c.e\.scripts\FaceClassifier\gender-.train d:\work\f.a.c.e\.config\svmutil.lbp_svm.conf d:\work\f.a.c.e\.scripts\FaceClassifier\gender.lbp_svm.model d:\work\f.a.c.e\.scripts\FaceClassifier\gender.lbp_svm.descr
// test d:\work\f.a.c.e\.scripts\FaceClassifier\gender+.test d:\work\f.a.c.e\.scripts\FaceClassifier\gender-.test d:\work\f.a.c.e\.scripts\FaceClassifier\gender.lbp_svm.model d:\work\f.a.c.e\.scripts\FaceClassifier\gender.lbp_svm.descr

int main(int argc, char *argv[])
{
	if (std::string(argv[1]) == "train" && argc == 7) {
		std::cout << "train started" << std::endl;

		if (!face::utils::FileSystemUtils::exists(std::string(argv[2]))) {
			std::runtime_error("Positive set not found!\n");
		}
		if (!face::utils::FileSystemUtils::exists(std::string(argv[3]))) {
			std::runtime_error("Negative set not found!\n");
		}

		// fill train positive and negative file lists
		std::list<std::string> trainFiles;
		std::list<float> trainResponses;
		loadData(argv[2], argv[3], trainFiles, trainResponses);

		// load descriptor
		face::utils::Configuration config;
		config.load(argv[6]);

		//set model file output path
		std::string modelFile = std::string(argv[4]);

		// create classifier and run train
		face::FaceClassifier *classifier = createClassifier(config, true);
		classifier->Train(modelFile, trainFiles, trainResponses);
		delete classifier;

		//write descriptor
		std::string descrFile = argv[5];
		saveDescriptor(config, descrFile);

	} else if (std::string(argv[1]) == "test" && argc == 6) {
		std::cout << "test started" << std::endl;

		if (!face::utils::FileSystemUtils::exists(std::string(argv[5]))) {
			std::runtime_error("Error! Descriptor file not found: " + std::string(argv[5]) + ".\n");
		}
		if (!face::utils::FileSystemUtils::exists(std::string(argv[2]))) {
			std::runtime_error("Positive set not found!\n");
		}
		if (!face::utils::FileSystemUtils::exists(std::string(argv[3]))) {
			std::runtime_error("Negative set not found!\n");
		}			
		std::list<std::string> testFiles;
		std::list<float> testResponses;		
		loadData(argv[2], argv[3], testFiles, testResponses);
		std::list<std::string>::iterator fileItr;
		std::list<float>::iterator responseItr;
		std::stringstream ss;
		std::ofstream file;
		IplImage *img;
		int prediction;

		// load descriptor
		face::utils::Configuration descr;
		descr.load(argv[5]);

		//set model file path
		std::string modelFile = std::string(argv[4]);
		if (!face::utils::FileSystemUtils::exists(modelFile)) {
			std::runtime_error( std::string("Error! Model file not found: ") + modelFile + ".\n");
		}

		// create classifier and run test
		face::FaceClassifier *classifier = createClassifier(descr);
		((face::SVMFaceClassifier*)classifier)->LoadModelFile(modelFile);

		int nOfNegSamples;
		int nOfPosSamples;
		double tp = 0;
		double tn = 0;
		double fp = 0;
		double fn = 0;
		std::string inputFile;
		file << "filename:\tclass\tprediction\n";
		for (fileItr = testFiles.begin(), responseItr = testResponses.begin();
			fileItr != testFiles.end(), responseItr != testResponses.end(); fileItr++, responseItr++) {
			inputFile = (*fileItr);
			img = cvLoadImage(inputFile.c_str());
			prediction = (int)(classifier->Classify(img));
			ss.str("");
			ss << inputFile.substr(inputFile.find_last_of("\\") + 1) << "\t" << (int)(*responseItr) << "\t" << prediction << "\n";
			if ((int)(*responseItr) == prediction) {
				if (prediction == 0) tn++; else tp++;
			} else {
				if (prediction == 0) fn++; else fp++;
			}
			std::cout << ss.str();
			cvReleaseImage(&img);
		}
		ss.str("");
		ss << "\nclassified negative as negative:\t" << tn << "\n";
		ss << "classified positive as positive:\t" << tp << "\n";
		ss << "misclassified negative as positive:\t" << fp << "\n";
		ss << "misclassified positive as negative:\t" << fn << "\n\n";
		nOfNegSamples = (int)tn + (int)fp;
		nOfPosSamples = (int)fn + (int)tp;
		tn /= nOfNegSamples;
		fp /= nOfNegSamples;
		fn /= nOfPosSamples;
		tp /= nOfPosSamples;
		ss << "accuracy:\t" << ((tn + tp) * 50.0) << "%\n";
		std::cout << ss.str();
		delete classifier;
	} else if (std::string(argv[1]) == "lbphist" && argc == 6) {
		std::list<std::string> testFiles;
		std::string resultFile;
		std::stringstream ss;
		IplImage *img;
		misc::getLines(std::string(argv[2]), &testFiles);

		// load descriptor
		face::utils::Configuration descr;
		descr.load(argv[4]);

		//set model file path
		std::string modelFile = std::string(argv[3]);
		if (!face::utils::FileSystemUtils::exists(modelFile)) {
			std::runtime_error( std::string("Error! Model file not found: ") + modelFile + ".\n");
		}

		// create classifier and run test
		face::FaceClassifier *classifier = createClassifier(descr);
		((face::SVMFaceClassifier*)classifier)->LoadModelFile(modelFile);
		face::LBPSVMFaceClassifier* lbpClassifier = (face::LBPSVMFaceClassifier*)classifier;

		for (std::list<CvRect>::iterator itr = lbpClassifier->rect.begin(); itr != lbpClassifier->rect.end(); itr++) {
			ss.str("");
			ss << (*itr).x << "," << (*itr).y << "," << (*itr).width << "," << (*itr).height << "\n";
			misc::appendFile(std::string(argv[5]) + "\\rects.txt", ss.str());
		}

		int linelength = (int)pow(2.0, ((face::LBPSVMFaceClassifier*)classifier)->p);

		for (std::list<std::string>::iterator itr = testFiles.begin(); itr != testFiles.end(); itr++) {
			CvMat *fv;
			img = cvLoadImage((*itr).c_str());
			resultFile = std::string(argv[5]) + (*itr).substr((*itr).find_last_of("\\")) + ".txt";
			fv = classifier->CreateFeatureVector(img);
			float *ptr = (float*)fv->data.ptr;
			int i = 0;
			for (std::list<SPLIT>::iterator itr = lbpClassifier->split.begin(); itr != lbpClassifier->split.end(); itr++) {
				for (int j = 0; j < lbpClassifier->rectOfSplit[i]; j++) {
					ss.str("");
					for (int k = 0; k < linelength; k++) {
						ss << (*ptr) << " ";
						ptr++;
					}
					misc::appendFile(resultFile, ss.str() + "\n");
				}
				i++;
			}
			std::cout << resultFile.c_str() << ": OK\n";
		}
		delete classifier;
	} else printUsage();
	return 0;
}
