// FaceClassiferUpdater.cpp : main project file.

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

#include <iostream>
#include <sstream>
#include <fstream>

CvSVM *incSVM(face::SVMFaceClassifier * const classifier, const IplImage * const sample, const float label) {	
	int i;
	std::cout << "Updating model file... ";

	CvFileStorage *fs = cvOpenFileStorage(classifier->getModelFile().c_str(), 0, CV_STORAGE_READ);
	CvFileNode *node = cvGetRootFileNode(fs);
	node = (CvFileNode*)cvGetSeqElem( node->data.seq, 0 );
	node = cvGetFileNodeByName(fs, node, "decision_functions");
	node = (CvFileNode*)cvGetSeqElem( node->data.seq, 0 );	
	int sv_count = cvReadIntByName(fs, node, "sv_count");
	node = cvGetFileNodeByName(fs, node, "alpha");
	double *alpha = (double*)malloc((sv_count) * sizeof(double));
	cvReadRawData(fs, node, alpha, "d");

	CvMat *responseMtx = cvCreateMat(sv_count + 1, 1,CV_32FC1);
	CvMat *dataMtx = cvCreateMat(sv_count + 1, classifier->getNOfFeatures(), CV_32FC1);

	float *srcptr, *dstptr;
	for (i = 0; i < sv_count; i++) {
		if (alpha[i] <= 0.0) cvmSet(responseMtx, i, 0, 1.f); else cvmSet(responseMtx, i, 0, 0.f);
		srcptr = (float*)(void*)classifier->getSVM()->get_support_vector(i);
		dstptr = (float*)cvPtr2D(dataMtx, i, 0);
		CvMat *srcMtx = &cvMat(1, classifier->getNOfFeatures(), CV_32FC1, srcptr);
		CvMat *dstMtx = &cvMat(1, classifier->getNOfFeatures(), CV_32FC1, dstptr);
		cvCopy(srcMtx, dstMtx);
	}
	cvmSet(responseMtx, i, 0, label);
	dstptr = (float*)cvPtr2D(dataMtx, i, 0);
	CvMat *dstMtx = &cvMat(1, classifier->getNOfFeatures(), CV_32FC1, dstptr);
	cvCopy(classifier->CreateFeatureVector(sample), dstMtx);

	CvSVM *svm = new CvSVM(dataMtx, responseMtx, 0, 0, classifier->getSVMParams());
	svm->save(classifier->getModelFile().c_str());
	classifier->getSVM()->clear();
	classifier->getSVM()->load(classifier->getModelFile().c_str());
	std::cout << "ready.\n";
	cvReleaseMat(&dataMtx);
	cvReleaseMat(&responseMtx);
	return svm;
}

void initClassifiers(face::utils::Configuration config, std::list<face::FaceClassifier*> * const faceClassifierList) {
	int i;
	face::utils::ConfigItem configItem;
	face::FaceClassifier *classifier;
	std::stringstream ss;
	for (i = 1;; i++) {
		ss.str("");
		ss << "classifier" << i;
		configItem = config["classifiers"][ss.str()];

		if (configItem["name"].get().compare("") == 0) break;

		face::utils::Configuration descrConfig;
		descrConfig.load(configItem["descrfile"].get());

		std::string classifierType = descrConfig["classifierType"].get();

		std::map<float, std::string> classLabel;
		classLabel[0.0] = configItem["neg"].get();
		classLabel[1.0] = configItem["pos"].get();

		if (classifierType == "WholeSVMFaceClassifier") {
			classifier = new face::WholeSVMFaceClassifier(
				configItem["name"].get(),
				classLabel,
				face::utils::StringUtils::parse<int>(descrConfig["image"]["width"].get()),
				face::utils::StringUtils::parse<int>(descrConfig["image"]["height"].get()),
				configItem["modelfile"].get()
			);
		}
		if (classifierType == "LBPSVMFaceClassifier") {
			std::vector<std::string> tokens;
		
			face::utils::StringUtils::tokenize(
				descrConfig["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);
			}

			classifier = new face::LBPSVMFaceClassifier(
				configItem["name"].get(),
				classLabel,
				face::utils::StringUtils::parse<int>(descrConfig["image"]["width"].get()),
				face::utils::StringUtils::parse<int>(descrConfig["image"]["height"].get()),
				split,
				face::utils::StringUtils::parse<int>(descrConfig["lbpparameters"]["p"].get()),
				face::utils::StringUtils::parseDouble(descrConfig["lbpparameters"]["r"].get()),
				configItem["modelfile"].get()
			);
		}
		faceClassifierList->push_back(classifier);
		std::cout << "\n";
	}
}

int main(int argc, char* argv[])
{
	int i;

	std::string inputFile;
	std::string configFile;
	if (argc == 1) configFile = "../../.config/faceClassifierUpdater.default.conf"; else configFile = std::string(argv[1]);

	std::list<face::FaceClassifier*> faceClassifierList;

	face::utils::Configuration config;
	config.load(configFile);
	initClassifiers(config, &faceClassifierList);

	misc::FaceDetector faceDetector(config["haarcascade"].get());
	if (config["input"].get().compare("") != 0) inputFile = config["input"].get();

	CvCapture *capture = cvCaptureFromCAM(0);
	if (capture == NULL) {
		capture = cvCaptureFromFile(inputFile.c_str());
		if (!face::utils::FileSystemUtils::exists(inputFile)) {
			std::runtime_error( std::string("Error! Input file not found: ") + inputFile + ".\n");
			exit(-1);
		} else
		if (capture == NULL) {
			std::cout << "Warning! Unable to capture from file: " << inputFile.c_str() << ".\n";
			exit(-1);
		}
	}

	std::cout << "\nConfiguration: " << configFile.c_str();
	std::cout << "\n\n\n";

	CvFont font;
	cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX, 1.0, 1.0, 0, 1);
	float prediction;
	std::string classLabel;

	CvSeq *seq;
	CvRect *firstFace;
	std::stringstream ss;
	std::list<face::FaceClassifier*>::iterator itr;

	IplImage *img, *subImg, *info;

	cvNamedWindow("inputImg", CV_WINDOW_AUTOSIZE);
	cvNamedWindow("info", CV_WINDOW_AUTOSIZE);
	info = cvCreateImage(cvSize(350, faceClassifierList.size() * 50 + 30 ), 8, 3);

	for (;;) {
		img = cvQueryFrame(capture);
		if (img == NULL) break;

		cvSet(info, CV_RGB(0, 0, 0));

		seq = faceDetector.DetectFaces(img);
		firstFace = (CvRect*)cvGetSeqElem(seq, 0);

		if (firstFace) {
			subImg = misc::clipSubImg(img, firstFace);
			misc::drawRect(img, firstFace);

			for ( i = 0, itr = faceClassifierList.begin(); itr != faceClassifierList.end(); i++, itr++) {
				prediction = (*itr)->Classify(subImg);
				ss.str("");
				classLabel = (*itr)->getClassLabel(prediction);
				ss << (*itr)->getName() << "=" << ((classLabel.compare("") == 0) ? "?" : classLabel);

				cvPutText (info, ss.str().c_str(), cvPoint(10, (i + 1) * 50), &font, CV_RGB(255, 255, 0));

			}
			cvReleaseImage(&subImg);
		} else {
			ss.str("unavailable");
			cvPutText (info, ss.str().c_str(), cvPoint(10, 50), &font, CV_RGB(255, 255, 0));
		}



		cvShowImage("inputImg", img);
		cvShowImage("info", info);

/**/		

		face::FaceClassifier *classifier;

		int no;
		float label;

		switch (cvWaitKey(10)) {
			case 27:
				cvReleaseCapture(&capture);
				cvDestroyWindow("inputImg");
				cvDestroyWindow("info");
				return 0;
			case ' ':
				face::SVMFaceClassifier *svmClassifier;
				if (faceClassifierList.size() > 1) {
					printf("Classifier to retrain <0 - %i>\n", faceClassifierList.size() - 1);
					for (;;) {
						scanf("%i", &no);
						if (no >= 0 && no < faceClassifierList.size()) break;
						std::cout << "Please try again!\n";
					}
				} else no = 0;
				for (itr = faceClassifierList.begin(); no > 0; no--, itr++); classifier = *itr;

				if (dynamic_cast<face::SVMFaceClassifier*>(classifier)) {
					svmClassifier = (face::SVMFaceClassifier*)classifier;
				} else {
					std::cout << "ERROR! The given classifier is not an SVM based classifier: incrementation failed.\n";
					break;
				}

				printf("Class label < + - >\n");
				char c;
				for (;;) {
					c = cvWaitKey(0);
					if (c != '-' && c != '+') {
						std::cout << "Please try again!\n";
					} else break;

				}
				if (c == '-') label = 0.0f;
				if (c == '+') label = 1.0f;
				incSVM(svmClassifier, img, label);
				break;
			default:;
		}
/**/		
	}
}
