// InteractiveBillboard.cpp : main project file.

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

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

#define FRAME_THRESHOLD 5

class Ad {
public:
	std::string image;
	std::vector<std::string> negresponse, posresponse;
	Ad(const std::string image, const std::vector<std::string> negresponse, const std::vector<std::string> posresponse);
};

Ad::Ad(const std::string image, const std::vector<std::string> negresponse, const std::vector<std::string> posresponse) {
	this->image = image;
	this->negresponse = negresponse;
	this->posresponse = posresponse;
}

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 state = 0, range = 0;

	int i;
	std::string configFile = "../../.config/Billboard.conf";
	std::stringstream ss;

	std::list<face::FaceClassifier*> faceClassifierList;
	std::map<std::string, float> response;

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

	// load advertisements

	std::list<Ad*> ads;
	std::list<Ad*>::iterator adItr;

	for (i = 1;; i++) {
		face::utils::ConfigItem configItem;
		ss.str("");
		ss << "ad" << i;
		configItem = config["ads"][ss.str()];
		if (configItem["image"].get().compare("") == 0) break;

		std::vector<std::string> negresponse, posresponse;

		std::vector<std::string> tokens;
		
		face::utils::StringUtils::tokenize(configItem["conditions"]["negresponses"].get(), tokens, " ,", true, " ,");
		negresponse = tokens;

		tokens.clear();

		face::utils::StringUtils::tokenize(configItem["conditions"]["posresponses"].get(), tokens, " ,", true, " ,");
		posresponse = tokens;

		ads.push_back(new Ad(configItem["image"].get(), negresponse, posresponse));
	}

/*
	for (adItr = ads.begin(); adItr != ads.end(); adItr++) {
		std::cout << (*adItr)->image << "\n";
		std::vector<std::string>::iterator resItr;
		for (resItr = ((*adItr)->negresponse).begin(); resItr != ((*adItr)->negresponse).end(); resItr++) {
			std::cout << "neg: " << (*resItr).c_str() << "\n";
		}
		for (resItr = ((*adItr)->posresponse).begin(); resItr != ((*adItr)->posresponse).end(); resItr++) {
			std::cout << "pos: " << (*resItr).c_str() << "\n";
		}
		std::cout << std::endl;
	}*/

	//

	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 << "\nInput: " << inputFile.c_str() << "\n";
	std::cout << "Configuration: " << configFile.c_str() << "\n";

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

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

	IplImage *img, *subImg, *info;

	cvNamedWindow("inputImg", CV_WINDOW_AUTOSIZE);
	cvDestroyWindow("inputImg");
	cvNamedWindow("info", CV_WINDOW_AUTOSIZE);
	cvDestroyWindow("info");
	cvNamedWindow("ad", CV_WINDOW_AUTOSIZE);
	cvDestroyWindow("ad");

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

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


		if (cvWaitKey(10) >= 0) break;

		seq = faceDetector.DetectFaces(img);

		face = (CvRect*)cvGetSeqElem(seq, 0);

		if (face) misc::drawRect(img, face);
		cvShowImage("inputImg", img);

		if (state == 2 || state == 3) {
			for (adItr = ads.begin(); adItr != ads.end(); adItr++) {
				bool conditionsOK = true;
				std::vector<std::string>::iterator tmpItr;
				for (tmpItr = (*adItr)->negresponse.begin(); tmpItr != (*adItr)->negresponse.end(); tmpItr++) {
					if (response[(*tmpItr)] == 1.0) {
						conditionsOK = false;
						break;
					}
				}
				
				if (!conditionsOK) continue;
				for (tmpItr = (*adItr)->posresponse.begin(); tmpItr != (*adItr)->posresponse.end(); tmpItr++) {
					if (response[(*tmpItr)] == 0.0) {
						conditionsOK = false;
						break;
					}
				}
				if (!conditionsOK) continue;
				if (conditionsOK) {
					cvShowImage("ad", cvLoadImage((*adItr)->image.c_str()));
					break;
//					std::cout << "ad with " << (*adItr)->image << " is ok\n";
				}
			}
		}

		if (state == 0) {
			if (face) {
				subImg = misc::clipSubImg(img, face);
				misc::drawRect(img, face);

				cvSet(info, CV_RGB(0, 0, 0));
				for ( i = 0, itr = faceClassifierList.begin(); itr != faceClassifierList.end(); i++, itr++) {
					prediction = (*itr)->Classify(subImg);
					response[(*itr)->getName()] = prediction;
					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);
				cvShowImage("info", info);
				state = 1;
				continue;
			} else {
				cvDestroyWindow("ad");
				ss.str("unavailable");
				cvSet(info, CV_RGB(0, 0, 0));
				cvPutText (info, ss.str().c_str(), cvPoint(10, 50), &font, CV_RGB(255, 255, 0));
				cvShowImage("info", info);
				continue;
			}
		}

		if (state == 1) {
			if (range == FRAME_THRESHOLD) {
				state = 2;
				continue;
			}
			if (face) {
				range++;
				continue;
			} else {
				state = 0;
				range = 0;
				continue;
			}
		}

		if (state == 2) {
			if (!face) state = 3;
		}

		if (state == 3) {
			if (face) {
				range = FRAME_THRESHOLD;
				state = 2;
				continue;
			} else {
				if (range == 0) {
					state = 0;
					continue;
				} else range--;
			}
		}
	}
	cvReleaseCapture(&capture);
	cvDestroyWindow("inputImg");
	cvDestroyWindow("info");
	cvDestroyWindow("ad");
	return 0;
}
