#include"ANSFireNSmoke.h"
namespace ANSCENTER
{
	// Normalize GLCM
	void GLCM::normalizeGLCM(cv::Mat& glcm) {
		double sum = cv::sum(glcm)[0];
		if (sum > 0) {
			glcm /= sum;
		}
	}
	std::vector<cv::Mat> GLCM::graycomatrix(const cv::Mat& image,
											const std::vector<int>& distances,
											const std::vector<float>& angles,
											int levels,
											bool symmetric ,
											bool normed) {
		// Validate input
		if (image.type() != CV_8UC1) {
			return std::vector<cv::Mat>();
		}
		if (levels <= 0 || levels > 256) {
			return std::vector<cv::Mat>();
		}

		int rows = image.rows;
		int cols = image.cols;

		// Create a 4D GLCM tensor (levels x levels x distances x angles)
		std::vector<cv::Mat> glcmMatrices(distances.size() * angles.size(), cv::Mat::zeros(levels, levels, CV_32F));

		// Iterate through distances and angles
		for (size_t d = 0; d < distances.size(); ++d) {
			int distance = distances[d];
			for (size_t a = 0; a < angles.size(); ++a) {
				float angle = angles[a];
				int dx = static_cast<int>(distance * std::cos(angle));
				int dy = static_cast<int>(distance * std::sin(angle));

				// Compute GLCM for the specific distance and angle
				cv::Mat& glcm = glcmMatrices[d * angles.size() + a];
				for (int y = 0; y < rows; ++y) {
					for (int x = 0; x < cols; ++x) {
						int x2 = x + dx;
						int y2 = y + dy;

						if (x2 >= 0 && x2 < cols && y2 >= 0 && y2 < rows) {
							int i = image.at<uchar>(y, x);
							int j = image.at<uchar>(y2, x2);
							glcm.at<float>(i, j)++;
						}
					}
				}
				// Make GLCM symmetric if required
				if (symmetric) {
					cv::Mat transposed;
					cv::transpose(glcm, transposed);
					glcm += transposed;
				}
				// Normalize GLCM if required
				if (normed) {
					normalizeGLCM(glcm);
				}
			}
		}

		return glcmMatrices;
	}
	// Function to calculate texture properties
	std::vector<double> GLCM::graycoprops(const std::vector<cv::Mat>& glcmMatrices, const std::string& property) {
		std::vector<double> results;

		for (const auto& glcm : glcmMatrices) {
			// Ensure the GLCM is normalized
			cv::Mat normalizedGLCM = glcm.clone();
			normalizeGLCM(normalizedGLCM);

			int levels = normalizedGLCM.rows;
			cv::Mat weights;

			// Define weights based on the property
			if (property == "energy") {
					double asmValue = cv::sum(normalizedGLCM.mul(normalizedGLCM))[0];
					results.push_back(std::sqrt(asmValue));
					continue;
			}
			else if (property == "homogeneity") {
				weights = cv::Mat::zeros(levels, levels, CV_32F);
				for (int i = 0; i < levels; ++i) {
					for (int j = 0; j < levels; ++j) {
						weights.at<float>(i, j) = 1.0f / (1.0f + (i - j) * (i - j));
					}
				}
			}
			else if (property == "dissimilarity") {
				weights = cv::Mat::zeros(levels, levels, CV_32F);
				for (int i = 0; i < levels; ++i) {
					for (int j = 0; j < levels; ++j) {
						weights.at<float>(i, j) = std::abs(i - j);
					}
				}
			}
			else if (property == "contrast") {
				weights = cv::Mat::zeros(levels, levels, CV_32F);
				for (int i = 0; i < levels; ++i) {
					for (int j = 0; j < levels; ++j) {
						weights.at<float>(i, j) = (i - j) * (i - j);
					}
				}
			}
			else if (property == "correlation") {
				double meanI = 0.0, meanJ = 0.0, stdI = 0.0, stdJ = 0.0, correlation = 0.0;

				for (int i = 0; i < levels; ++i) {
					for (int j = 0; j < levels; ++j) {
						double value = normalizedGLCM.at<float>(i, j);
						meanI += i * value;
						meanJ += j * value;
					}
				}

				for (int i = 0; i < levels; ++i) {
					for (int j = 0; j < levels; ++j) {
						double value = normalizedGLCM.at<float>(i, j);
						stdI += (i - meanI) * (i - meanI) * value;
						stdJ += (j - meanJ) * (j - meanJ) * value;
					}
				}

				stdI = std::sqrt(stdI);
				stdJ = std::sqrt(stdJ);

				for (int i = 0; i < levels; ++i) {
					for (int j = 0; j < levels; ++j) {
						double value = normalizedGLCM.at<float>(i, j);
						correlation += ((i - meanI) * (j - meanJ) * value);
					}
				}

				if (stdI > 0 && stdJ > 0) {
					correlation /= (stdI * stdJ);
				}
				else {
					correlation = 1.0; // Handle near-zero standard deviation
				}

				results.push_back(correlation);
				continue;
			}
		
			else if (property == "ASM") {
				double asmValue = cv::sum(normalizedGLCM.mul(normalizedGLCM))[0];
				results.push_back(asmValue);
				continue;
			}

			
			else {
				return std::vector<double>();
			}

			// Compute the property based on the weights
			if (!weights.empty()) {
				double result = cv::sum(normalizedGLCM.mul(weights))[0];
				results.push_back(result);
			}
		}

		return results;
	}
	
	bool ANSFIRENSMOKE::OptimizeModel(bool fp16, std::string& optimizedModelFolder) {
		if (_engineType == EngineType::NVIDIA_GPU) // NVIDIA CUDA
		{
			return _gpuObjectDetector.OptimizeModel(fp16, optimizedModelFolder);
		}
		else {
			return _cpuObjectDetector.OptimizeModel(fp16, optimizedModelFolder);
		}
	}
	bool ANSFIRENSMOKE::LoadModel(const std::string& modelZipFilePath, const std::string& modelZipPassword) {
		try {
			if (_engineType == EngineType::NVIDIA_GPU) // NVIDIA CUDA
			{
				return _gpuObjectDetector.LoadModel(modelZipFilePath, modelZipPassword);
			}
			else {
				return _cpuObjectDetector.LoadModel(modelZipFilePath, modelZipPassword);
			}
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSFIRENSMOKE::LoadModel", e.what(), __FILE__, __LINE__);
			return false;
		}
	}
	bool ANSFIRENSMOKE::LoadModelFromFolder(std::string licenseKey, ModelConfig modelConfig, std::string modelName, const std::string& modelFolder, std::string& labelMap) {
		try {
			if (_engineType == EngineType::NVIDIA_GPU) // NVIDIA CUDA
			{
				return _gpuObjectDetector.LoadModelFromFolder(licenseKey, modelConfig,modelName,modelFolder,labelMap);
			}
			else {
				return _cpuObjectDetector.LoadModelFromFolder(licenseKey, modelConfig,modelName, modelFolder, labelMap);
			}
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSFIRENSMOKE::LoadModel", e.what(), __FILE__, __LINE__);
			return false;
		}
	}
	bool ANSFIRENSMOKE::Initialize(std::string licenseKey, ModelConfig modelConfig, const std::string& modelZipFilePath, const std::string& modelZipPassword, std::string& labelMap) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		try {
			ANSODBase::CheckLicense();
			if (!_licenseValid) return false;
			_modelConfig = modelConfig;
			_engineType = ANSLicenseHelper::CheckHardwareInformation();
			if (_engineType == EngineType::NVIDIA_GPU) // NVIDIA CUDA
			{
				_modelConfig.detectionType = DetectionType::DETECTION;
				_modelConfig.modelType = ModelType::YOLOV10RTOD;
				_isInitialized = _gpuObjectDetector.Initialize(licenseKey, _modelConfig, modelZipFilePath, modelZipPassword, labelMap);
			}
			else {
				_modelConfig.detectionType = DetectionType::DETECTION;
				_modelConfig.modelType = ModelType::YOLOV10OVOD;
				_isInitialized = _cpuObjectDetector.Initialize(licenseKey, _modelConfig, modelZipFilePath, modelZipPassword, labelMap);
			}

			//this->_hsvThreshold = 0.05;
			//this->_fire_colour.push_back(std::pair(
			//	cv::Scalar(0, 10, 200), cv::Scalar(40, 255, 255)) 
			//);
			//this->_smoke_colour.push_back(std::pair(
			//	cv::Scalar(0, 0, 51), cv::Scalar(180, 60, 204)) //(0,0,80)(0,0,51) -> (180,60,200) or (180, 128, 204)
			//);

			this->_hsvThreshold = 0.25;
			this->_fire_colour.push_back(std::pair(
				cv::Scalar(0, 0, 179), cv::Scalar(255, 255, 255)) // 0,0,179 -> 255, 255, 255
			);
			this->_smoke_colour.push_back(std::pair(
				cv::Scalar(0, 0, 51), cv::Scalar(180, 128, 204)) // 0,0,0 -> 255, 255, 127
			);

			this->_previousDetectedArea = cv::Rect(0, 0, 0, 0);
			_detectedArea.width = 0;
			_detectedArea.height = 0;
			_classifierModelPath = "C:\\ProgramData\\ANSCENTER\\Shared\\FireNSmoke.ann";
			if (FileExist(_classifierModelPath)) {
				_annhub.Init(licenseKey, _classifierModelPath);
				_classifierInitialized = true;
				std::string colourSpace = "RGB";
				std::vector<std::string> properties = { "energy","homogeneity","dissimilarity","contrast","correlation"};
				_extractor.Init(colourSpace, properties);
			}
			return _isInitialized;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSFIRENSMOKE::Initialize", e.what(), __FILE__, __LINE__);
			return false;
		}
	}
	std::vector<Object> ANSFIRENSMOKE::ProcecssDetection(const std::vector<Object>& detectedObjects, const std::string& camera_id, int threshold) {
		// Get detection queue
		std::deque<std::vector<Object>> detectionQueue=DequeueDetection(camera_id);

		// Step 1: Initialize vector to store frequently appearing objects
		std::vector<Object> frequentObjects;

		// Step 2: Check each object in detectedObjects
		for (const auto& detectedObj : detectedObjects) {
			int occurrenceCount = 0;
			// Check if the detectedObj appears at least 8 times in the queue
			for (const auto& queueDetection : detectionQueue) {
				for (const auto& queueObj : queueDetection) {
					if (isOverlayObject(detectedObj, queueObj)) {
						occurrenceCount++;
						break;  // Found a match in this detection, move to the next queue element
					}
				}
				if (occurrenceCount >= threshold) break;  // No need to check further if already counted 8
			}
			if (occurrenceCount >= threshold) {
				frequentObjects.push_back(detectedObj);
			}
		}
		// Return the array of frequently appearing objects
		return frequentObjects;
	}
	cv::Rect ANSFIRENSMOKE::CreateMinimumSquareBoundingBox(const std::vector<Object>& detectedObjects, int minSize) {
		// Ensure there are rectangles to process
		int adMinSize = minSize-20;
		if (adMinSize < 0) adMinSize = 0;
		if (detectedObjects.empty()) return cv::Rect(0, 0, minSize, minSize);

		// Initialize min and max coordinates
		int minX = detectedObjects[0].box.x;
		int minY = detectedObjects[0].box.y;
		int maxX = detectedObjects[0].box.x + detectedObjects[0].box.width;
		int maxY = detectedObjects[0].box.y + detectedObjects[0].box.height;

		// Calculate bounding box that includes all rectangles
		for (const auto& rect : detectedObjects) {
			minX = std::min(minX, rect.box.x);
			minY = std::min(minY, rect.box.y);
			maxX = std::max(maxX, rect.box.x + rect.box.width);
			maxY = std::max(maxY, rect.box.y + rect.box.height);
		}

		// Calculate width and height of the bounding box
		int width = maxX - minX;
		int height = maxY - minY;

		// Determine the size of the square
		int squareSize = std::max({ width, height, adMinSize });

		// Center the square around the bounding box
		int centerX = minX + width / 2;
		int centerY = minY + height / 2;
		int squareX = centerX - squareSize / 2;
		int squareY = centerY - squareSize / 2;

		return cv::Rect(squareX - 10, squareY - 10, squareSize + 20, squareSize + 20); // add 10 pixels to the square
	}
	std::vector<Object> ANSFIRENSMOKE::RunInference(const cv::Mat& input) {
		return RunInference(input, "SmokeCam");
	}

	std::vector<Object> ANSFIRENSMOKE::RunInference(const cv::Mat& input, const std::string& camera_id) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		std::vector<Object> output;
		output.clear();
		if (!_licenseValid) {
			this->_logger.LogError("ANSFIRENSMOKE::RunInference", "Invalid License", __FILE__, __LINE__);
			return output;
		}
		if (!_isInitialized) {
			this->_logger.LogError("ANSFIRENSMOKE::RunInference", "Model is not initialized", __FILE__, __LINE__);
			return output;
		}
		try {
			if (input.empty()) return output;
			if ((input.cols < 10) || (input.rows < 10)) return output;
			//1. Check movement detection on entired image
			std::vector<Object> movementObjects = DetectMovement(input, camera_id);
			if (!movementObjects.empty()) {
				std::vector<Object> smokeNSmokeRects;
				//2. Check if the moving object is a
				for (Object& obj : movementObjects) {
					//bool fcol_detected = this->MajorityColourInFrame(obj.mask, this->_fire_colour, _hsvThreshold);
					//bool scol_detected = this->MajorityColourInFrame(obj.mask, this->_smoke_colour, 0.85);
					//if (fcol_detected || scol_detected) {
					//	smokeNSmokeRects.push_back(obj);
					//}
					smokeNSmokeRects.push_back(obj);
				}
				int detectedArea = _detectedArea.width* _detectedArea.height;
				if ((!smokeNSmokeRects.empty())||
					(detectedArea>0)) {
					if ((_detectedArea.width < 10) || (_detectedArea.height <10))
					{ 
						_detectedArea = CreateMinimumSquareBoundingBox(smokeNSmokeRects, 640);
						int x1_new = _detectedArea.x;
						int y1_new = _detectedArea.y;
						_detectedArea.x = std::max(x1_new, 0);
						_detectedArea.y = std::max(y1_new, 0);
						_detectedArea.width = std::min(640, input.cols - _detectedArea.x);
						_detectedArea.height = std::min(640, input.rows - _detectedArea.y);
					}

					//cv::Mat draw = input.clone();
					//// draw rectangles
					//for (const auto& rect : smokeNSmokeRects) {
					//	cv::rectangle(draw, rect.box, cv::Scalar(0, 255, 0), 2);
					//}
					//cv::rectangle(draw, _detectedArea, cv::Scalar(0, 0, 255), 2);
					//// Diplsay the frame with the combined detected areas
					//cv::imshow("Combined Detected Areas", draw);
					//cv::waitKey(1);
					//draw.release();

					cv::Mat frame = input.clone();
					std::vector<Object> detectedObjects;
					detectedObjects.clear();
					if (_detectedArea.width* _detectedArea.height > 0) {
						cv::Mat detectedObj = frame(_detectedArea).clone();  // Get sub-image of the detected area
						if (_engineType == EngineType::NVIDIA_GPU) {  // NVIDIA CUDA
							detectedObjects = _gpuObjectDetector.RunInference(detectedObj);
						}
						else {
							detectedObjects = _cpuObjectDetector.RunInference(detectedObj);
						}
						detectedObj.release();
					}		
					frame.release();

					// Do detections
					float maxScore = 0.0;
					if (!detectedObjects.empty()) {
						for (auto& detectedObj : detectedObjects) {
							detectedObj.box.x += _detectedArea.x;
							detectedObj.box.y += _detectedArea.y;
							detectedObj.cameraId = camera_id;
							if (detectedObj.classId != 1) {
								if (detectedObj.classId == 2) {
									if (detectedObj.confidence > 0.5)output.push_back(detectedObj);
								}
								else output.push_back(detectedObj);
								if (maxScore < detectedObj.confidence) {
									int cropSize = 640;
									int imagegSize = std::max(input.cols, input.rows);
									if (imagegSize > 1920) cropSize = 640;
									else if (imagegSize > 1280) cropSize = 480;
									else if (imagegSize > 640) cropSize = 320;
									else cropSize = 224;
									maxScore = detectedObj.confidence;
									int x1 = detectedObj.box.x;
									int y1 = detectedObj.box.y;
									int xc = x1 + detectedObj.box.width / 2;
									int yc = y1 + detectedObj.box.height / 2;
									int  x1_new = std::max(xc - cropSize/2, 0);
									int  y1_new = std::max(yc - cropSize/2, 0);
									x1_new = std::min(x1_new, input.cols- cropSize);
									y1_new = std::min(y1_new, input.rows- cropSize);
									_detectedArea.x = std::max(x1_new, 0);
									_detectedArea.y = std::max(y1_new, 0);
									_detectedArea.width = std::min(cropSize, input.cols - _detectedArea.x);
									_detectedArea.height = std::min(cropSize, input.rows - _detectedArea.y);
								}
								_isFireNSmokeDetected = true;
								_retainDetectedArea = 0;// Reset the retain detected area
							}
							else {
								if (_isFireNSmokeDetected) {
									_retainDetectedArea++;
									if (_retainDetectedArea >= 40) {// Reset detected area after 10 frames
										_detectedArea.width = 0;
										_detectedArea.height = 0;
										_retainDetectedArea = 0;
										_isFireNSmokeDetected = false;// Reset the retain detected area
									}
								}
								else {
									_detectedArea.width = 0;
									_detectedArea.height = 0;
									_retainDetectedArea = 0;
								}	
							}
						}
					}
					else {
						if (_isFireNSmokeDetected) {
							_retainDetectedArea++;
							if (_retainDetectedArea >= 40) {// Reset detected area after 10 frames
								_detectedArea.width = 0;
								_detectedArea.height = 0;
								_retainDetectedArea = 0;
								_isFireNSmokeDetected = false;// Reset the retain detected area
							}
						}
						else {
							_detectedArea.width = 0;
							_detectedArea.height = 0;
							_retainDetectedArea = 0;
						}
					}
				}
			}
			// Always adding the detection to the queue
			EnqueueDetection(output, camera_id);
			std::vector<Object> frequentObjects = ProcecssDetection(output, camera_id, SMOKE_THRESHOLD_SIZE);
			return frequentObjects;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSFIRENSMOKE::RunInference", e.what(), __FILE__, __LINE__);
			return output;
		}
	}
	
	// For debug
	/*
		std::vector<Object> ANSFIRENSMOKE::RunInference(const cv::Mat& input, const std::string& camera_id) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		std::vector<Object> output;
		output.clear();
		if (!_licenseValid) {
			this->_logger.LogError("ANSFIRENSMOKE::RunInference", "Invalid License", __FILE__, __LINE__);
			return output;
		}
		if (!_isInitialized) {
			this->_logger.LogError("ANSFIRENSMOKE::RunInference", "Model is not initialized", __FILE__, __LINE__);
			return output;
		}
		try {
			//1. Check movement detection on entired image
			std::vector<Object> stablizedMovementObjects = DetectMovement(input, camera_id);
			//std::vector<Object> stablizedMovementObjects= StablizeDetection(mObjects, camera_id);
			if (!stablizedMovementObjects.empty()) {
				if (stablizedMovementObjects.size() < 6) { // Otherwise, it could be noise
					//// draw rectangles
					cv::Mat draw = input.clone();
					for (auto movedObject : stablizedMovementObjects) {
						cv::rectangle(draw, movedObject.box, cv::Scalar(0, 128, 128), 2);
						if ((_detectedArea.width == 0) || (_detectedArea.height == 0)) {
							// Create a 640x640 rectangle centered on the detected object
							if (calculateIOU(movedObject.box, _previousDetectedArea) < IOU_THRESHOLD)
							{
								int x1 = movedObject.box.x;
								int y1 = movedObject.box.y;
								int xc = x1 + movedObject.box.width / 2;
								int yc = y1 + movedObject.box.height / 2;
								int  x1_new = std::max(xc - 320, 0);
								int  y1_new = std::max(yc - 320, 0);
								int  x2_new = std::min(xc + 320, input.cols);
								int  y2_new = std::min(yc + 320, input.rows);
								_detectedArea = cv::Rect(x1_new, y1_new, x2_new - x1_new, y2_new - y1_new);
								_previousDetectedArea = _detectedArea;
							}
						}
						_detectedArea.x = std::max(_detectedArea.x, 0);
						_detectedArea.y = std::max(_detectedArea.y, 0);
						_detectedArea.width = std::min(_detectedArea.width, input.cols - _detectedArea.x);
						_detectedArea.height = std::min(_detectedArea.height, input.rows - _detectedArea.y);
						//// Diplsay the frame with the combined detected areas
						cv::rectangle(draw, _detectedArea, cv::Scalar(0, 128, 0), 2);
						cv::Mat frame = input.clone();
						std::vector<Object> detectedObjects;
						detectedObjects.clear();
						if ((_detectedArea.width > 0) && (_detectedArea.height > 0)) {
							cv::Mat detectedObj = frame(_detectedArea).clone();  // Get sub-image of the detected area
							if (_engineType == EngineType::NVIDIA_GPU) {  // NVIDIA CUDA
								detectedObjects = _gpuObjectDetector.RunInference(detectedObj);
							}
							else {
								detectedObjects = _cpuObjectDetector.RunInference(detectedObj);
							}
							if (!detectedObjects.empty()) {
								for (Object detectedObj : detectedObjects) {
									detectedObj.box.x += _detectedArea.x;
									detectedObj.box.y += _detectedArea.y;
									detectedObj.cameraId = camera_id;
									bool detected = false;
									if (detectedObj.classId != 1) {
										output.push_back(detectedObj);
										cv::rectangle(draw, detectedObj.box, cv::Scalar(0, 0, 255), 2);
										// Create a 640x640 rectangle centered on the detected object
										int x1 = detectedObj.box.x;
										int y1 = detectedObj.box.y;
										int xc = x1 + detectedObj.box.width / 2;
										int yc = y1 + detectedObj.box.height / 2;
										int  x1_new = std::max(xc - 160, 0);
										int  y1_new = std::max(yc - 160, 0);
										int  x2_new = std::min(xc + 160, input.cols);
										int  y2_new = std::min(yc + 160, input.rows);
										_detectedArea = cv::Rect(x1_new, y1_new, x2_new - x1_new, y2_new - y1_new);
										detected = true;
										_focusCount = 0;
									}
									else {
										if (detected == false) {
											_focusCount++;
											if (_focusCount >= 15) {
												_detectedArea.width = 0;
												_detectedArea.height = 0;
												_focusCount = 0;
											}

										}
									}
								}
							}
							else { // we could not find any detection then we move to the next detected area
								_focusCount++;
								if (_focusCount >= 5) { // stay there fore 5 frames before move to next detected area
									_detectedArea.width = 0;
									_detectedArea.height = 0;
									_focusCount = 0;
								}
							}
							detectedObj.release();
						}
						frame.release();
					}
					cv::imshow("Combined Detected Areas", draw);
					cv::waitKey(1);
					draw.release();
				}
			}
			// Always adding the detection to the queue
			EnqueueDetection(output, camera_id);
			std::vector<Object> frequentObjects = ProcecssDetection(output, camera_id, SMOKE_THRESHOLD_SIZE);
			return frequentObjects;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSFIRENSMOKE::RunInference", e.what(), __FILE__, __LINE__);
			return output;
		}
	}
	*/




	ANSFIRENSMOKE::~ANSFIRENSMOKE() {
		try {
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSFIRENSMOKE::~ANSFIRENSMOKE()", e.what(), __FILE__, __LINE__);
		}
	}
	bool ANSFIRENSMOKE::Destroy() {
		try {
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSFIRENSMOKE::Destroy()", e.what(), __FILE__, __LINE__);
			return false;
		}

	}
	cv::Point2f ANSFIRENSMOKE::CalculateCentroid(const std::vector<cv::Rect>& group) {

		int totalArea = 0;
		cv::Point2f centroid(0, 0);
		for (const auto& rect : group) {
			int area = rect.width * rect.height;
			centroid += cv::Point2f(rect.x + rect.width / 2.0f, rect.y + rect.height / 2.0f) * area;
			totalArea += area;
		}
		if (totalArea > 0) {
			centroid *= (1.0f / totalArea);
		}
		return centroid;
	}
	cv::Point2f ANSFIRENSMOKE::CalculateGroupCenter(const std::vector<cv::Rect>& group) {
		if (group.empty()) {
			return cv::Point2f(0, 0);
		}
		// Initialize min and max points with the first rectangle's values
		int minX = group[0].x;
		int minY = group[0].y;
		int maxX = group[0].x + group[0].width;
		int maxY = group[0].y + group[0].height;

		// Loop through the group to find the minimum and maximum points
		for (const auto& rect : group) {
			minX = std::min(minX, rect.x);
			minY = std::min(minY, rect.y);
			maxX = std::max(maxX, rect.x + rect.width);
			maxY = std::max(maxY, rect.y + rect.height);
		}

		// Calculate the center point
		int centerX = (minX + maxX) / 2;
		int centerY = (minY + maxY) / 2;

		return cv::Point2f(centerX, centerY);
	}
	std::vector<cv::Rect> ANSFIRENSMOKE::CreateCoveringMaskRects(const cv::Mat& image, const std::vector<cv::Rect>& maskRects, float maxDistance) {
		std::vector<cv::Rect> coveringRects;
		std::vector<bool> visited(maskRects.size(), false);

		// Loop through each maskRect to group nearby rectangles
		for (size_t i = 0; i < maskRects.size(); ++i) {
			if (visited[i]) continue;

			// Start a new group with the current rectangle
			std::vector<cv::Rect> group;
			group.push_back(maskRects[i]);
			visited[i] = true;
			// Find nearby rectangles to add to the current group
			for (size_t j = i + 1; j < maskRects.size(); ++j) {
				if (visited[j]) continue;
				// Calculate distance between centroids of the two rectangles
				cv::Point2f center1(maskRects[i].x + maskRects[i].width / 2.0f, maskRects[i].y + maskRects[i].height / 2.0f);
				cv::Point2f center2(maskRects[j].x + maskRects[j].width / 2.0f, maskRects[j].y + maskRects[j].height / 2.0f);
				float distance = cv::norm(center1 - center2);

				// If the distance is within the maxDistance, add it to the group
				if (distance < maxDistance) {
					group.push_back(maskRects[j]);
					visited[j] = true;
				}
			}
			// Calculate the centroid of the group
			cv::Point2f centroid = CalculateCentroid(group);

			// Create a 640x640 rectangle centered on the group's centroid
			cv::Point topLeft(static_cast<int>(centroid.x) - 320, static_cast<int>(centroid.y) - 320);		
			// Check if the rectangle is within the image bounds
			topLeft.x = std::max(topLeft.x, 0);
			topLeft.y = std::max(topLeft.y, 0);
			topLeft.x = std::min(topLeft.x, image.cols - 640);
			topLeft.y = std::min(topLeft.y, image.rows - 640);
			coveringRects.emplace_back(topLeft, cv::Size(640, 640));
		}

		return coveringRects;
	}
	cv::Mat ANSFIRENSMOKE::CreateBinaryImageWithRects(int width, int height, const std::vector<cv::Rect>& rects) {
		// Initialize the image with zeros (background value 0)
		cv::Mat image = cv::Mat::zeros(height, width, CV_8UC1);

		// Iterate over each rectangle
		for (const auto& rect : rects) {
			// Set pixels within each rectangle to 1
			cv::rectangle(image, rect, cv::Scalar(1), cv::FILLED);
		}
		return image;
	}
	bool ANSFIRENSMOKE::MajorityColourInFrame(cv::Mat frame, Range range, float area_threshold)
	{
		cv::Mat img_hsv;
		try {
			cv::Mat blur, fireMask;
			cv::GaussianBlur(frame, blur, cv::Size(21, 21), 0);
			cv::cvtColor(blur, img_hsv, cv::COLOR_BGR2HSV);
			cv::inRange(img_hsv, range.first, range.second, fireMask);

			cv::erode(fireMask, fireMask, cv::Mat(), cv::Point(-1, -1), 2);
			cv::dilate(fireMask, fireMask, cv::Mat(), cv::Point(-1, -1), 2);
			return (cv::countNonZero(fireMask) / (frame.rows * frame.cols)) > area_threshold;

		}
		catch (cv::Exception& e) {
			return false;
		}
	}
	bool ANSFIRENSMOKE::MajorityColourInFrame(cv::Mat frame, std::vector<Range> ranges, float area_threshold)
	{
		unsigned int current_area = 0;
		cv::Mat img_hsv;
		try {
			cv::cvtColor(frame, img_hsv, cv::COLOR_BGR2HSV);
			for (const Range& range : ranges) {
				cv::inRange(img_hsv, range.first, range.second, frame);
				current_area += cv::countNonZero(frame);
			}
		}
		catch (cv::Exception& e) {
			return false;
		}
		return (float)current_area / ((float)frame.rows * frame.cols) > area_threshold;
	}
	bool ANSFIRENSMOKE::DetectFireNSmokeColourInFrame(const cv::Mat& frame, const cv::Rect bBox,  const std::vector<Range>& ranges, float area_threshold) {
		// Validate inputs
		if (frame.empty()) {
			std::cerr << "Error: Input frame is empty." << std::endl;
			return false;
		}
		if (ranges.empty()) {
			std::cerr << "Error: No HSV ranges provided for detection." << std::endl;
			return false;
		}
		if (area_threshold < 0.0f || area_threshold > 1.0f) {
			std::cerr << "Error: Area threshold must be between 0.0 and 1.0." << std::endl;
			return false;
		}

		try {
			// Get the actual area of the bounding box
			int x1 = std::max(bBox.x-20, 0);
			int y1 = std::max(bBox.y-20, 0);
			int x2 = std::min(bBox.x + bBox.width + 20, frame.cols);
			int y2 = std::min(bBox.y + bBox.height + 20, frame.rows);
			cv::Rect roi(x1, y1, x2 - x1, y2 - y1);
			cv::Mat roiFrame = frame(roi).clone();

			unsigned int total_area = roiFrame.rows * roiFrame.cols; // Total area of the frame
			unsigned int matched_area = 0;

			// Convert input frame to HSV color space
			// Apply guasian to remove noise
			cv::Mat blur;
			cv::GaussianBlur(roiFrame, blur, cv::Size(21, 21), 0);

			cv::Mat img_hsv;
			cv::cvtColor(blur, img_hsv, cv::COLOR_BGR2HSV);

			// Create an empty mask to accumulate results
			cv::Mat combined_mask = cv::Mat::zeros(img_hsv.size(), CV_8U);

			// Process each HSV range and accumulate the results in the combined mask
			for (const Range& range : ranges) {
				cv::Mat temp_mask;
				cv::inRange(img_hsv, range.first, range.second, temp_mask);
				combined_mask |= temp_mask; // Accumulate results
			}

			// Apply morphological operations to clean up the mask
			cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
			cv::morphologyEx(combined_mask, combined_mask, cv::MORPH_CLOSE, kernel);
			cv::morphologyEx(combined_mask, combined_mask, cv::MORPH_OPEN, kernel);

			// Count the number of non-zero pixels in the combined mask
			matched_area = cv::countNonZero(combined_mask);

			// Check if the matched area exceeds the threshold
			float matched_ratio = static_cast<float>(matched_area) / total_area;
			bool smoke_detected = matched_ratio > area_threshold;

			blur.release();
			img_hsv.release();
			roiFrame.release();
			return smoke_detected;

		}
		catch (const cv::Exception& e) {
			// Handle OpenCV exceptions
			std::cerr << "OpenCV Exception: " << e.what() << std::endl;
			return false;
		}
	}
	bool ANSFIRENSMOKE::IsFireDetected(const cv::Mat image, const cv::Rect bBox) {
		bool isFireColour = this->DetectFireNSmokeColourInFrame(image, bBox,this->_fire_colour, _hsvThreshold);
		return isFireColour;
		bool fireDetected = false;
		auto start1 = std::chrono::system_clock::now();
		if (_classifierInitialized) {
			int w = std::min(bBox.width,bBox.height);
			int paddingSize = static_cast<int>(w * 0.2);
			int padding = std::max(10, paddingSize);
			int x1 = std::max(bBox.x - padding, 0);
			int y1 = std::max(bBox.y - padding, 0);
			int x2 = std::min(bBox.x + bBox.width + padding, image.cols);
			int y2 = std::min(bBox.y + bBox.height + padding, image.rows);
			cv::Rect roi(x1, y1, x2 - x1, y2 - y1);
			cv::Mat roiImage = image(roi).clone();
			cv::Mat rgbImage;
			cv::cvtColor(roiImage, rgbImage, cv::COLOR_BGR2RGB);
			std::vector<double> glcmFeatures = _extractor.createFeatureVector(rgbImage);
			std::vector<double> output = _annhub.Inference(glcmFeatures);
			if (output.size() < 1) fireDetected = true;
			double fireValue = abs(output[0]);
			if (fireValue < 0.2) {
				fireDetected= true;
			}
			rgbImage.release();
			roiImage.release();
		}
		auto end1 = std::chrono::system_clock::now();
		auto elapsed1 = std::chrono::duration_cast<std::chrono::milliseconds>(end1 - start1);
		std::cout << "Elapsed time for fire detection: " << elapsed1.count() << " ms" << std::endl;
		bool isFire =  fireDetected || isFireColour;
		return isFire;
	}
	bool ANSFIRENSMOKE::IsSmokeDetected(const cv::Mat image, const cv::Rect bBox) {
		bool isSmokeColour = this->DetectFireNSmokeColourInFrame(image, bBox,this->_smoke_colour, 0.85);
		return isSmokeColour;
		bool smokeDetected = true;
		if (_classifierInitialized) {
			int w = std::min(bBox.width, bBox.height);
			int paddingSize = static_cast<int>(w * 0.2);
			int padding = std::max(10, paddingSize);
			int x1 = std::max(bBox.x - padding, 0);
			int y1 = std::max(bBox.y - padding, 0);
			int x2 = std::min(bBox.x + bBox.width + padding, image.cols);
			int y2 = std::min(bBox.y + bBox.height + padding, image.rows);
			cv::Rect roi(x1, y1, x2 - x1, y2 - y1);
			cv::Mat roiImage = image(roi).clone();
			cv::Mat rgbImage;
			cv::cvtColor(roiImage, rgbImage, cv::COLOR_BGR2RGB);
			std::vector<double> glcmFeatures = _extractor.createFeatureVector(rgbImage);
			std::vector<double> output = _annhub.Inference(glcmFeatures);
			if (output.size() < 1) smokeDetected = false;
			double smokeValue = abs(1 - output[0]);
			if (smokeValue > 0.2) {
				smokeDetected= false;
			}
			rgbImage.release();
			roiImage.release();
		}
		bool isSmoke = isSmokeColour&& smokeDetected;
		return isSmoke;
	}
	bool ANSFIRENSMOKE::IsOverlap(const cv::Rect& target, const std::vector<Object>& rectArray) {
		// Validate inputs
		if (target.area() <= 0 || rectArray.empty()) {
			return false; // No overlap possible
		}
		// Minimum overlap percentage (30%)
		const float minOverlapThreshold = 0.30f;

		// Check if the target rectangle overlaps sufficiently with any rectangle in the array
		for (const auto& rect : rectArray) {
			// Calculate intersection area
			cv::Rect intersection = rect.box & target;
			int intersectionArea = intersection.area();
			// If there's an intersection, check the overlap ratio
			if (intersectionArea > 0) {
				// Calculate the smaller rectangle's area
				int smallerArea = std::min(rect.box.area(), target.area());

				// Calculate overlap ratio
				float overlapRatio = static_cast<float>(intersectionArea) / smallerArea;

				// Check if overlap ratio meets the threshold
				if (overlapRatio >= minOverlapThreshold) {
					return true; // Sufficient overlap found
				}
			}
		}

		return false; // No sufficient overlap found
	}
	float ANSFIRENSMOKE::calculateIOU(const cv::Rect& box_a, const cv::Rect& box_b) {
		int x_a = std::max(box_a.x, box_b.x);
		int y_a = std::max(box_a.y, box_b.y);
		int x_b = std::min(box_a.x + box_a.width, box_b.x + box_b.width);
		int y_b = std::min(box_a.y + box_a.height, box_b.y + box_b.height);

		if (x_b < x_a || y_b < y_a) {
			return 0.0f;
		}

		float intersection = (x_b - x_a) * (y_b - y_a);
		float area_a = box_a.width * box_a.height;
		float area_b = box_b.width * box_b.height;

		/*
		NOTE:
			- Esentially area of overlap over area of union.
		*/
		return intersection / (area_a + area_b - intersection);
	}
	std::vector<Object> ANSFIRENSMOKE::StablizeDetection(const std::vector<Object>& detectedObjects, const std::string& camera_id) {
		_stablization_queue.push_back(detectedObjects);
		while (_stablization_queue.size() > HISTORY_SIZE) {
			_stablization_queue.pop_front();
		}
		for (auto it = _persistent_detections.begin(); it != _persistent_detections.end();) {
			it->second--;
			if (it->second <= 0) {
				it = _persistent_detections.erase(it);

				/*
				NOTE:
					- Queue to stablize detection and minimize flickering. We dequeue
					and make sure our queue is less than or equal to the size we define
					(technically almost every frame).
				*/
				_stablization_queue.push_back(detectedObjects);
				while (_stablization_queue.size() > HISTORY_SIZE) {
					_stablization_queue.pop_front();
				}
			}
			else {
				it++;
			}
		}
		std::vector<Object> new_stable_detections;
		for (const Object& obj : detectedObjects) {
			std::vector<cv::Rect> matching_boxes;
			matching_boxes.reserve(HISTORY_SIZE);
			matching_boxes.push_back(obj.box);
			int fire_count = 0, smoke_count = 0;
			if (obj.classId == 0) {
				fire_count = 1;
			}
			else if (obj.classId == 2) {
				smoke_count = 1;
			}
			float recent_weight = 1.0;
			for (auto it = _stablization_queue.rbegin(); it != _stablization_queue.rend(); ++it) {
				bool matched = false;
				for (const Object& past_obj : *it) {
					if (calculateIOU(obj.box, past_obj.box) > IOU_THRESHOLD) {
						matching_boxes.push_back(past_obj.box);
						if (past_obj.classId == 0) {
							fire_count += recent_weight;
						}
						else if (past_obj.classId == 2) {
							smoke_count += recent_weight;
						}
						matched = true;
						break;
					}
				}

				const float WEIGHT_DECAY = 0.9;
				recent_weight *= WEIGHT_DECAY;
			}
			if (matching_boxes.size() >= MIN_MATCHES) {
				Object stable_obj;
				float sum_x = 0, sum_y = 0, sum_w = 0, sum_h = 0;
				float weight_sum = 0;
				float weight = 1.0;
				for (const auto& box : matching_boxes) {
					sum_x += box.x * weight;
					sum_y += box.y * weight;
					sum_w += box.width * weight;
					sum_h += box.height * weight;
					weight_sum += weight;
					weight *= ALPHA;
				}

				cv::Rect smoothed_box(
					int(sum_x / weight_sum),
					int(sum_y / weight_sum),
					int(sum_w / weight_sum),
					int(sum_h / weight_sum)
				);
				bool matched_persistent = false;
				for (auto& persistent : _persistent_detections) {
					if (calculateIOU(persistent.first.box, smoothed_box) > IOU_THRESHOLD) {
						float dx = smoothed_box.x - persistent.first.box.x;
						float dy = smoothed_box.y - persistent.first.box.y;
						if (std::sqrt(dx * dx + dy * dy) < MAX_MOVEMENT) {
							persistent.first.box.x = int(ALPHA * persistent.first.box.x +
								(1 - ALPHA) * smoothed_box.x);
							persistent.first.box.y = int(ALPHA * persistent.first.box.y +
								(1 - ALPHA) * smoothed_box.y);
							persistent.first.box.width = int(ALPHA * persistent.first.box.width +
								(1 - ALPHA) * smoothed_box.width);
							persistent.first.box.height = int(ALPHA * persistent.first.box.height +
								(1 - ALPHA) * smoothed_box.height);
							if (fire_count > smoke_count) {
								persistent.first.classId = 0;
								persistent.first.className = "Fire";
							}
							else {
								persistent.first.classId = 2;
								persistent.first.className = "Smoke";
							}
						}
						persistent.second = MINIMUM_STABLE_DURATION;
						matched_persistent = true;
						break;
					}
				}
				if (!matched_persistent) {
					stable_obj.box = smoothed_box;

					if (fire_count > smoke_count) {
						stable_obj.classId = 0;
						stable_obj.className = "Fire";
					}
					else {
						stable_obj.classId = 2;
						stable_obj.className = "Smoke";
					}
					_persistent_detections.push_back({ stable_obj, MINIMUM_STABLE_DURATION });
					new_stable_detections.push_back(stable_obj);
				}
			}
		}
		std::vector<Object> final_detections;
		final_detections.reserve(_persistent_detections.size());
		for (const auto& persistent : _persistent_detections) {
			final_detections.push_back(persistent.first);
		}
		return final_detections;

	}
}


// Old implementation




/*
* 
* std::vector<Object> ANSFIRENSMOKE::RunInference(const cv::Mat& input, const std::string& camera_id) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		std::vector<Object> output;
		output.clear();
		if (!_licenseValid) {
			this->_logger.LogError("ANSFIRENSMOKE::RunInference", "Invalid License", __FILE__, __LINE__);
			return output;
		}
		if (!_isInitialized) {
			this->_logger.LogError("ANSFIRENSMOKE::RunInference", "Model is not initialized", __FILE__, __LINE__);
			return output;
		}
		try {
			//1. Check movement detection on entired image
			//std::vector<Object> mObjects = DetectMovement(input, camera_id);
			std::vector<Object> stablizedMovementObjects = DetectMovement(input, camera_id);
			if (!stablizedMovementObjects.empty()) {
				std::vector<Object> fireNSmokeRects;
				fireNSmokeRects.clear();
				//2. Check if the moving object is a
				for (Object& obj : stablizedMovementObjects) {
					bool fcol_detected = this->IsFireDetected(input, obj.box);
					bool scol_detected = this->IsSmokeDetected(input, obj.box);
					if (fcol_detected || scol_detected) {
						fireNSmokeRects.push_back(obj);
					}
				}
				if (!fireNSmokeRects.empty()) {
					cv::Rect detectedArea = CreateMinimumSquareBoundingBox(fireNSmokeRects, 640);
					// estimate that the detected area is within the image bounds
					detectedArea.x = std::max(detectedArea.x, 0);
					detectedArea.y = std::max(detectedArea.y, 0);
					detectedArea.width = std::min(detectedArea.width, input.cols - detectedArea.x);
					detectedArea.height = std::min(detectedArea.height, input.rows - detectedArea.y);

					cv::Mat frame = input.clone();
					std::vector<Object> detectedObjects;
					detectedObjects.clear();
					cv::Mat detectedObj = frame(detectedArea).clone();  // Get sub-image of the detected area
					if (_engineType == EngineType::NVIDIA_GPU) {  // NVIDIA CUDA
						detectedObjects = _gpuObjectDetector.RunInference(detectedObj);
					}
					else {
						detectedObjects = _cpuObjectDetector.RunInference(detectedObj);
					}
					if (!detectedObjects.empty()) {
						for (Object detectedObj : detectedObjects) {
							detectedObj.box.x += detectedArea.x;
							detectedObj.box.y += detectedArea.y;
							detectedObj.cameraId = camera_id;
							if (IsOverlap(detectedObj.box, fireNSmokeRects)) {
								output.push_back(detectedObj);
							}
						}
					}
					detectedObj.release();
					frame.release();
				}
			}
			// Always adding the detection to the queue
			EnqueueDetection(output, camera_id);
			std::vector<Object> frequentObjects = ProcecssDetection(output, camera_id, SMOKE_THRESHOLD_SIZE);
			return frequentObjects;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSFIRENSMOKE::RunInference", e.what(), __FILE__, __LINE__);
			return output;
		}
	}


std::vector<Object> ANSFIRENSMOKE::RunInference(const cv::Mat& input) {
	std::lock_guard<std::recursive_mutex> lock(_mutex);
	std::vector<Object> output;
	output.clear();
	if (!_licenseValid) {
		this->_logger.LogError("ANSFIRENSMOKE::RunInference", "Invalid License", __FILE__, __LINE__);
		return output;
	}
	if (!_isInitialized) {
		this->_logger.LogError("ANSFIRENSMOKE::RunInference", "Model is not initialized", __FILE__, __LINE__);
		return output;
	}
	try {
		std::vector<Object> movementObjects = DetectMovement(input,"1234");
		std::vector<cv::Rect> fireNsmokeRects;
		//2. Check if the moving object is a fire or smoke
		for (Object& obj : movementObjects) {
			bool fcol_detected = this->MajorityColourInFrame(obj.mask, this->_fire_colour, _hsvThreshold);
			bool scol_detected = this->MajorityColourInFrame(obj.mask, this->_smoke_colour, 0.85);
			if (fcol_detected || scol_detected) {
				if (fcol_detected) {
					fireNsmokeRects.push_back(obj.box);
				}
				else if (scol_detected) {
					fireNsmokeRects.push_back(obj.box);
				}
			}
		}
		if ((fireNsmokeRects.size() > 0)) {
			std::vector<Object> detectedObjects;
			if (_engineType == EngineType::NVIDIA_GPU) // NVIDIA CUDA
			{
				detectedObjects = _gpuObjectDetector.RunInference(input);
			}
			else {
				detectedObjects = _cpuObjectDetector.RunInference(input);
			}
			for (const auto& detectedObj : detectedObjects) {
				for (const auto& rect : fireNsmokeRects) {
					if (rect.contains(detectedObj.box.tl()) ||
						rect.contains(detectedObj.box.br()) ||
						rect.contains(cv::Point(detectedObj.box.x + detectedObj.box.width, detectedObj.box.y)) ||
						rect.contains(cv::Point(detectedObj.box.x, detectedObj.box.y + detectedObj.box.height)) ||
						detectedObj.box.contains(rect.tl()) ||
						detectedObj.box.contains(rect.br()) ||
						detectedObj.box.contains(cv::Point(rect.x + rect.width, rect.y)) ||
						detectedObj.box.contains(cv::Point(rect.x, rect.y + rect.height)))
					{
						output.push_back(detectedObj);
						break;
					}
				}
			}
		}
		return output;
	}
	catch (std::exception& e) {
		this->_logger.LogFatal("ANSFIRENSMOKE::RunInference", e.what(), __FILE__, __LINE__);
		return output;
	}
}
*/