/*
    Questo script esegue l'analisi di un video fornito per rilevare le discontinuità
    che vengono trovate.

    Tutte le informazioni necessarie all'agoritmo si possono individuare nei file XML
    all'interno della cartella config.

    @author Maniero Mirco, Ylenia Grava
    @version 2.0
    @date 01-04-2019
*/
#include <filesystem>
#include <fstream>
#include <iostream>
#include <vector>

#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/timeb.h>
#include <sys/types.h>
#include <time.h>

#include <boost/uuid/uuid.hpp>            // uuid class
#include <boost/uuid/uuid_generators.hpp> // generators
#include <boost/uuid/uuid_io.hpp>         // streaming operators etc.
#include <boost/lexical_cast.hpp>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

#include <nlohmann/json.hpp>

#include "rapidxml-1.13/rapidxml.hpp"

#include "utility.h"
#include "forAudioAnalyser.h"

namespace fs = std::__fs::filesystem;
using namespace cv;
using namespace rapidxml;
using namespace std;
using json = nlohmann::json;



/*
------------------------------------------------------------------------------
VARIABLES
------------------------------------------------------------------------------
*/

const char* window = "Select ROI";
bool callback, rotated = false;
bool drag = false, pinchRollerSelection = false, savingPinchRoller = false, pinchRollerRect = false;
bool savingBrand = false;
cv::Point point1, point2, point1b, point2b,point1c, point2c, point3, point4, point5, M;
cv::Mat myFrame;
int x_l, x_r, y_u, y_d, xM, yM, y_m1, y_m2, BD; // Processing rectangle coordinates
int xc_l, xc_r, yc_u, yc_d; // PinchRoller rectangle coordinates
int processingSelection = true;
Point2f vertices[4];
double mA, qA, mB, qB, mC, qC, mD, qD; // Rotated rectangle variables
int rotationEnabled = false;
int x_r_orig, x_l_orig, y_u_orig, y_d_orig;
float mediaPrevFrame = 0;
bool firstBrand = true;	// The first frame containing brands on tape must be saved
int firstTime = 1; // First analysis
float firstBrandInstant = 0;

// config.json parameters
bool brands;
std::string irregularityFileInputPath;
std::string outputPath;
std::string videoPath;
float speed;
float thresholdPercentual;
float thresholdPercentualPinchRoller;
// JSON files
json configurationFile;
json irregularityFileInput;
json irregularityFileOutput1;
json irregularityFileOutput2;
// RotatedRect identifying the processing area
RotatedRect rect;



/*
------------------------------------------------------------------------------
PREPROCESSING
------------------------------------------------------------------------------
*/


// void mouseHandler(int event, int x, int y, int flags, void* param) {

// 	// Instructions
// 	if (processingSelection && event == EVENT_LBUTTONUP && drag)
// 		std::cout << "\033[32mProcessing area identified.\033[0m\nYou can modify the rectangle by clicking within its area and dragging the mouse.\nAfterwards, you can press CTRL and then use the mouse to rotate the rectangle.\n\033[36mPress 'y' to confirm\033[0m or any other key to exit the program.\n" << std::endl;
// 	if (pinchRollerSelection && event == EVENT_LBUTTONUP && drag)
// 		std::cout << "\033[32mPinchRoller area identified.\n\033[36mPress 'y' to confirm\033[0m or any other key to exit the program.\n" << std::endl;

// 	// Initial drawing of a rectangle
// 	if (processingSelection || pinchRollerSelection) {

// 		if (event == EVENT_LBUTTONDOWN && !drag) {
// 	    	point1 = cv::Point(x, y);
// 	    	drag = true;
// 			if (pinchRollerSelection) {
// 				xc_l = x;
// 				yc_u = y;
// 			} else {
// 				x_l = x;
// 				y_u = y;
// 			}
// 	    }

// 		if (event == EVENT_MOUSEMOVE && drag) {
// 			// Mouse dragged, ROI being selected
//    			cv::Mat img1 = myFrame.clone();
// 			if (img1.empty()) {
// 				std::cout << "Empty frame." << std::endl;
// 			}
// 			point2 = cv::Point(x, y);
// 			cv::rectangle(img1, point1, point2, cv::Scalar(255, 255, 0), 2);
// 		   	cv::imshow(window, img1);
//     	}

//   		if (event == EVENT_LBUTTONUP && drag) {
//     	    cv::Mat img2 = myFrame.clone();
//     	    point2 = cv::Point(x, y);
//     	    drag = false;
// 			cv::rectangle(img2, point1, point2, cv::Scalar(255, 255, 0), 2);
//     	    cv::imshow(window, img2);
//     	    callback = true;
// 			processingSelection = false;
// 			if (pinchRollerSelection) {
// 				xc_r = x;
// 				yc_d = y;
// 				pinchRollerSelection = false;
// 				pinchRollerRect = true;
// 			} else {
// 				x_r = x;
// 				y_d = y;
// 			}

// 			x_l_orig = x_l;
// 			x_r_orig = x_r;
// 			y_u_orig = y_u;
// 			y_d_orig = y_d;
// 		}

// 	} else if (!pinchRollerRect) { // PinchRoller rectangle cannot be rotated or modified and after its definition it is not possible to modify again the processing rectangle
// 		// If CTRL key is pressed, the user wants to rotate the rectangle
// 		if (flags == cv::EVENT_FLAG_CTRLKEY && !rotationEnabled) {
// 			rotationEnabled = true;
// 			std::cout << "Rotation enabled." << std::endl;
// 			// Finding original rectangle center M
// 			xM = (x_l_orig + x_r_orig)/2;
// 			yM = (y_u_orig + y_d_orig)/2;
// 			M = cv::Point(xM,yM);
// 		}
// 		if (rotationEnabled) {
// 			if (event == EVENT_LBUTTONDOWN && !drag) {
// 				drag = true;
// 				point1 = cv::Point(x,y);
// 			}
// 			if (event == EVENT_MOUSEMOVE && drag) {
// 				cv::Mat img1 = myFrame.clone();
// 				point2 = cv::Point(x,y);
// 				RotatedRect rRect = RotatedRect(Point2f(xM, yM), Size2f(x_r_orig - x_l_orig, y_d_orig - y_u_orig), point2.x - point1.x);
// 				rRect.points(vertices);
// 				for (int i = 0; i < 4; i++)
// 					cv::line(img1, vertices[i], vertices[(i + 1) % 4], Scalar(255, 255, 0));
// 				cv::imshow(window, img1);
// 			}
// 			if (event == EVENT_LBUTTONUP && drag) {
// 				rotated = true;
// 				drag = false;
// 				cv::Mat img2 = myFrame.clone();
// 				point2 = cv::Point(x, y);
// 				RotatedRect rRect = RotatedRect(Point2f(xM,yM), Size2f(x_r_orig - x_l_orig, y_d_orig - y_u_orig), point2.x - point1.x);
// 				Point2f vertices[4];
// 				rRect.points(vertices);
// 				for (int i = 0; i < 4; i++)
// 					cv::line(img2, vertices[i], vertices[(i + 1) % 4], Scalar(255, 255, 0), 2);
// 				cv::imshow(window, img2);

// 				double m1, q1, m2, q2, m3, q3, m4, q4;
// 				findRectBound(vertices[0], vertices[1], vertices[2], vertices[3], m1, m2, m3, m4, q1, q2, q3, q4);

// 				// Finding vertices order
// 				int ordinate[4] = {(int)vertices[0].y, (int)vertices[1].y, (int)vertices[2].y, (int)vertices[3].y};

// 				if (*std::min_element(ordinate, ordinate + 4) == (int)vertices[0].y) {
// 					y_u = vertices[0].y;	
// 					y_d = vertices[2].y;

// 					mA = m1; qA = q1; mD = m4; qD = q4;	mB = m2; qB = q2; mC = m3; qC = q3;
					
// 					if (*std::min_element(ordinate + 1, ordinate + 3) == (int)vertices[1].y) {
// 						y_m1 = vertices[1].y;
// 						y_m2 = vertices[3].y;
// 						BD = true;
// 					} else {
// 						y_m1 = vertices[3].y;
// 						y_m2 = vertices[1].y;
// 						BD = false;
// 					}
// 				} else if (*std::min_element(ordinate, ordinate + 4) == (int)vertices[1].y) {
// 					y_u = vertices[1].y;
// 					y_d = vertices[3].y;

// 					mA = m2; qA = q2; mD = m1; qD = q1; mB = m3; qB = q3; mC = m4; qC = q4;

// 					if (*std::min_element(ordinate + 1, ordinate + 3) == (int)vertices[2].y) {
// 						y_m1 = vertices[2].y;
// 						y_m2 = vertices[0].y;
// 						BD = true;
// 					} else {
// 						y_m1 = vertices[0].y;
// 						y_m2 = vertices[2].y;
// 						BD = false;
// 					}
// 				} else if (*std::min_element(ordinate, ordinate + 4) == (int)vertices[2].y) {
// 					y_u = vertices[2].y;
// 					y_d = vertices[0].y;

// 					mA = m3; qA = q3; mD = m2; qD = q2; mB = m4; qB = q4; mC = m1; qC = q1;

// 					if (*std::min_element(ordinate + 1, ordinate + 3) == (int)vertices[3].y) {
// 						y_m1 = vertices[3].y;
// 						y_m2 = vertices[1].y;
// 						BD = true;
// 					} else {
// 						y_m1 = vertices[1].y;
// 						y_m2 = vertices[3].y;
// 						BD = false;
// 					}
// 				} else {
// 					y_u = vertices[3].y;
// 					y_d = vertices[1].y;

// 					mA = m4; qA = q4; mD = m3; qD = q3; mB = m1; qB = q1; mC = m2; qC = q2;

// 					if (*std::min_element(ordinate + 1, ordinate + 3) == (int)vertices[0].y) {
// 						y_m1 = vertices[0].y;
// 						y_m2 = vertices[2].y;
// 						BD = true;
// 					} else {
// 						y_m1 = vertices[2].y;
// 						y_m2 = vertices[0].y;
// 						BD = false;
// 					}
// 				}	

// 				float ascisse [4] = {vertices[0].x, vertices[1].x, vertices[2].x, vertices[3].x};
// 				float min_ascissa = ascisse[3];
// 				float max_ascissa = ascisse[0];

// 				for (int j = 0; j < 4; j++) {
// 					if (ascisse[j] > max_ascissa) {
// 						max_ascissa = ascisse[j];
// 					}
// 					if (ascisse[j] < min_ascissa) {
// 						min_ascissa = ascisse[j];
// 					}					
// 				}

// 				x_r = max_ascissa;
// 				x_l = min_ascissa;

// 				rotationEnabled = false;
// 				std::cout << "End rotation.\n" << std::endl;
// 			}
// 		} else if (!rotated) { // The rectangle can be modified only before its rotation
// 			// Here the user wants to modify the rectangle
// 			if (event == EVENT_LBUTTONDOWN && !drag) {
// 				point3 = cv::Point(x,y);
// 				drag = true;
// 			}		
// 			if (event == EVENT_MOUSEMOVE && drag && !processingSelection) {
// 				// Mouse dragged, ROI being selected 
// 		        cv::Mat img4 = myFrame.clone();
// 				point4 = cv::Point(x, y);
// 				point2b = cv::Point(point4.x, y_d);	//x mouse, y_d
// 				point1b = cv::Point(point4.x, y_u);	//x mouse, y_u
// 				point1c = cv::Point(x_l, point4.y); //x_l, y mouse
// 				point2c = cv::Point(x_r, point4.y);	//x_r, y mouse
		
// 				if ((point3.y >= y_u) && (point3.y <= y_d) && (point3.x >= x_l) && (point3.x <= x_r)) { // Within the rectangle
// 					if (point3.x >= (x_l + x_r)/2) { // Right rectangle side
// 						if (std::abs(point3.y - y_u) >= std::abs(point3.y - y_d)) {
// 							if (std::abs(point3.y - y_d) <= std::abs(point3.x - x_r)) {
// 								cv::rectangle(img4, point1, point2c, cv::Scalar(255,0,0), 2);
// 		       					cv::imshow(window, img4);
// 								point2 = point2c;
// 							} else {
// 								cv::rectangle(img4, point1, point2b, cv::Scalar(255,0,0), 2);
// 		       					cv::imshow(window, img4);
// 								point2 = point2b;
// 							}					
// 						} else {
// 							if (std::abs(point3.y - y_u) <= std::abs(point3.x - x_r)) {
// 								cv::rectangle(img4, point1c, point2, cv::Scalar(255,0,0), 2);
// 		       					cv::imshow(window, img4);
// 								point1 = point1c;
// 							} else {
// 								cv::rectangle(img4, point1, point2b, cv::Scalar(255,0,0), 2);
// 		       					cv::imshow(window, img4);
// 								point2 = point2b;
// 							}
// 						}
// 					} else { // Left side of the rectangle
// 						if (std::abs(point3.y - y_u) >= std::abs(point3.y - y_d)) {
// 							if (std::abs(point3.y - y_d) <= std::abs(point3.x - x_l)) {
// 								cv::rectangle(img4, point1, point2c, cv::Scalar(255,0,0), 2);
// 		       					cv::imshow(window, img4);
// 								point2 = point2c;
// 							} else {
// 								cv::rectangle(img4, point1b, point2, cv::Scalar(255,0,0), 2);
// 		       					cv::imshow(window, img4);
// 								point1 = point1b;
// 							}					
// 						} else {
// 							if (std::abs(point3.y - y_u) <= std::abs(point3.x - x_l)) {
// 								cv::rectangle(img4, point1c, point2, cv::Scalar(255,0,0), 2);
// 		       					cv::imshow(window, img4);
// 								point1 = point1c;
// 							} else {
// 								cv::rectangle(img4, point1b, point2, cv::Scalar(255,0,0), 2);
// 		       					cv::imshow(window, img4);
// 								point1 = point1b;
// 							}
// 						}
// 					}
// 		    	}
// 			}
	
// 			if (event == EVENT_LBUTTONUP && drag) {
				
// 		        cv::Mat img5 = myFrame.clone();
// 		        point5 = cv::Point(x, y);
// 				drag = false;
// 				point2b = cv::Point(point5.x, y_d);
// 				point1b = cv::Point(point5.x, y_u);
// 				point1c = cv::Point(x_l, point5.y);
// 				point2c = cv::Point(x_r, point5.y);
		
// 				if ((point3.y >= y_u) && (point3.y <= y_d) && (point3.x >= x_l) && (point3.x <= x_r)) { // Within the rectangle.
// 					if (point3.x >= (x_l + x_r)/2) { // Right side of the rectangle
// 						if (std::abs(point3.y - y_u) >= std::abs(point3.y - y_d)) {
// 							if (std::abs(point3.y - y_d) <= std::abs(point3.x - x_r)) {
// 								cv::rectangle(img5, point1, point2c, cv::Scalar(255, 0, 0), 2);
// 		       					cv::imshow(window, img5);
// 								point2 = point2c;
// 								y_d = point2.y;
// 							} else {
// 								cv::rectangle(img5, point1, point2b, cv::Scalar(255, 0, 0), 2);
// 		       					cv::imshow(window, img5);
// 								point2 = point2b;
// 								x_r = point2.x;
// 							}					
// 						} else {
// 							if (std::abs(point3.y - y_u) <= std::abs(point3.x - x_r)) {
// 								cv::rectangle(img5, point1c, point2, cv::Scalar(255, 0, 0), 2);
// 		       					cv::imshow(window, img5);
// 								point1 = point1c;
// 								y_u = point1.y;
// 							} else {
// 								cv::rectangle(img5, point1, point2b, cv::Scalar(255, 0, 0), 2);
// 		       					cv::imshow(window, img5);
// 								point2 = point2b;
// 								x_r = point2.x;
// 							}
// 						}
// 					} else { // Left side of the rectangle
// 						if (std::abs(point3.y - y_u) >= std::abs(point3.y - y_d)) {
// 							if (std::abs(point3.y - y_d) <= std::abs(point3.x - x_l)) {
// 								cv::rectangle(img5, point1, point2c, cv::Scalar(255, 0, 0), 2);
// 		       					cv::imshow(window, img5);
// 								point2 = point2c;
// 								y_d = point2.y;
// 							} else {
// 								cv::rectangle(img5, point1b, point2, cv::Scalar(255, 0, 0), 2);
// 		       					cv::imshow(window, img5);
// 								point1 = point1b;
// 								x_l = point1.x;
// 							}					
// 						} else {
// 							if (std::abs(point3.y - y_u) <= std::abs(point3.x - x_l)) {
// 								cv::rectangle(img5, point1c, point2, cv::Scalar(255, 0, 0), 2);
// 		       					cv::imshow(window, img5);
// 								point1 = point1c;
// 								y_u = point1.y;
// 							} else {
// 								cv::rectangle(img5, point1b, point2, cv::Scalar(255, 0, 0), 2);
// 		       					cv::imshow(window, img5);
// 								point1 = point1b;
// 								x_l = point1.x;
// 							}
// 						}
// 					} 
// 				}
// 				x_l_orig = x_l;
// 				x_r_orig = x_r;
// 				y_u_orig = y_u;
// 				y_d_orig = y_d;
// 			}
// 		}
// 	}
// }

// int selectRect() {

//     cv::VideoCapture videoCapture(videoPath);
//     if (!videoCapture.isOpened()) {
//         std::cerr << "\033[31m" << "Video unreadable." << std::endl;
//         return -1;
//     }

// 	// Get total number of frames
// 	int totalFrames = videoCapture.get(CAP_PROP_FRAME_COUNT);
// 	// Get a frame where the tape will surely be in its reading position (20 seconds since the beginning)
// 	if (500 <= totalFrames)
// 		// Set frame position
// 		videoCapture.set(CAP_PROP_POS_FRAMES, 500);
// 	else {
// 		std::cout << "Video too short! Exiting..." << std::endl;
// 		return -1;
// 	}

// 	videoCapture >> myFrame;
   
// 	cv::namedWindow(window, WINDOW_AUTOSIZE);
//     cv::imshow(window, myFrame);
//     cv::setMouseCallback(window, mouseHandler, 0);

//     std::cout << "\nDraw with the mouse the rectangle that contains the desired processing area.\n";
// 	if (speed == 15)
//     	std::cout << "At 15 ips, it should cover \033[1mthe entire\033[0m reading head.\n";
// 	else
//     	std::cout << "At 7.5 ips, it should cover \033[1mhalf\033[0m the reading head.\n";
// 	std::cout << "\033[33mATTENTION: please, start drawing from the top-left corner of the rectangle, and end on the bottom-right corner.\033[0m" << std::endl;

// 	char c = waitKey(0);
// 	if (c != 'y') {
// 		return -1;
// 	}

// 	if (callback) {
//         if (myFrame.empty()) {
// 	        std::cout << "Empty frame." << std::endl;
// 			return -1;
// 		}
// 		if (rotated) {
// 			point1.x = x_l;
// 			point1.y = y_u;
// 			point2.x = x_r;
// 			point2.y = y_d;
// 			for (int i = 0; i < 4; i++)
// 				cv::line(myFrame, vertices[i], vertices[(i + 1) % 4], Scalar(0, 0, 255), 2);
// 		} else {
// 			cv::rectangle(myFrame, point1, point2, cv::Scalar(0, 0, 255), 2);
// 		}
// 		cv::imshow(window, myFrame);
// 		// Writing rectangle coordinates on a file
// 		ofstream coordFile;
// 		coordFile.open("../config/coordinate.txt");
//  		coordFile << x_r << "\n";
// 		coordFile << x_l << "\n";
//  		coordFile << y_u << "\n";
// 		coordFile << y_d << "\n";
// 		coordFile.close();
//     } else {
// 		std::cout << "Rectangle not found." << std::endl;
// 		return -1;
// 	}
//     return 0;
// }


// int selectPinchRoller() {

// 	pinchRollerSelection = true;

//     cv::imshow(window, myFrame);
//     cv::setMouseCallback(window, mouseHandler, 0);

//     std::cout << "Draw the rectangle that contains the pinchRoller area.\n" << std::endl;
// 	char c = waitKey(0);
// 	if (c != 'y') {
// 		return -1;
// 	}

// 	if (callback) {
//         if (myFrame.empty()) {
// 	        std::cout << "Empty frame." << std::endl;
// 			return -1;
// 		}
// 		cv::rectangle(myFrame, point1, point2, cv::Scalar(0, 255, 0), 2);
// 		cv::imshow(window, myFrame);
//     } else {
// 		std::cout << "Rectangle not found." << std::endl;
// 		return -1;
// 	}
//     return 0;
// }


// int defineProcessingArea() {

//     if (selectRect() != 0) {
//         return -1;
// 	}
// 	std::cout << "Proceeding with the second rectangle." << std::endl;
// 	callback = false;
// 	if (selectPinchRoller() != 0) {
//         return -1;
// 	}

//     std::cout << "\033[36mPress 'y' to confirm everything and proceed with the analysis, \033[35m'm' to modify the ROIs\033[0m or any other key to exit." << std::endl;
    
//     char c_bis = waitKey(0);

//     destroyWindow(window);
    
//     if (c_bis == 'y') {
//         return 0;
//     } else if (c_bis == 'm') {
// 		cv::namedWindow(window, WINDOW_AUTOSIZE);
// 		cv::imshow(window, myFrame);
// 		cv::setMouseCallback(window, mouseHandler, 0);
// 		return 1;
// 	} else {
//         return -1;
//     }
// }

bool frameDifference(cv::Mat prevFrame, cv::Mat currentFrame, int msToEnd) {

	// Processing area
    int areaPixels = rect.size.width * rect.size.height;
	float differentPixelsThreshold = areaPixels * thresholdPercentual / 100;
	// // PinchRoller area
	// int pixelsNumberPinchRoller = (xc_r - xc_l) * (yc_d - yc_u);
	// float differentPixelsThresholdPinchRoller = (thresholdPercentualPinchRoller * pixelsNumberPinchRoller)/100;

	/***************** Extract matrices corresponding to the processing area *********************/
	// CODE FROM https://answers.opencv.org/question/497/extract-a-rotatedrect-area/

	// matrices we'll use
	Mat M, rotatedPrevFrame, croppedPrevFrame, rotatedCurrentFrame, croppedCurrentFrame;
	// get angle and size from the bounding box
	float angle = rect.angle;
	Size rect_size = rect.size;
	// thanks to http://felix.abecassis.me/2011/10/opencv-rotation-deskewing/
	if (rect.angle < -45.) {
		angle += 90.0;
		swap(rect_size.width, rect_size.height);
	}
	// get the rotation matrix
	M = getRotationMatrix2D(rect.center, angle, 1.0);
	// perform the affine transformation
	warpAffine(prevFrame, rotatedPrevFrame, M, prevFrame.size(), INTER_CUBIC);
	warpAffine(currentFrame, rotatedCurrentFrame, M, currentFrame.size(), INTER_CUBIC);
	// crop the resulting image
	getRectSubPix(rotatedPrevFrame, rect_size, rect.center, croppedPrevFrame);
	getRectSubPix(rotatedCurrentFrame, rect_size, rect.center, croppedCurrentFrame);

	// imshow("Current frame", currentFrame);
	// imshow("Cropped Current Frame", croppedCurrentFrame);
	// waitKey();

	// END CODE FROM https://answers.opencv.org/question/497/extract-a-rotatedrect-area/

	cv::Mat differenceFrame = difference(croppedPrevFrame, croppedCurrentFrame);

	int blackPixelsPinchRoller = 0;

	int decEnd = (msToEnd % 1000) / 100;
	int secEnd = (msToEnd - (msToEnd % 1000)) / 1000;
	int minEnd = secEnd / 60;
	secEnd = secEnd % 60;

	/******************************* PinchRoller analysis ****************************************/

	// // In the last minute of the video, check for pinchRoller position for endTape event
	// if ((msToEnd < 60000) && pinchRollerRect) {
	// 	for (int i = yc_u; i < yc_d; i++) {
	// 	    for (int j = xc_l; j < xc_r; j++) {
    // 		  	if (differenceFrame.at<cv::Vec3b>(i, j)[0] == 0) {
	// 				// There is a black pixel, then there is a difference between previous and current frames
    // 		    	blackPixelsPinchRoller++;
    // 		  	}
    // 		}
  	// 	}
	// 	if (blackPixelsPinchRoller > differentPixelsThresholdPinchRoller) {
	// 		savingPinchRoller = true;
	// 		return true;
	// 	} else {
	// 		savingPinchRoller = false;
	// 	}
	// }

	/****************************** Segment analysis ****************************************/
			
  	int blackPixels = 0;
	float mediaCurrFrame;
	int totColoreCF = 0;

	for (int i = 0; i < croppedCurrentFrame.rows; i++) {
		for (int j = 0; j < croppedCurrentFrame.cols; j++) {
			totColoreCF += croppedCurrentFrame.at<cv::Vec3b>(i, j)[0] + croppedCurrentFrame.at<cv::Vec3b>(i, j)[1] + croppedCurrentFrame.at<cv::Vec3b>(i, j)[2];
			if (differenceFrame.at<cv::Vec3b>(i, j)[0] == 0) {
				blackPixels++;
			}
		}
	}

	// if (!rotated) {
	// 	for (int i = y_u; i < y_d; i++) {
	// 	    for (int j = x_l; j < x_r; j++) {
	// 			totColoreCF = totColoreCF + currentFrame.at<cv::Vec3b>(i, j)[0] + currentFrame.at<cv::Vec3b>(i, j)[1] + currentFrame.at<cv::Vec3b>(i, j)[2];
	// 			areaPixels++;
   	// 		  	if (differenceFrame.at<cv::Vec3b>(i, j)[0] == 0) {
   	// 		    	blackPixels++;
   	// 		  	}
   	// 		}
 	// 	}
	// } else {
	// 	int x_r_max = (y_u - qA)/mA;
	// 	int x_l_max = (y_u - qD)/mD;

	// 	for (int j = y_u; j < y_m1; j++) {
	// 		double mD_pos = -mD;
	// 		x_r_max = (j - qA)/mA;
	// 		x_l_max = (qD - j)/mD_pos;
	// 		for (int i = x_l_max; i < x_r_max; i++) {
	// 			totColoreCF = totColoreCF + currentFrame.at<cv::Vec3b>(i, j)[0] + currentFrame.at<cv::Vec3b>(i, j)[1] + currentFrame.at<cv::Vec3b>(i, j)[2];
	// 			areaPixels++;
	// 			if (differenceFrame.at<cv::Vec3b>(j, i)[0] == 0) {
    // 			    blackPixels++;
    // 			}
	// 		}
	// 	}
	// 	// Use B and D straight lines
	// 	for (int j = y_m1; j < y_m2; j++) {
	// 		if (BD) { // If the right point is higher than the left one (eg: y0 y1 y3 y2)			
	// 			x_r_max = (j - qB)/mB;
	// 			x_l_max = (j - qD)/mD;
	// 			for (int i = x_l_max; i < x_r_max; i++) {
	// 				totColoreCF = totColoreCF + currentFrame.at<cv::Vec3b>(i, j)[0] + currentFrame.at<cv::Vec3b>(i, j)[1] + currentFrame.at<cv::Vec3b>(i, j)[2];
	// 				areaPixels++;
	// 				if (differenceFrame.at<cv::Vec3b>(j, i)[0] == 0) {
    // 				    blackPixels++;
    // 				}
	// 			}
	// 		} else { // AC case for eg: y0 y3 y1 y2
	// 			x_r_max = (j - qA)/mA;
	// 			x_l_max = (j - qC)/mC;
	// 			for (int i = x_l_max; i < x_r_max; i++) {
	// 				totColoreCF = totColoreCF + currentFrame.at<cv::Vec3b>(i, j)[0] + currentFrame.at<cv::Vec3b>(i, j)[1] + currentFrame.at<cv::Vec3b>(i, j)[2];
	// 				areaPixels++;
	// 				if (differenceFrame.at<cv::Vec3b>(j, i)[0] == 0) {
    // 				    blackPixels++;
    // 				}
	// 			}
	// 		}
	// 	}
	// 	// Using B and C straight lines
	// 	for (int j = y_m2; j < y_d; j++) {
	// 		x_r_max = (j - qB)/mB;
	// 		x_l_max = (j - qC)/mC;
	// 		for (int i = x_l_max; i < x_r_max; i++) {
	// 			totColoreCF = totColoreCF + currentFrame.at<cv::Vec3b>(i, j)[0] + currentFrame.at<cv::Vec3b>(i, j)[1] + currentFrame.at<cv::Vec3b>(i, j)[2];
	// 			areaPixels++;
	// 			if (differenceFrame.at<cv::Vec3b>(j, i)[0] == 0) {
    // 			    blackPixels++;
    // 			}
	// 		}
	// 	}
	// }
	mediaCurrFrame = totColoreCF/areaPixels;

	float tsh = areaPixels * thresholdPercentual / 100;

	if (blackPixels > tsh) {
		if (brands) {
			if (mediaPrevFrame > (mediaCurrFrame + 10) || mediaPrevFrame < (mediaCurrFrame - 10)) { // They are not similar for color average
				// Update mediaPrevFrame
				mediaPrevFrame = mediaCurrFrame;
				firstBrandInstant = msToEnd;
				return true;
			}
			// If the above condition is not verified, update anyway mediaPrevFrame
			mediaPrevFrame = mediaCurrFrame;
			// At the beginning of the video, wait at least 1 second before the next Irregularity to consider it as a brand.
			// It is not guaranteed that it will be the first brand, but it is generally a safe approach to have a correct image
			if (firstBrand && (firstBrandInstant - msToEnd > 1000)) {
				firstBrand = false;
				savingBrand = true;
				return true;
			}
		} else {
			return true;
		}
	}
	
	return false;
}


int processing(cv::VideoCapture videoCapture, std::string fileName) {

	// Video duration
	int frameNumbers_v = videoCapture.get(CAP_PROP_FRAME_COUNT);
	float fps_v = videoCapture.get(CAP_PROP_FPS); // FPS can be non-integers!!!
	float videoLength = (float) frameNumbers_v / fps_v; // [s]
	int videoLength_ms = videoLength * 1000;
	
    int savedFrames = 0, unsavedFrames = 0;
	float lastSaved = -160;
	int savingRate = 0; // [ms]
	// Whenever we find an Irregularity, we want to skip a lenght equal to the reading head (3 cm = 1.18 inches)
	if (speed == 7.5)
		savingRate = 157; // Time taken to cross 3 cm at 7.5 ips
	else if (speed == 15)
		savingRate = 79; // Time taken to cross 3 cm at 15 ips

	// The first frame of the video won't be processed
    cv::Mat prevFrame;
	videoCapture >> prevFrame;
	firstBrandInstant = videoLength_ms - videoCapture.get(CAP_PROP_POS_MSEC);

    while (videoCapture.isOpened()) {

		cv::Mat frame;
        videoCapture >> frame;

        if (!frame.empty()) {

			int ms = videoCapture.get(CAP_PROP_POS_MSEC);
			int msToEnd = videoLength_ms - ms;
			if (ms == 0) // With OpenCV library, this happens at the last few frames of the video before realising that "frame" is empty.
				break;
			int secToEnd = msToEnd / 1000;
			int minToEnd = (secToEnd / 60) % 60;
			secToEnd = secToEnd % 60;

			std::string secStrToEnd = std::to_string(secToEnd), minStrToEnd = std::to_string(minToEnd);
			if (minToEnd < 10)
				minStrToEnd = "0" + minStrToEnd;
			if (secToEnd < 10)
				secStrToEnd = "0" + secStrToEnd;

			std::cout << "\rIrregularities: " << savedFrames << ".   ";
			std::cout << "Remaining video time [mm:ss]: " << minStrToEnd << ":" << secStrToEnd << std::flush;

			if ((ms - lastSaved > savingRate) && frameDifference(prevFrame, frame, msToEnd)) {
				
				// An Irregularity is found!

				// De-interlacing frame
				cv::Mat oddFrame(frame.rows/2, frame.cols, CV_8UC3);
				cv::Mat evenFrame(frame.rows/2, frame.cols, CV_8UC3);
				separateFrame(frame, oddFrame, evenFrame);

				// Finding an image containing the whole tape
				Point2f pts[4];
				rect.points(pts);
				cv::Mat subImageNastro(frame, cv::Rect(100, min(pts[1].y, pts[2].y), frame.cols - 100, static_cast<int>(rect.size.height)));

				// De-interlacing the image with the whole tape
				cv::Mat oddSubImage(subImageNastro.rows/2, subImageNastro.cols, CV_8UC3);
				int evenSubImageRows = subImageNastro.rows/2;
				if (subImageNastro.rows % 2 != 0) // If the found rectangle is of odd height, we must increase evenSubImage height by 1, otherwise we have segmentation_fault!!!
					evenSubImageRows += 1;
				cv::Mat evenSubImage(evenSubImageRows, subImageNastro.cols, CV_8UC3);
				separateFrame(subImageNastro, oddSubImage, evenSubImage);

				std::string timeLabel = getTimeLabel(ms);
				std::string safeTimeLabel = getSafeTimeLabel(ms);

				saveIrregularityImage(safeTimeLabel, fileName, oddFrame, oddSubImage);

				// Append Irregularity information to JSON
				boost::uuids::uuid uuid = boost::uuids::random_generator()();
				irregularityFileOutput1["Irregularities"] += {{
						"IrregularityID", boost::lexical_cast<std::string>(uuid)
					}, {
						"Source", "v"
					}, {
						"TimeLabel", timeLabel
					}
				};
				irregularityFileOutput2["Irregularities"] += {{
						"IrregularityID", boost::lexical_cast<std::string>(uuid)
					}, {
						"Source", "v"
					}, {
						"TimeLabel", timeLabel
					}, {
						"ImageURI", pathTape224 + "/"+ fileName + "_" + safeTimeLabel + ".jpg"
					}
				};

				lastSaved = ms;
				savedFrames++;

			} else {
				unsavedFrames++;
			}

			prevFrame = frame;

	    } else {
			std::cout << "\nEmpty frame!" << std::endl;
	    	videoCapture.release();
	    	break;
	    }
	}

	ofstream myFile;
	myFile.open("log.txt", ios::app);
	myFile << "Saved frames are: " << savedFrames << std::endl;
	myFile.close();

    return 0;

}


bool findProcessingArea(json configurationFile) {

	// Returned variable
	bool found = false;

	// Read parameters from JSON
	int minDist, angleThresh, scaleThresh, posThresh, minDistTape, angleThreshTape, scaleThreshTape, posThreshTape;
	try {
		minDist = configurationFile["MinDist"];
		angleThresh = configurationFile["AngleThresh"];
		scaleThresh = configurationFile["ScaleThresh"];
		posThresh = configurationFile["PosThresh"];
		minDistTape = configurationFile["MinDistTape"];
		angleThreshTape = configurationFile["AngleThreshTape"];
		scaleThreshTape = configurationFile["ScaleThreshTape"];
		posThreshTape = configurationFile["PosThreshTape"];
	} catch (nlohmann::detail::type_error e) {
		std::cerr << "\033[1;31mconfig.json error!\033[0;31m\n" << e.what() << std::endl;
		return -1;
	}

	// Obtain grayscale version of myFrame
	Mat myFrameGrayscale;
	cvtColor(myFrame, myFrameGrayscale, COLOR_BGR2GRAY);
	// Downsample myFrameGrayscale in half pixels
	Mat myFrameGrayscaleHalf;
	pyrDown(myFrameGrayscale, myFrameGrayscaleHalf, Size(myFrame.cols/2, myFrame.rows/2));

	// Get input shape in grayscale
	Mat templateImage = imread("../input/readingHead.png", IMREAD_GRAYSCALE);
	// Downsample tapeShape in half pixels
	Mat templateImageHalf;
	pyrDown(templateImage, templateImageHalf, Size(templateImage.cols/2, templateImage.rows/2));

	// Select the image to process
	Mat processingImage = myFrameGrayscaleHalf;
	// Select the template to be detected
	Mat templateShape = templateImageHalf;

	// Algorithm and parameters
	Ptr<GeneralizedHoughGuil> alg = createGeneralizedHoughGuil();

	alg -> setMinDist(minDist);
	alg -> setLevels(360);
	alg -> setDp(2);
	alg -> setMaxBufferSize(1000);

	alg -> setAngleStep(1);
	alg -> setAngleThresh(angleThresh);
	
	alg -> setMinScale(0.8);
	alg -> setMaxScale(1.2);
	alg -> setScaleStep(0.1);
	alg -> setScaleThresh(scaleThresh);

	alg -> setPosThresh(posThresh);

	alg -> setCannyLowThresh(100);
	alg -> setCannyHighThresh(300);

	alg -> setTemplate(templateShape);

	vector<Vec4f> positions, positions2;
    TickMeter tm;
	int oldPosThresh = posThresh;

	tm.start();
	// Parameters are quite slack, therefore more than one match should be expected.
	// By inspecting different angles (only between +10 and -10 degrees of maximum inclination) or increasing the position threshold,
	// the algorithm should eventually identify only one region.
	while (positions.size() != 1) {
		std::cout << "Proceeding with positive angles." << endl;
		alg -> setMinAngle(0);
		alg -> setMaxAngle(10);
		alg -> detect(processingImage, positions);

		if (positions.size() == 1)
			break;

		std::cout << "Proceeding with negative angles." << endl;
		alg -> setMinAngle(350);
		alg -> setMaxAngle(360);
		alg -> detect(processingImage, positions);

		if (positions.size() == 1)
			break;
		
		std::cout << "Increasing position threshold." << endl;
		oldPosThresh += 10;
		alg -> setPosThresh(oldPosThresh);
	}
	tm.stop();

    std::cout << "Reading head detection time : " << tm.getTimeMilli() << " ms" << endl;

	Point2f pos(positions[0][0], positions[0][1]);
	float scale = positions[0][2];
	float angle = positions[0][3];

	rect.center = pos * 2; // * 2 since the processed image is half the original one
	rect.size = Size2f(templateShape.cols * scale * 2, templateShape.rows * scale * 2); // * 2 for the same reason
	rect.angle = angle;

	Point2f pts[4];

	rect.points(pts);

	// Red for the reading head
	line(myFrame, pts[0], pts[1], Scalar(0, 0, 255), 2);
	line(myFrame, pts[1], pts[2], Scalar(0, 0, 255), 2);
	line(myFrame, pts[2], pts[3], Scalar(0, 0, 255), 2);
	line(myFrame, pts[3], pts[0], Scalar(0, 0, 255), 2);
	
	// Defining the processing area for identifying the tape under the reading head.
	//
	// Parameters for extracting a rectangle containing the found rectangle completely (also if it is slightly rotated)
	// and with twice its height (since the tape is immediatley below the found rectangle).
	int tapeProcessingAreaX = min(pts[0].x, pts[1].x);
	int tapeProcessingAreaY = min(pts[1].y, pts[2].y) + (max(pts[0].y, pts[3].y) - min(pts[1].y, pts[2].y)) * 2/3; // Shift down the area
	int tapeProcessingAreaWidth = max(pts[3].x-pts[1].x, pts[2].x-pts[0].x);
	int tapeProcessingAreaHeight = max(pts[3].y-pts[1].y, pts[0].y-pts[2].y);

	Rect tapeProcessingAreaRect(tapeProcessingAreaX, tapeProcessingAreaY, tapeProcessingAreaWidth, tapeProcessingAreaHeight);
	// Obtain the new image
	Mat tapeProcessingArea = myFrame(tapeProcessingAreaRect);
	// Obtain grayscale version of tapeProcessingArea
	Mat tapeProcessingAreaGrayscale = myFrameGrayscale(tapeProcessingAreaRect);
	// Read template image - it is smaller than before, therefore there is no need to downsample
	templateImage = imread("../input/tapeArea.png", IMREAD_GRAYSCALE);

	// Reset algorithm and set parameters
	alg = createGeneralizedHoughGuil();

	alg -> setMinDist(minDistTape);
	alg -> setLevels(360);
	alg -> setDp(2);
	alg -> setMaxBufferSize(1000);

	alg -> setAngleStep(1);
	alg -> setAngleThresh(angleThreshTape);

	alg -> setMinScale(0.9);
	alg -> setMaxScale(1.1);
	alg -> setScaleStep(0.05);
	alg -> setScaleThresh(scaleThreshTape);

	alg -> setPosThresh(posThreshTape);

	alg -> setCannyLowThresh(100);
	alg -> setCannyHighThresh(300);

	alg -> setTemplate(templateImage);

	oldPosThresh = posThreshTape;

	tm.start();
	for (int i = 0; i < 10; i++) {
		std::cout << "Proceeding with positive angles." << endl;
		alg -> setMinAngle(0);
		alg -> setMaxAngle(10);
		alg -> detect(tapeProcessingAreaGrayscale, positions2);

		if (positions2.size() == 1) {
			found = true;
			break;
		}

		std::cout << "Proceeding with negative angles." << endl;
		alg -> setMinAngle(350);
		alg -> setMaxAngle(360);
		alg -> detect(tapeProcessingAreaGrayscale, positions2);

		if (positions2.size() == 1) {
			found = true;
			break;
		}
		
		std::cout << "Increasing position value." << endl;
		oldPosThresh += 10;
		alg -> setPosThresh(oldPosThresh);
	}
	tm.stop();

    std::cout << "Tape detection time : " << tm.getTimeMilli() << " ms" << endl;

	for (int i = 0; i < positions2.size(); i++) {
		Point2f pos2(positions2[i][0], positions2[i][1]);
		scale = positions2[i][2];
		angle = positions2[i][3];

		rect.center = pos2;
		rect.size = Size2f(templateShape.cols * scale, templateShape.rows * scale);
		rect.angle = angle;

		rect.points(pts);

		// Update points with tape processing area coordinates
		pts[0] = Point2f(pts[0].x+tapeProcessingAreaX, pts[0].y+tapeProcessingAreaY);
		pts[1] = Point2f(pts[1].x+tapeProcessingAreaX, pts[1].y+tapeProcessingAreaY);
		pts[2] = Point2f(pts[2].x+tapeProcessingAreaX, pts[2].y+tapeProcessingAreaY);
		pts[3] = Point2f(pts[3].x+tapeProcessingAreaX, pts[3].y+tapeProcessingAreaY);
		// Update rect
		rect = RotatedRect(pts[0], pts[1], pts[2]);


		line(myFrame, pts[0], pts[1], Scalar(0, 255, 0), 2);
		line(myFrame, pts[1], pts[2], Scalar(0, 255, 0), 2);
		line(myFrame, pts[2], pts[3], Scalar(0, 255, 0), 2);
		line(myFrame, pts[3], pts[0], Scalar(0, 255, 0), 2);
	}

	imshow("Tape area(s)", myFrame);
	waitKey();

	return found;
}


int main(int argc, char** argv) {

	/**************************************** CONFIGURATION FILE ****************************************/

	// Read configuration file
	std::ifstream iConfig("../config/config.json");
	iConfig >> configurationFile;
	// Initialise parameters
	try {
		brands = configurationFile["Brands"];
		irregularityFileInputPath = configurationFile["IrregularityFileInput"];
		outputPath = configurationFile["OutputPath"];
		videoPath = configurationFile["PreservationAudioVisualFile"];
		speed = configurationFile["Speed"];
		thresholdPercentual = configurationFile["ThresholdPercentual"];
		thresholdPercentualPinchRoller = configurationFile["ThresholdPercentualPinchRoller"];
	} catch (nlohmann::detail::type_error e) {
		std::cerr << "\033[1;31mconfig.json error!\033[0;31m\n" << e.what() << std::endl;
		return -1;
	}
	// Input JSON check
	std::ifstream iJSON(irregularityFileInputPath);
	if (iJSON.fail()) {
		std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nIrregularityFileInput.json cannot be found or opened."  << std::endl;
		return -1;
	}
	std::string fileName, extension;
    if (findFileName(videoPath, fileName, extension) == -1) {
        std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nThe PreservationAudioVisualFile cannot be found or opened." << std::endl;
        return -1;
    }
	if (speed != 7.5 && speed != 15) {
		std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nSpeed parameter must be 7.5 or 15 ips."  << std::endl;
		return -1;
	}
	if (thresholdPercentual < 0 || thresholdPercentual > 100) {
		std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nThresholdPercentual parameter must be a percentage value."  << std::endl;
		return -1;
	}
	if (thresholdPercentualPinchRoller < 0 || thresholdPercentualPinchRoller > 100) {
		std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nThresholdPercentual parameter must be a percentage value."  << std::endl;
		return -1;
	}

	if (speed == 15)
		thresholdPercentual += 4;

    std::cout << "\nParameters from config.json file:" << std::endl;
	std::cout << "  Brands: " << brands << std::endl;
	std::cout << "  Speed: " << speed << std::endl;
    std::cout << "  ThresholdPercentual: " << thresholdPercentual << std::endl;
	std::cout << "  ThresholdPercentualPinchRoller: " << thresholdPercentualPinchRoller << std::endl;

	// Read input JSON
	iJSON >> irregularityFileInput;

	/**************************************** DEFINE THE PROCESSING AREA ****************************************/

	// bool processingAreaDefined = false;
	// while (!processingAreaDefined) {
	// 	// Reset variables
	// 	savingPinchRoller = false;
	// 	pinchRollerRect = false;
	// 	processingSelection = true;
	// 	rotated = false;
	// 	int defineProcArea = defineProcessingArea();
	// 	if (defineProcArea == -1) {
	// 		std::cout << "Exit." << std::endl;
	// 		return -1;
	// 	} else if (defineProcArea == 1) {
	// 		std::cout << "Modifying the area again." << std::endl;
	// 	} else {
	// 		processingAreaDefined = true;
	// 	}
	// }

	/******************************************* TAPE AREA DETECTION *******************************************/

	cv::VideoCapture videoCapture(videoPath);
    if (!videoCapture.isOpened()) {
        std::cerr << "\033[31m" << "Video unreadable." << std::endl;
        return -1;
    }

	// Get total number of frames
	int totalFrames = videoCapture.get(CAP_PROP_FRAME_COUNT);
	// Set frame position to half video length
	videoCapture.set(CAP_PROP_POS_FRAMES, totalFrames/2);
	// Get frame and show it
	videoCapture >> myFrame;
	
	// Find the processing area corresponding to the tape area over the reading head
	bool found = findProcessingArea(configurationFile);

	// Reset frame position
	videoCapture.set(CAP_PROP_POS_FRAMES, 0);

	/**************************** WRITE USEFUL INFORMATION TO LOG FILE ***************************/

	// Get now time
	std::time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    std::string ts = std::ctime(&t);
	ofstream myFile;
	myFile.open("log.txt", ios::app);
	myFile << endl << fileName << endl;
	myFile << "tsh: " << thresholdPercentual << "   tshp: " << thresholdPercentualPinchRoller << std::endl;
	myFile << ts; // No endline character for avoiding middle blank line.

	if (found) {
		cout << "Processing area found!" << endl;
		myFile << "Processing area found!" << endl;
		myFile.close();
	} else {
		cout << "Processing area not found. Try changing JSON parameters." << endl;
		myFile << "Processing area not found." << endl;
		myFile.close();
		return 1; // Program terminated early
	}

	/********************************* MAKE REQUIRED DIRECTORIES *********************************/
	
	makeDirectories(fileName, outputPath, brands);

	/**************************************** PROCESSING *****************************************/

	std::cout << "\n\033[32mStarting processing...\033[0m\n" << std::endl;

	// Processing timer
	time_t startTimer, endTimer;
	startTimer = time(NULL);

	processing(videoCapture, fileName);

	endTimer = time(NULL);
	float min = (endTimer - startTimer) / 60;
	float sec = (endTimer - startTimer) % 60;

	std::string result("Processing elapsed time: " + std::to_string((int)min) + ":" + std::to_string((int)sec));
	cout << endl << result << endl;

	myFile.open("log.txt", ios::app);
	myFile << result << std::endl << std::endl;
	myFile.close();

	/**************************************** IRREGULARITY FILES ****************************************/

	std::ofstream outputFile1;
	std::string outputFile1Name = outputPath + "IrregularityFileOutput1.json";
	outputFile1.open(outputFile1Name);
	outputFile1 << irregularityFileOutput1 << std::endl;

	// Irregularities to extract for the AudioAnalyser and to the TapeIrregularityClassifier
	extractIrregularityImagesForAudio(outputPath, videoPath, irregularityFileInput, irregularityFileOutput2);

	std::ofstream outputFile2;
	std::string outputFile2Name = outputPath + "IrregularityFileOutput2.json";
	outputFile2.open(outputFile2Name);
	outputFile2 << irregularityFileOutput2 << std::endl;
	
    return 0;

}
