// FaceClassifyApp.cpp : main project file.

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

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

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;
	bool onScreen = true, udp = false, fromCam = true;
	std::string dstIP;
	int dstPort;

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

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

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

	if (config["output"]["onscreen"].get().compare("on") == 0) onScreen = 1;
	if (config["output"]["onscreen"].get().compare("off") == 0) onScreen = 0;
	if (config["output"]["udp"].get().compare("on") == 0) udp = 1;
	if (config["output"]["udp"].get().compare("off") == 0) udp = 0;
	if (udp) {
		dstIP = config["output"]["address"]["host"].get();
		dstPort = atoi(config["output"]["address"]["port"].get().c_str());
	}

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

	CvCapture *capture = cvCaptureFromCAM(0);
	if (capture == NULL) {
		fromCam = false;
		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();
	if (!fromCam) std::cout << "\nInputfile: " << inputFile.c_str();
	if (udp) {
		printf("\nUdp: IP: %s, port: %i", dstIP.c_str(), dstPort);
	}
	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, udpPacket;
	std::list<face::FaceClassifier*>::iterator itr;

	IplImage *img, *subImg, *info;

	if (onScreen) {
		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;

		if (onScreen) cvSet(info, CV_RGB(0, 0, 0));
		if (udp) udpPacket.str("");

		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);

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

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

			if (udp) {
				udpPacket << ss.str();
			}
		}

		if (udp) {
			face::comm::UDPSender udpSender(dstIP, dstPort);
			face::comm::Packet packet;
			packet << udpPacket.str();
			udpSender.send(packet);
		}

		if (onScreen) {
			cvShowImage("inputImg", img);
			cvShowImage("info", info);
		}

		if (cvWaitKey(10) >= 0) break;
	}
	cvReleaseCapture(&capture);
	if (onScreen) {
		cvDestroyWindow("inputImg");
		cvDestroyWindow("info");
	}
	return 0;
}
