#ifndef EIGENFACEDETECTOR_H_
#define EIGENFACEDETECTOR_H_

#include <fstream>
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

#include "MyExceptions.h"
#include <windows.h>
typedef double MyType;

namespace thermoCamera {

	/**
	* @class EigenFaceDetector
	* @brief EigenFaceDetector is able to find face in thermal images. 
	* @author Matej Roman (student of BUT)
	*/

	class EigenFaceDetector {

	public:

		/////////////////////////////////// Enum /////////////////////////////////////

		enum class Flag {
			disabled = 0,
			enabled = 1,
			computing = 2
		};

		/////////////////////////////////// Struct /////////////////////////////////////

		/**
		* x0-left , y0-top , x1 - width , y2 - height
		*/
		typedef struct {
			int x0, y0, x1, y1;
			float score;
		} Bbox;

		/////////////////////////////////// Constructors /////////////////////////////

		/**
		* Standard constructor
		*/
		EigenFaceDetector() :
			_isInit(false),
			_flag(Flag::disabled),
			_inImage(cv::UMat()),
			//_minErr(2),
			_minErr(3),
			//_minErr(2.8),
			_maxWeight(0),
			_K(20),
			_mean(cv::UMat()),
			_eigenvectors(cv::UMat()),
			_weights(cv::UMat()),
			_index(NULL)
		{}

		/////////////////////////////////// Destructor /////////////////////////////

		/**
		* Destructor
		*/
		~EigenFaceDetector()
		{
			if (_index)     delete[]  _index;
		}

		/////////////////////////////////// Methods /////////////////////////////////

		/**
		* Init the detector, can take a while
		*/
		void Init();

		/**
		* Find face in thermal image
		* @param[in] image is thermal image CV_8UC1
		* @param[in] rect is an array of rectangle. Will be filled, max number of faces is 4
		* @param[in] numOfRect is size of rect, must be  1,2,3 or 4
		* @param[in] numOfFace is the number of detecting faces
		*/
		bool FindFace(cv::UMat& image, cv::Rect* rect, const int numOfRect, int& numOfFace);

		/////////////////////////////////// Setters /////////////////////////////////

		void Abort() { _flag = Flag::disabled; }

		/////////////////////////////////// Getters /////////////////////////////////

		bool isInit() { return _isInit; }

	private:

		std::vector<Bbox> _bbox;

		bool _isInit;
		Flag _flag;

		cv::UMat _inImage;

		float _minErr;
		double _maxWeight;
		int _K;
		cv::UMat _mean;
		cv::UMat _eigenvectors;
		cv::UMat _weights;

		const char* _locationOfMean = "C:\\Images\\Locations\\mean.yml";
		const char* _locationOfWeights = "C:\\Images\\Locations\\weight.yml";
		const char* _locationOfEigenVectors = "C:\\Images\\Locations\\eigenvectors.yml";
		const char* _locationOfMaxWeight = "C:\\Images\\Locations\\maxWeight.txt";

		int _windows_n_rows = 40;
		int _windows_n_cols = 40;
		int _StepSlide = 20;
		int _eigenFaceSize = 50;
		/////////////////////////////////// Private method /////////////////////////////////

		void _LoadMatrix(const char* filePath, cv::UMat& mat, const char* name);

		void _MatrixToVector(cv::UMat& vector, cv::UMat& matrix);

		void _VectorToMatrix(cv::UMat& vector, cv::UMat& matrix, unsigned int N);

		// width original image, height original image
		int _SlideWindow(cv::UMat& img, int& numOfRect,int width, int height, bool show);

		bool _ComputeError(cv::UMat& input, cv::UMat& mean, cv::UMat& eigenvectors, int K, float minTh, float& score);

		void _ComputePhiMatrix(cv::UMat& face, cv::UMat& mean, cv::UMat& phi, int type, const cv::Size size, const uint MaxNumOfPics);

		void _AddNewRectangle(cv::Rect& windows, int& numActualRect,float score);

		void _ComputeWeights(cv::UMat& phi, cv::UMat& eigenvectors, cv::UMat& weights, int K);

		void _ReconstructFromWeights(cv::UMat& phi, cv::UMat& mean, cv::UMat& weights, cv::UMat& eigenvectors, int K);

		void _ReconstructFromPhi(cv::UMat& phi, cv::UMat& mean);

		void _transformLinear2D(int& x, int& y, int& xReal, int& yReal, float inlox, float inupx, float outlox, float outupx, float inloy, float inupy, float outloy, float outupy);

		void _GetRectFromBbox(cv::Rect& rect, const  int whichOne);

		void _StretchWindow(cv::Rect& input, cv::UMat& img, int newWidth, int newHeight);

		/////////////////////////////////// Fast non-maximum suppression //////////////////

		#define fast_max(x,y) (x - ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1))))
		#define fast_min(x,y) (y + ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1))))

		unsigned int* _index;

		void sort_idx(const Bbox* rects, int* idxes, int n);

		int sort_stable(int* arr, int n);

		int fast_nms(Bbox* rects, int num, float overlap_th);
	};

}

#endif
