/*
    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 Nadir Dalla Pozza
    @version 3.0
    @date 29-06-2022
*/
#include <filesystem>
#include <iostream>
#include <sys/timeb.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 "utility.h"
#include "forAudioAnalyser.h"

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



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

bool savingPinchRoller = false, pinchRollerRect = false;
bool savingBrand = false;
cv::Mat myFrame;
float mediaPrevFrame = 0;
bool firstBrand = true;	// The first frame containing brands on tape must be saved
float firstBrandInstant = 0;

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

bool debug = true;



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

	/********************************** Capstan analysis *****************************************/

	// In the last minute of the video, check for pinchRoller position for endTape event
	if (msToEnd < 60000) {

		// Capstan area
		int capstanAreaPixels = rectCapstan.size.width * rectCapstan.size.height;
		float capstanDifferentPixelsThreshold = capstanAreaPixels * capstanThresholdPercentual / 100;
		
		// 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 = rectCapstan.angle;
		Size rect_size = rectCapstan.size;
		// thanks to http://felix.abecassis.me/2011/10/opencv-rotation-deskewing/
		if (rectCapstan.angle < -45.) {
			angle += 90.0;
			swap(rect_size.width, rect_size.height);
		}
		// get the rotation matrix
		M = getRotationMatrix2D(rectCapstan.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, rectCapstan.center, croppedPrevFrame);
		getRectSubPix(rotatedCurrentFrame, rect_size, rectCapstan.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 blackPixelsCapstan = 0;

		for (int i = 0; i < croppedCurrentFrame.rows; i++) {
			for (int j = 0; j < croppedCurrentFrame.cols; 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
					blackPixelsCapstan++;
				}
			}
		}

		if (blackPixelsCapstan > capstanDifferentPixelsThreshold) {
			savingPinchRoller = true;
			return true;
		} else {
			savingPinchRoller = false;
		}
	}
	
	/************************************ Tape analysis ******************************************/

	// Tape area
    int tapeAreaPixels = rectTape.size.width * rectTape.size.height;
	float tapeDifferentPixelsThreshold = tapeAreaPixels * tapeThresholdPercentual / 100;

	/***************** Extract matrices corresponding to the processing area *********************/

	// Tape 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 = rectTape.angle;
	Size rect_size = rectTape.size;
	// thanks to http://felix.abecassis.me/2011/10/opencv-rotation-deskewing/
	if (rectTape.angle < -45.) {
		angle += 90.0;
		swap(rect_size.width, rect_size.height);
	}
	// get the rotation matrix
	M = getRotationMatrix2D(rectTape.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, rectTape.center, croppedPrevFrame);
	getRectSubPix(rotatedCurrentFrame, rect_size, rectTape.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 decEnd = (msToEnd % 1000) / 100;
	int secEnd = (msToEnd - (msToEnd % 1000)) / 1000;
	int minEnd = secEnd / 60;
	secEnd = secEnd % 60;


	/****************************** 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++;
			}
		}
	}
	mediaCurrFrame = totColoreCF/tapeAreaPixels;

	if (blackPixels > tapeDifferentPixelsThreshold) {
		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];
				if (savingPinchRoller)
					rectCapstan.points(pts);
				else
					rectTape.points(pts);
				cv::Mat subImage(frame, cv::Rect(100, min(pts[1].y, pts[2].y), frame.cols - 100, static_cast<int>(rectTape.size.height)));

				// De-interlacing the image with the whole tape
				cv::Mat oddSubImage(subImage.rows/2, subImage.cols, CV_8UC3);
				int evenSubImageRows = subImage.rows/2;
				if (subImage.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, subImage.cols, CV_8UC3);
				separateFrame(subImage, oddSubImage, evenSubImage);

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

				saveIrregularityImage(safeTimeLabel, fileName, oddFrame, oddSubImage, savingPinchRoller);

				// 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 findProcessingAreas(json configurationFile) {

	/******************************************* JSON PARAMETERS *******************************************/

	// Read parameters from JSON
	int minDist, angleThresh, scaleThresh, posThresh, minDistTape, angleThreshTape, scaleThreshTape, posThreshTape, minDistCapstan, angleThreshCapstan, scaleThreshCapstan, posThreshCapstan;
	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"];
		minDistCapstan = configurationFile["MinDistCapstan"];
		angleThreshCapstan = configurationFile["AngleThreshCapstan"];
		scaleThreshCapstan = configurationFile["ScaleThreshCapstan"];
		posThreshCapstan = configurationFile["PosThreshCapstan"];
	} catch (nlohmann::detail::type_error e) {
		std::cerr << "\033[1;31mconfig.json error!\033[0;31m\n" << e.what() << std::endl;
		return -1;
	}
	
	/******************************************* READING HEAD DETECTION *******************************************/

	// Obtain grayscale version of myFrame
	Mat myFrameGrayscale;
	cvtColor(myFrame, myFrameGrayscale, COLOR_BGR2GRAY);
	// Downsample myFrameGrayscale in half pixels for performance reasons
	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.9);
	alg -> setMaxScale(1.1);
	alg -> setScaleStep(0.1);
	alg -> setScaleThresh(scaleThresh);

	alg -> setPosThresh(posThresh);

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

	alg -> setTemplate(templateShape);

	vector<Vec4f> positionsPos, positionsNeg;
	Mat votesPos, votesNeg;
    TickMeter tm;
	int oldPosThresh = posThresh;

	std::cout << "\033[1;36mReading head\033[0m" << endl;
	tm.start();
	detectShape(alg, templateShape, posThresh, positionsPos, votesPos, positionsNeg, votesNeg, myFrameGrayscaleHalf);
	tm.stop();
    std::cout << "Reading head detection time : " << tm.getTimeMilli() << " ms" << endl;
	RotatedRect rectPos = drawShapes(myFrame, positionsPos, Scalar(0, 0, 255), templateImageHalf.cols, templateImageHalf.rows, 0, 0, 2);
	RotatedRect rectNeg = drawShapes(myFrame, positionsNeg, Scalar(128, 128, 255), templateImageHalf.cols, templateImageHalf.rows, 0, 0, 2);

	Point2f pts[4];
	if (rectPos.size.width > 0)
		if (rectNeg.size.width > 0)
			if (votesPos.at<int>(0) > votesNeg.at<int>(0)) {
				cout << "Positive is best" << endl;
				rect = rectPos;
			} else {
				cout << "Negative is best" << endl;
				rect = rectNeg;
			}
		else {
			cout << "Positive is the only choice." << endl;
			rect = rectPos;
		}
	else if (rectNeg.size.width > 0) {
		cout << "Negative is the only choice." << endl;
		rect = rectNeg;
	} else {
		return false;
	}
	cout << endl;

	rect.points(pts);

	/******************************************* TAPE AREA DETECTION *******************************************/
	
	// 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 grayscale version of tapeProcessingArea
	Mat tapeProcessingAreaGrayscale = myFrameGrayscale(tapeProcessingAreaRect);
	// Read template image - it is smaller than before, therefore there is no need to downsample
	templateShape = 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(templateShape);

	oldPosThresh = posThreshTape;

	vector<Vec4f> positionsTapePos, positionsTapeNeg;
	Mat votesTapePos, votesTapeNeg;

	std::cout << "\033[1;36mTape\033[0m" << endl;
	tm.reset();
	tm.start();
	detectShape(alg, templateShape, posThreshTape, positionsTapePos, votesTapePos, positionsTapeNeg, votesTapeNeg, tapeProcessingAreaGrayscale);
	tm.stop();
    std::cout << "Tape detection time : " << tm.getTimeMilli() << " ms" << endl;
	RotatedRect rectTapePos = drawShapes(myFrame, positionsTapePos, Scalar(0, 255, 0), templateShape.cols, templateShape.rows, tapeProcessingAreaX, tapeProcessingAreaY, 1);
	RotatedRect rectTapeNeg = drawShapes(myFrame, positionsTapeNeg, Scalar(128, 255, 128), templateShape.cols, templateShape.rows, tapeProcessingAreaX, tapeProcessingAreaY, 1);
	
	if (rectTapePos.size.width > 0)
		if (rectTapeNeg.size.width > 0)
			if (votesTapePos.at<int>(0) > votesTapeNeg.at<int>(0)) {
				cout << "Positive is best" << endl;
				rectTape = rectTapePos;
			} else {
				cout << "Negative is best" << endl;
				rectTape = rectTapeNeg;
			}
		else {
			cout << "Positive is the only choice." << endl;
			rectTape = rectTapePos;
		}
	else if (rectTapeNeg.size.width > 0) {
		cout << "Negative is the only choice." << endl;
		rectTape = rectTapeNeg;
	} else {
		return false;
	}
	cout << endl;

	/******************************************* CAPSTAN DETECTION *******************************************/

	// Process only right portion of the image, wherw the capstain always appears
	int capstanProcessingAreaRectX = myFrame.cols*3/4;
	int capstanProcessingAreaRectY = myFrame.rows/2;
	int capstanProcessingAreaRectWidth = myFrame.cols/4;
	int capstanProcessingAreaRectHeight = myFrame.rows/2;
	Rect capstanProcessingAreaRect(capstanProcessingAreaRectX, capstanProcessingAreaRectY, capstanProcessingAreaRectWidth, capstanProcessingAreaRectHeight);
	Mat capstanProcessingAreaGrayscale = myFrameGrayscale(capstanProcessingAreaRect);
	// Read template image - it is smaller than before, therefore there is no need to downsample
	templateShape = imread("../input/capstanBERIO058prova.png", IMREAD_GRAYSCALE);

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

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

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

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

	alg -> setPosThresh(posThreshCapstan);

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

	alg -> setTemplate(templateShape);

	oldPosThresh = posThreshCapstan;

	vector<Vec4f> positionsC1Pos, positionsC1Neg;
	Mat votesC1Pos, votesC1Neg;

	std::cout << "\033[1;36mCapstan\033[0m" << endl;
	tm.reset();
	tm.start();
	detectShape(alg, templateShape, posThreshCapstan, positionsC1Pos, votesC1Pos, positionsC1Neg, votesC1Neg, capstanProcessingAreaGrayscale);
	tm.stop();
    std::cout << "Capstan detection time : " << tm.getTimeMilli() << " ms" << endl;
	RotatedRect rectCapstanPos = drawShapes(myFrame, positionsC1Pos, Scalar(255, 0, 0), templateShape.cols-22, templateShape.rows-92, capstanProcessingAreaRectX+11, capstanProcessingAreaRectY+46, 1);
	RotatedRect rectCapstanNeg = drawShapes(myFrame, positionsC1Neg, Scalar(255, 128, 0), templateShape.cols-22, templateShape.rows-92, capstanProcessingAreaRectX+11, capstanProcessingAreaRectY+46, 1);
	
	if (rectCapstanPos.size.width > 0)
		if (rectCapstanNeg.size.width > 0)
			if (votesC1Pos.at<int>(0) > votesC1Neg.at<int>(0)) {
				cout << "Positive is best" << endl;
				rectCapstan = rectCapstanPos;
			} else {
				cout << "Negative is best" << endl;
				rectCapstan = rectCapstanNeg;
			}
		else {
			cout << "Positive is the only choice." << endl;
			rectCapstan = rectCapstanPos;
		}
	else if (rectTapeNeg.size.width > 0) {
		cout << "Negative is the only choice." << endl;
		rectCapstan = rectCapstanNeg;
	} else {
		return false;
	}
	cout << endl;

	// Shows the detected areas
	// imshow("Tape area(s)", myFrame);
	// waitKey();

	return true;
}


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"];
		tapeThresholdPercentual = configurationFile["ThresholdPercentualTape"];
		capstanThresholdPercentual = configurationFile["ThresholdPercentualCapstan"];
	} 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 (tapeThresholdPercentual < 0 || tapeThresholdPercentual > 100) {
		std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nThresholdPercentual parameter must be a percentage value."  << std::endl;
		return -1;
	}
	if (capstanThresholdPercentual < 0 || capstanThresholdPercentual > 100) {
		std::cerr << "\033[1;31mconfig.json error!\033[0;31m\nThresholdPercentual parameter must be a percentage value."  << std::endl;
		return -1;
	}

	// Speed reference = 7.5
	// If the speed is 15, then increase threshold percentual by 4 to have similar detection performance
	if (speed == 15)
		tapeThresholdPercentual += 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: " << tapeThresholdPercentual << std::endl;
	std::cout << "  ThresholdPercentualCapstan: " << capstanThresholdPercentual << std::endl;
	std::cout << std::endl;

	// Read input JSON
	iJSON >> irregularityFileInput;

	/******************************************* 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 = findProcessingAreas(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: " << tapeThresholdPercentual << "   tshp: " << capstanThresholdPercentual << std::endl;
	myFile << ts; // No endline character for avoiding middle blank line.

	if (found) {
		std::cout << "Processing areas found!" << endl;
		myFile << "Processing areas found!" << endl;
		myFile.close();
	} else {
		std::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));
	std::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;

}
