#include "ANSCustomFireNSmoke.h"

bool ANSCustomFS::IsDetectedAreaValid(const cv::Rect& area) const {
	return area.width > MIN_ROI_SIZE && area.height > MIN_ROI_SIZE;
}
bool ANSCustomFS::IsFireOrSmoke(int classId, float confidence) const {
	return (classId == 0) || // Fire
		(classId == 2 && confidence >= SMOKE_CONFIDENCE_THRESHOLD); // Smoke
}
bool ANSCustomFS::ValidateMotionCorrelation(const std::vector<cv::Rect>& fireNSmokeRects) const {
	return !fireNSmokeRects.empty() && fireNSmokeRects.size() < MAX_MOTION_TRACKING;
}
void ANSCustomFS::UpdatePositiveDetection() {
	_isFireNSmokeDetected = true;
	_retainDetectedArea = 0;
	_isRealFireFrame = true;
}
void ANSCustomFS::ResetDetectedArea() {
	_detectedArea = cv::Rect(0, 0, 0, 0);
}
void ANSCustomFS::UpdatePriorityRegion(const cv::Mat& frame)
{
	std::vector<ImageSection> sections = divideImage(frame);
	int lowestPriority = getLowestPriorityRegion();
	if (_currentPriority > lowestPriority || _currentPriority == 0) {
		_currentPriority = getHighestPriorityRegion();
	}
	else {
		_currentPriority++;
	}
}
int ANSCustomFS::CalculateOptimalCropSize(const cv::Mat& frame) const {
	int maxDim = std::max(frame.cols, frame.rows);
	if (maxDim > 1920) return 640;
	if (maxDim > 1280) return 480;
	if (maxDim > 640) return 320;
	return 224;
}
void ANSCustomFS::RefineDetectedArea(const cv::Mat& frame, const ANSCENTER::Object& detection) {
	int cropSize = CalculateOptimalCropSize(frame);

	int xc = detection.box.x + detection.box.width / 2;
	int yc = detection.box.y + detection.box.height / 2;

	int x1_new = std::clamp(xc - cropSize / 2, 0, frame.cols - cropSize);
	int y1_new = std::clamp(yc - cropSize / 2, 0, frame.rows - cropSize);

	_detectedArea = cv::Rect(
		x1_new,
		y1_new,
		std::min(cropSize, frame.cols - x1_new),
		std::min(cropSize, frame.rows - y1_new)
	);
}
void ANSCustomFS::ResetDetectionState() {
	ResetDetectedArea();
	_retainDetectedArea = 0;
	_isFireNSmokeDetected = false;
}
void ANSCustomFS::UpdateNoDetectionCondition()
{
	_isRealFireFrame = false;
	_realFireCheck = 0;

	if (_isFireNSmokeDetected) {
		_retainDetectedArea++;
		if (_retainDetectedArea >= RETAINFRAMES) {
			ResetDetectionState();
		}
	}
	else {
		ResetDetectionState();
	}
}
std::vector<CustomObject> ANSCustomFS::ConvertToCustomObjects(const std::vector<ANSCENTER::Object>& objects) {
	std::vector<CustomObject> results;
	results.reserve(objects.size());

	for (const auto& obj : objects) {
		if (obj.confidence >= _detectionScoreThreshold) {
			CustomObject customObj;
			customObj.box = obj.box;
			customObj.classId = obj.classId;
			customObj.confidence = obj.confidence;
			customObj.cameraId = obj.cameraId;
			customObj.className = obj.className;
			customObj.extraInfo = "Detection Score Threshold:" +
				std::to_string(_detectionScoreThreshold);
			results.push_back(customObj);
		}
	}
	return results;
}

void ANSCustomFS::GetModelParameters() {
	if (!_readROIs) {
		cv::Rect exRoi;
		for (auto& roi : _params.ROI_Values) {
			if (roi.Name.find("ExclusiveROIs") != std::string::npos) {
				exRoi.x = roi.ROIPoints[0].x;
				exRoi.y = roi.ROIPoints[0].y;
				exRoi.width = roi.ROIPoints[1].x - roi.ROIPoints[0].x;
				exRoi.height = roi.ROIPoints[2].y - roi.ROIPoints[0].y;
				_exclusiveROIs.push_back(exRoi);
			}
		}

		// Get parameters
		for (auto& param : _params.Parameters) {
			if (param.Name.find("SmokeScore") != std::string::npos) {
				_smokeDetetectionThreshold = std::stof(param.Value);
			}
			else if (param.Name.find("Sensitivity") != std::string::npos) {
				_motionSpecificity = 1.0f - std::stof(param.Value);
				_motionSpecificity = std::clamp(_motionSpecificity, 0.0f, 1.0f);
				// Note: Motion detector threshold is set during LoadModelFromFolder.
				// Runtime threshold update is not supported through ANSLIB API.
			}
		}
		_readROIs = true;
	}
}
std::vector<ANSCENTER::Object> ANSCustomFS::ProcessExistingDetectedArea(
	const cv::Mat& frame,
	const std::string& camera_id,
	const std::vector<cv::Rect>& fireNSmokeRects,
	cv::Mat& draw)
{
#ifdef FNS_DEBUG
	cv::rectangle(draw, _detectedArea, cv::Scalar(255, 255, 0), 2); // Cyan
#endif

	// Run detection on ROI (no clone - just a view into frame)
	cv::Mat activeROI = frame(_detectedArea);
	std::vector<ANSCENTER::Object> detectedObjects;
	_detector->RunInference(activeROI, camera_id.c_str(), detectedObjects);

	if (detectedObjects.empty()) {
		UpdateNoDetectionCondition();
		return {};
	}

	std::vector<ANSCENTER::Object> output;
	output.reserve(detectedObjects.size());

	for (auto& detectedObj : detectedObjects) {
		ProcessDetectedObject(frame, detectedObj, camera_id, fireNSmokeRects, output, draw);
	}

	if (output.empty()) {
		UpdateNoDetectionCondition();
	}

	return output;
}
bool ANSCustomFS::ProcessDetectedObject(
	const cv::Mat& frame,
	ANSCENTER::Object& detectedObj,
	const std::string& camera_id,
	const std::vector<cv::Rect>& fireNSmokeRects,
	std::vector<ANSCENTER::Object>& output, cv::Mat& draw)
{
	// Adjust coordinates to frame space
	detectedObj.box.x += _detectedArea.x;
	detectedObj.box.y += _detectedArea.y;
	detectedObj.cameraId = camera_id;

	// Check exclusive ROI overlap
	if (IsROIOverlapping(detectedObj.box, _exclusiveROIs, INCLUSIVE_IOU_THRESHOLD)) {
		return false;
	}

	// Check confidence threshold
	if (detectedObj.confidence <= _detectionScoreThreshold) {
		UpdateNoDetectionCondition();
		return false;
	}

	// Check if fire or smoke
	if (!IsFireOrSmoke(detectedObj.classId, detectedObj.confidence)) {
		UpdateNoDetectionCondition();
		return false;
	}

	// Check for reflection
	cv::Mat objectMask = frame(detectedObj.box);
	if (detectReflection(objectMask)) {
		UpdateNoDetectionCondition();
		return false;
	}

	// Check area overlap
	float areaOverlap = calculateIoU(_detectedArea, detectedObj.box);
	if (areaOverlap >= MAX_AREA_OVERLAP) {
		UpdateNoDetectionCondition();
		return false;
	}

#ifdef FNS_DEBUG
	cv::Scalar color = (detectedObj.classId == 0) ?
		cv::Scalar(0, 255, 255) : cv::Scalar(255, 0, 255); // Yellow/Purple
	cv::rectangle(draw, detectedObj.box, color, 2);
#endif

	// Check motion correlation
	if (!ValidateMotionCorrelation(fireNSmokeRects)) {
		UpdateNoDetectionCondition();
		return false;
	}

	if (!IsOverlapping(detectedObj, fireNSmokeRects, 0)) {
		UpdateNoDetectionCondition();
		return false;
	}

	// Filter validation
	if (!ValidateWithFilter(frame, detectedObj, camera_id, output, draw))
	{
		return false;
	}

	return true;
}
bool ANSCustomFS::ValidateWithFilter(
	const cv::Mat& frame,
	const ANSCENTER::Object& detectedObj,
	const std::string& camera_id,
	std::vector<ANSCENTER::Object>& output, cv::Mat& draw)
{
	// Skip filter check after sufficient confirmation frames
	if (_realFireCheck > FILTER_VERIFICATION_FRAMES) {
		output.push_back(detectedObj);
		UpdatePositiveDetection();
		return true;
	}

	// Run filter inference
	std::vector<ANSCENTER::Object> filteredObjects;
	_filter->RunInference(frame, camera_id.c_str(), filteredObjects);
	std::vector<ANSCENTER::Object> excludedObjects;

	for (const auto& filteredObj : filteredObjects) {
		if (EXCLUDED_FILTER_CLASSES.find(filteredObj.classId) == EXCLUDED_FILTER_CLASSES.end()) {
			excludedObjects.push_back(filteredObj);

#ifdef FNS_DEBUG
			cv::rectangle(draw, filteredObj.box, cv::Scalar(0, 255, 0), 2);
			cv::putText(draw, filteredObj.className,
				cv::Point(filteredObj.box.x, filteredObj.box.y - 10),
				cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 2);
#endif
		}
	}

	// Check if detection overlaps with excluded objects
	if (excludedObjects.empty() || !IsOverlapping(detectedObj, excludedObjects, 0)) {
		output.push_back(detectedObj);
		UpdatePositiveDetection();
		_realFireCheck++;
		return true;
	}
	else {
		// Decrement but don't go negative
		_realFireCheck = std::max(0, _realFireCheck - 1);
		_isRealFireFrame = (_realFireCheck > 0);
		return false;
	}
}

std::vector<ANSCENTER::Object> ANSCustomFS::FindNewDetectedArea(
	const cv::Mat& frame,
	const std::string& camera_id, cv::Mat& draw)
{
	std::vector<ANSCENTER::Object> output;

	// Reset detection state
	_isRealFireFrame = false;
	_realFireCheck = 0;

	// Update priority region
	UpdatePriorityRegion(frame);
	_detectedArea = getRegionByPriority(_currentPriority);

#ifdef FNS_DEBUG
	cv::rectangle(draw, _detectedArea, cv::Scalar(0, 0, 255), 4); // Red
#endif

	if (!IsDetectedAreaValid(_detectedArea)) {
		ResetDetectedArea();
		return output;
	}

	// Run detection on new area
	cv::Mat detectedROI = frame(_detectedArea);
	std::vector<ANSCENTER::Object> detectedObjects;
	_detector->RunInference(detectedROI, camera_id.c_str(), detectedObjects);

	if (detectedObjects.empty()) {
		ResetDetectedArea();
		return output;
	}

	// Find best detection and refine area
	float maxScore = 0.0f;
	ANSCENTER::Object* bestDetection = nullptr;

	for (auto& detectedObj : detectedObjects) {
		detectedObj.box.x += _detectedArea.x;
		detectedObj.box.y += _detectedArea.y;
		detectedObj.cameraId = camera_id;

		if (detectedObj.confidence > DETECTION_CONFIDENCE_THRESHOLD &&
			IsFireOrSmoke(detectedObj.classId, detectedObj.confidence) &&
			detectedObj.confidence > maxScore) {

			maxScore = detectedObj.confidence;
			bestDetection = &detectedObj;
		}
	}

	if (bestDetection != nullptr) {
		RefineDetectedArea(frame, *bestDetection);
		output.push_back(*bestDetection);
		UpdatePositiveDetection();
	}
	else {
		ResetDetectedArea();
	}

	return output;
}


std::vector<ANSCENTER::Object> ANSCustomFS::FindMovementObjects(const cv::Mat& frame, const std::string& camera_id, cv::Mat& draw)
{
	if (_motionSpecificity < 1) {
		// 1. Extract the detected area
		cv::Mat activeROI = frame(_detectedArea);

		// 2. Detect movement in the detected area
		std::vector<ANSCENTER::Object> movementObjects;
		_motiondetector->RunInference(activeROI, camera_id.c_str(), movementObjects);
		std::vector<ANSCENTER::Object> fireNSmokeRects;
		float movementarea_threshold = 0;
		int totalMovementObjects = static_cast<int>(movementObjects.size());

		if ((totalMovementObjects > 0) && (totalMovementObjects <= 10)) {
			for (const auto& rect : movementObjects) {
				ANSCENTER::Object mBbj;
				mBbj.box = rect.box;
				mBbj.box.x += _detectedArea.x;
				mBbj.box.y += _detectedArea.y;
				movementarea_threshold = calculateIoU(_detectedArea, mBbj.box);
				if (movementarea_threshold < 0.5) {
					fireNSmokeRects.push_back(mBbj);
#ifdef FNS_DEBUG
					cv::rectangle(draw, mBbj.box, cv::Scalar(123, 122, 34), 2);
#endif
				}
			}
		}
		activeROI.release();
		return fireNSmokeRects;
	}
	else return std::vector<ANSCENTER::Object>{};
}


std::vector<ANSCENTER::Object> ANSCustomFS::FindExcludedObjects(
	const cv::Mat& frame,
	const std::string& camera_id, cv::Mat& draw)
{
	// Final check for filters
	// Get activeROI with padding to include surrounding area
	cv::Rect paddedROI = _detectedArea;
	paddedROI.x -= 100;
	paddedROI.y -= 100;
	paddedROI.width += 200;
	paddedROI.height += 200;
	// Ensure paddedROI is within frame bounds
	paddedROI.x = std::max(0, paddedROI.x);
	paddedROI.y = std::max(0, paddedROI.y);
	paddedROI.width = std::min(frame.cols - paddedROI.x, paddedROI.width);
	paddedROI.height = std::min(frame.rows - paddedROI.y, paddedROI.height);
	// Run filter inference on padded ROI
	cv::Mat paddedFrame = frame(paddedROI);
	std::vector<ANSCENTER::Object> filteredObjects;
	_filter->RunInference(paddedFrame, camera_id.c_str(), filteredObjects);

	if (!filteredObjects.empty()) {
		std::vector<ANSCENTER::Object> excludedObjects;
		for (const auto& filteredObj : filteredObjects) {
			if (EXCLUDED_FILTER_CLASSES.find(filteredObj.classId) == EXCLUDED_FILTER_CLASSES.end())
			{
				ANSCENTER::Object exOBbj;
				exOBbj.box.x = paddedROI.x + filteredObj.box.x;
				exOBbj.box.y = paddedROI.y + filteredObj.box.y;
				exOBbj.box.width = filteredObj.box.width;
				exOBbj.box.height = filteredObj.box.height;
				excludedObjects.push_back(exOBbj);
#ifdef FNS_DEBUG
				cv::rectangle(draw, exOBbj.box, cv::Scalar(23, 25, 0), 2);
				cv::putText(draw, filteredObj.className, cv::Point(exOBbj.box.x, exOBbj.box.y - 10), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 2);
#endif
			}
		}
	}
	paddedFrame.release();
	return filteredObjects;
}

ANSCustomFS::ANSCustomFS()
	: EXCLUDED_FILTER_CLASSES({ 13, 59, 60, 68, 69, 70 })
{
	_smokeDetetectionThreshold = 0;
}
bool ANSCustomFS::OptimizeModel(bool fp16)
{
	if (!_detector || !_filter) return false;
	int detectorResult = _detector->Optimize(fp16);
	int filterResult = _filter->Optimize(fp16);
	if ((detectorResult != 1) || (filterResult != 1)) return false;
	return true;
}
bool ANSCustomFS::ConfigureParameters(CustomParams& param) {
	param.ROI_Config.clear();
	// We do need to have exclusive ROIs for the model
	CustomROIConfig exclusiveROI;
	if (this->_params.ROI_Config.empty()) {
		exclusiveROI.Name = "ExclusiveROIs";
		exclusiveROI.Polygon = false;
		exclusiveROI.Rectangle = true;
		exclusiveROI.Line = false;
		exclusiveROI.MinItems = 0;
		exclusiveROI.MaxItems = 20;
		exclusiveROI.ROIMatch = "All Corners";
		param.ROI_Config.push_back(exclusiveROI);
	}
	else {
		for (auto& roi : this->_params.ROI_Config) {
			param.ROI_Config.push_back(roi);
		}
	}

	param.ROI_Options.clear();
	param.Parameters.clear();
	CustomParameter param1;
	param1.Name = "SmokeScore";
	param1.DataType = "float";
	param1.NoOfDecimals = 2;
	param1.MaxValue = 1;
	param1.MinValue = 0;
	param1.StartValue = std::to_string(SMOKE_CONFIDENCE_THRESHOLD);
	param1.ListItems.clear();
	param1.DefaultValue = std::to_string(SMOKE_CONFIDENCE_THRESHOLD);
	param.Parameters.push_back(param1);

	CustomParameter param2;
	param2.Name = "Sensitivity";
	param2.DataType = "float";
	param2.NoOfDecimals = 2;
	param2.MaxValue = 1;
	param2.MinValue = 0;
	param2.StartValue = std::to_string(MOTION_SENSITIVITY);
	param2.ListItems.clear();
	param2.DefaultValue = std::to_string(MOTION_SENSITIVITY);
	param.Parameters.push_back(param2);
	param.ROI_Values.clear();
	for (auto& roi : this->_params.ROI_Values) {
		CustomROIValue roiValue;
		roiValue.Name = roi.Name;
		roiValue.ROIPoints = roi.ROIPoints;
		roiValue.ROIMatch = roi.ROIMatch;
		roiValue.Option = roi.Option;
		param.ROI_Values.push_back(roiValue);
	}
	return true;
}

std::vector<CustomObject> ANSCustomFS::RunInference(const cv::Mat& input)
{
	return RunInference(input, "CustomCam");
}
bool ANSCustomFS::Destroy()
{
	try {
		_detector.reset();
		_filter.reset();
		_motiondetector.reset();
		return true;
	}
	catch (...) {
		return false;
	}
}
bool ANSCustomFS::Initialize(const std::string& modelDirectory, float detectionScoreThreshold, std::string& labelMap)
{
	try {
		_modelDirectory = modelDirectory;
		_detectionScoreThreshold = detectionScoreThreshold;

		// Create model instances using factory pattern (ABI-safe)
		_detector = ANSLIBPtr(ANSCENTER::ANSLIB::Create(), &ANSCENTER::ANSLIB::Destroy);
		_filter = ANSLIBPtr(ANSCENTER::ANSLIB::Create(), &ANSCENTER::ANSLIB::Destroy);
		_motiondetector = ANSLIBPtr(ANSCENTER::ANSLIB::Create(), &ANSCENTER::ANSLIB::Destroy);


		_detectorModelType = 4;        // TENSORRT
		_detectorDetectionType = 1;    // DETECTION
		_filterModelType = 4;          // TENSORRT
		_filterDetectionType = 1;      // DETECTION
		_motionModelType = 19;          // Motion detection (generic CPU)
		_motionDetectionType = 1;      // DETECTION

		// Check the hardware type
		engineType = _detector->GetEngineType();
		if (engineType == 1) {
			// NVIDIA GPU: Use TensorRT
			_detectorModelType = 4;        // TENSORRT
			_detectorDetectionType = 1;    // DETECTION
			_filterModelType = 4;          // TENSORRT
			_filterDetectionType = 1;      // DETECTION
			std::cout << "NVIDIA GPU detected. Using TensorRT" << std::endl;
		}
		else {
			// CPU/Other: Use OpenVINO
			_detectorModelType = 17;        // OPENVINO
			_detectorDetectionType = 1;     // DETECTION
			_filterModelType = 17;          //Yolo v12
			_filterDetectionType = 1;      // DETECTION
			std::cout << "CPU detected. Using OpenVINO and ONNX" << std::endl;

		}

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

		if (this->_detectionScoreThreshold < 0.25f) this->_detectionScoreThreshold = 0.25f;

		// Initialize parameters
		_params.ROI_Config.clear();
		_params.ROI_Options.clear();
		_params.Parameters.clear();
		_params.ROI_Values.clear();

		_smokeDetetectionThreshold = SMOKE_CONFIDENCE_THRESHOLD;

#ifdef FNS_DEBUG
		this->_loadEngineOnCreate = true;
#endif
		int loadEngineOnCreation = _loadEngineOnCreate ? 1 : 0;
		int autoEngineDetection = 1;
		std::string licenseKey = "";
		std::string motionDetectorLabels;

		// Load detector model (fire/smoke detector)
		float detScoreThreshold = _detectionScoreThreshold;
		float detConfThreshold = 0.5f;
		float detNMSThreshold = 0.5f;
		int detResult = _detector->LoadModelFromFolder(
			licenseKey.c_str(),
			"detector", "detector.names",
			detScoreThreshold, detConfThreshold, detNMSThreshold,
			autoEngineDetection,
			_detectorModelType, _detectorDetectionType,
			loadEngineOnCreation,
			modelDirectory.c_str(),
			labelMap);
		if (detResult != 1) {
			std::cerr << "ANSCustomFS::Initialize: Failed to load detector model." << std::endl;
			return false;
		}

		// Load filter model (COCO general object detector for false positive filtering)
		float filterScoreThreshold = 0.25f;
		float filterConfThreshold = 0.5f;
		float filterNMSThreshold = 0.5f;
		int filterResult = _filter->LoadModelFromFolder(
			licenseKey.c_str(),
			"filter", "filter.names",
			filterScoreThreshold, filterConfThreshold, filterNMSThreshold,
			autoEngineDetection,
			_filterModelType, _filterDetectionType,
			loadEngineOnCreation,
			modelDirectory.c_str(),
			_filterLabels);
		if (filterResult != 1) {
			std::cerr << "ANSCustomFS::Initialize: Failed to load filter model." << std::endl;
			return false;
		}

		// Load motion detector model (background subtraction / frame differencing)
		float motionScoreThreshold = MOTION_SENSITIVITY;
		float motionConfThreshold = 0.5f;
		float motionNMSThreshold = 0.5f;
		int motionResult = _motiondetector->LoadModelFromFolder(
			licenseKey.c_str(),
			"motion", "motion.names",
			motionScoreThreshold, motionConfThreshold, motionNMSThreshold,
			autoEngineDetection,
			_motionModelType, _motionDetectionType,
			loadEngineOnCreation,
			"",  // Empty model directory - motion detector is algorithmic
			motionDetectorLabels);
		if (motionResult != 1) {
			std::cerr << "ANSCustomFS::Initialize: Failed to load motion detector model." << std::endl;
			return false;
		}

		return true;
	}
	catch (const std::exception& e) {
		std::cerr << "ANSCustomFS::Initialize: Exception: " << e.what() << std::endl;
		return false;
	}
	catch (...) {
		std::cerr << "ANSCustomFS::Initialize: Unknown exception." << std::endl;
		return false;
	}
}
ANSCustomFS::~ANSCustomFS()
{
	Destroy();
}
cv::Rect ANSCustomFS::GenerateMinimumSquareBoundingBox(const std::vector<ANSCENTER::Object>& detectedObjects, int minSize) {
	try {
		int adMinSize = minSize - 20;
		if (adMinSize < 0) adMinSize = 0;
		if (detectedObjects.empty()) return cv::Rect(0, 0, minSize, minSize);

		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;

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

		int width = maxX - minX;
		int height = maxY - minY;
		int squareSize = std::max({ width, height, adMinSize });
		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);
	}
	catch (std::exception& e) {
		return cv::Rect(0, 0, minSize, minSize);
	}
}
bool ANSCustomFS::detectStaticFire(std::deque<cv::Mat>& frameQueue) {
	return false;
	if (frameQueue.size() < 10) return false;
	double totalFlow = 0.0;
	int count = 0;

	for (size_t i = 2; i < frameQueue.size(); i += 3) {
		cv::Mat prevGray, currGray;
		cv::resize(frameQueue[i - 2], prevGray, cv::Size(), 0.5, 0.5);
		cv::resize(frameQueue[i], currGray, cv::Size(), 0.5, 0.5);
		cv::cvtColor(prevGray, prevGray, cv::COLOR_BGR2GRAY);
		cv::cvtColor(currGray, currGray, cv::COLOR_BGR2GRAY);

		std::vector<cv::Point2f> prevCorners, currCorners;
		cv::goodFeaturesToTrack(prevGray, prevCorners, 100, 0.01, 10);

		if (prevCorners.empty()) continue;

		std::vector<uchar> status;
		std::vector<float> err;
		cv::calcOpticalFlowPyrLK(prevGray, currGray, prevCorners, currCorners, status, err);

		double avgMotion = 0.0;
		int validPoints = 0;

		for (size_t j = 0; j < prevCorners.size(); j++) {
			if (status[j]) {
				double dx = currCorners[j].x - prevCorners[j].x;
				double dy = currCorners[j].y - prevCorners[j].y;
				avgMotion += std::sqrt(dx * dx + dy * dy);
				validPoints++;
			}
		}

		if (validPoints > 0) avgMotion /= validPoints;
		totalFlow += avgMotion;
		count++;
	}

	double avgMotion = (count > 0) ? totalFlow / count : 0;

	if (avgMotion < 0.5) {
		return true;
	}

	return false;
}
bool ANSCustomFS::detectScreenFlicker(const std::vector<cv::Mat>& frames) {
	if (frames.size() < 10) return false;

	std::vector<double> intensities;
	for (const auto& frame : frames) {
		cv::Mat gray;
		cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
		intensities.push_back(cv::mean(gray)[0]);
	}

	cv::Mat fftInput(static_cast<int>(intensities.size()), 1, CV_64F, intensities.data());
	cv::Mat fftOutput;
	cv::dft(fftInput, fftOutput, cv::DFT_REAL_OUTPUT);

	for (int i = 1; i < fftOutput.rows / 2; i++) {
		double freq = std::abs(fftOutput.at<double>(i, 0));
		if (freq > 50 && freq < 70) {
			return true;
		}
	}
	return false;
}
bool ANSCustomFS::detectReflection(const cv::Mat& frame) {
	cv::Mat gray, edges;
	cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
	cv::Canny(gray, edges, 50, 150);

	int edgeCount = cv::countNonZero(edges);
	return edgeCount > 5000;
}
bool ANSCustomFS::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 ANSCustomFS::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 ANSCustomFS::DetectFireNSmokeColourInFrame(const cv::Mat& frame, const cv::Rect& bBox,
	const std::vector<Range>& ranges, float area_threshold)
{
	if (frame.empty()) {
		return false;
	}

	if (ranges.empty()) {
		return false;
	}

	if (area_threshold < 0.0f || area_threshold > 1.0f) {
		return false;
	}

	try {
		constexpr int padding = 20;
		const int x1 = std::max(bBox.x - padding, 0);
		const int y1 = std::max(bBox.y - padding, 0);
		const int x2 = std::min(bBox.x + bBox.width + padding, frame.cols);
		const int y2 = std::min(bBox.y + bBox.height + padding, frame.rows);

		const int roiWidth = x2 - x1;
		const int roiHeight = y2 - y1;

		if (roiWidth <= 0 || roiHeight <= 0) {
			return false;
		}

		cv::Mat roiFrame = frame(cv::Rect(x1, y1, roiWidth, roiHeight));

		cv::Mat blur;
		cv::GaussianBlur(roiFrame, blur, cv::Size(21, 21), 0);

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

		cv::Mat combined_mask = cv::Mat::zeros(img_hsv.size(), CV_8U);
		cv::Mat temp_mask;

		for (const Range& range : ranges) {
			cv::inRange(img_hsv, range.first, range.second, temp_mask);
			combined_mask |= temp_mask;
		}

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

		const int matched_area = cv::countNonZero(combined_mask);
		const int total_area = roiWidth * roiHeight;
		const float matched_ratio = static_cast<float>(matched_area) / static_cast<float>(total_area);

		return matched_ratio > area_threshold;

	}
	catch (const cv::Exception& e) {
		return false;
	}
}
bool ANSCustomFS::IsFireDetected(const cv::Mat image, const cv::Rect bBox) {
	bool isFireColour = this->DetectFireNSmokeColourInFrame(image, bBox, this->_fire_colour, _hsvThreshold);
	return isFireColour;
}
bool ANSCustomFS::IsSmokeDetected(const cv::Mat image, const cv::Rect bBox) {
	bool isSmokeColour = this->DetectFireNSmokeColourInFrame(image, bBox, this->_smoke_colour, 0.9);
	return isSmokeColour;
}
float ANSCustomFS::calculateIoU(const cv::Rect& box1, const cv::Rect& box2) {
	cv::Rect intersection = box1 & box2;
	int intersectionArea = intersection.area();
	if (intersectionArea <= 0) return 0.0f;

	int unionArea = box1.area() + box2.area() - intersectionArea;
	if (unionArea <= 0) return 0.0f;
	return static_cast<float>(intersectionArea) / unionArea;
}
bool ANSCustomFS::IsOverlapping(const ANSCENTER::Object& obj, const std::vector<ANSCENTER::Object>& objectList, float iouThreshold)
{
	if (objectList.empty()) return false;
	for (const auto& otherObj : objectList)
	{
		float iou = calculateIoU(obj.box, otherObj.box);
		if (iou > iouThreshold)
		{
			return true;
		}
	}
	return false;
}
bool ANSCustomFS::IsOverlapping(const ANSCENTER::Object& obj, const std::vector<cv::Rect>& objectList, float iouThreshold) {
	if (objectList.empty()) return false;
	for (const auto& otherObj : objectList)
	{
		float iou = calculateIoU(obj.box, otherObj);
		if (iou > iouThreshold)
		{
			return true;
		}
	}
	return false;
}
bool ANSCustomFS::IsROIOverlapping(const cv::Rect& obj, const std::vector<cv::Rect>& objectList, float iouThreshold) {
	if (objectList.empty()) return false;

	for (const auto& otherObj : objectList)
	{
		float iou = calculateIoU(obj, otherObj);
		if (iou > iouThreshold)
		{
			return true;
		}
	}
	return false;
}

// Functions for screen size division
double ANSCustomFS::calculateDistanceToCenter(const cv::Point& center, const cv::Rect& rect) {
	cv::Point rectCenter(rect.x + rect.width / 2, rect.y + rect.height / 2);
	return std::sqrt(std::pow(rectCenter.x - center.x, 2) + std::pow(rectCenter.y - center.y, 2));
}
std::vector<ANSCustomFS::ImageSection> ANSCustomFS::divideImage(const cv::Mat& image) {
	if (image.empty()) {
		std::cerr << "Error: Empty image!" << std::endl;
		return cachedSections;
	}
	cv::Size currentSize(image.cols, image.rows);

	if (currentSize == previousImageSize) {
		return cachedSections;
	}

	previousImageSize = currentSize;
	cachedSections.clear();

	int width = image.cols;
	int height = image.rows;
	int maxDimension = std::max(width, height);
	int numSections = 10;
	if (maxDimension <= 2560)numSections = 8;
	if (maxDimension <= 1920)numSections = 6;
	if (maxDimension <= 1280)numSections = 4;
	if (maxDimension <= 960)numSections = 2;
	if (maxDimension <= 480)numSections = 1;

	int gridRows = static_cast<int>(std::sqrt(numSections));
	int gridCols = (numSections + gridRows - 1) / gridRows;

	int sectionWidth = width / gridCols;
	int sectionHeight = height / gridRows;

	cv::Point imageCenter(width / 2, height / 2);
	std::vector<std::pair<double, ImageSection>> distancePriorityList;

	for (int r = 0; r < gridRows; ++r) {
		for (int c = 0; c < gridCols; ++c) {
			int x = c * sectionWidth;
			int y = r * sectionHeight;
			int w = (c == gridCols - 1) ? width - x : sectionWidth;
			int h = (r == gridRows - 1) ? height - y : sectionHeight;

			ImageSection section(cv::Rect(x, y, w, h));
			double distance = calculateDistanceToCenter(imageCenter, section.region);
			distancePriorityList.emplace_back(distance, section);
		}
	}

	std::sort(distancePriorityList.begin(), distancePriorityList.end(),
		[](const std::pair<double, ImageSection>& a, const std::pair<double, ImageSection>& b) {
			if (std::abs(a.first - b.first) > 1e-5) {
				return a.first < b.first;
			}
			return a.second.region.y == b.second.region.y
				? a.second.region.x < b.second.region.x
				: a.second.region.y < b.second.region.y;
		});

	int priority = 1;
	for (auto& entry : distancePriorityList) {
		entry.second.priority = priority++;
		cachedSections.push_back(entry.second);
	}

	return cachedSections;
}
int ANSCustomFS::getHighestPriorityRegion() {
	if (!cachedSections.empty()) {
		return cachedSections.front().priority;
	}
	return 0;
}
int ANSCustomFS::getLowestPriorityRegion() {
	if (!cachedSections.empty()) {
		return cachedSections.back().priority;
	}
	return 0;
}
cv::Rect ANSCustomFS::getRegionByPriority(int priority) {
	for (const auto& section : cachedSections) {
		if (section.priority == priority) {
			return section.region;
		}
	}
	return cv::Rect();
}

std::vector<CustomObject> ANSCustomFS::RunInference(const cv::Mat& input, const std::string& camera_id) {
	std::lock_guard<std::recursive_mutex> lock(_mutex);

	if (input.empty() || input.cols < MIN_IMAGE_SIZE || input.rows < MIN_IMAGE_SIZE) {
		return {};
	}

	if (!_detector) {
		return {};
	}

	try {
		GetModelParameters();

#ifdef FNS_DEBUG
		cv::Mat draw = input.clone();
#else
		cv::Mat draw;
#endif

		std::vector<ANSCENTER::Object> output;

		// A. Check if detected area is retained and valid
		if (_detectedArea.width > MIN_ROI_SIZE && _detectedArea.height > MIN_ROI_SIZE) {
#ifdef FNS_DEBUG
			cv::rectangle(draw, _detectedArea, cv::Scalar(0, 0, 255), 2);
			for (const auto& roi : _exclusiveROIs) {
				cv::rectangle(draw, roi, cv::Scalar(255, 0, 0), 2);
			}
#endif
			output = ProcessExistingDetectedArea(input, camera_id, draw);
		}
		else {
			// B. Find new detected area
			output = FindNewDetectedArea(input, camera_id, draw);
		}

#ifdef FNS_DEBUG
		DisplayDebugFrame(draw);
#endif

		std::vector<CustomObject> results = ConvertToCustomObjects(output);

		if (results.empty()) {
			UpdateNoDetectionCondition();
		}

		return results;

	}
	catch (const std::exception& e) {
		return {};
	}
}

// New helper function to process detected area
std::vector<ANSCENTER::Object> ANSCustomFS::ProcessExistingDetectedArea(
	const cv::Mat& frame,
	const std::string& camera_id,
	cv::Mat& draw)
{
	std::vector<ANSCENTER::Object> output;

	cv::Mat activeROI = frame(_detectedArea);

	// Detect movement and objects
	std::vector<ANSCENTER::Object> movementObjects = FindMovementObjects(frame, camera_id, draw);
	std::vector<ANSCENTER::Object> detectedObjects;
	_detector->RunInference(activeROI, camera_id.c_str(), detectedObjects);

	if (detectedObjects.empty()) {
		UpdateNoDetectionCondition();
		return output;
	}

	const bool skipMotionCheck = (_motionSpecificity < 0.0f) || (_motionSpecificity >= 1.0f);
	const bool validMovement = !movementObjects.empty() && movementObjects.size() < MAX_MOTION_TRACKING;

	for (auto& detectedObj : detectedObjects) {
		// Adjust coordinates to full frame
		detectedObj.box.x += _detectedArea.x;
		detectedObj.box.y += _detectedArea.y;
		detectedObj.cameraId = camera_id;

		// Skip if overlapping with exclusive ROIs
		if (IsROIOverlapping(detectedObj.box, _exclusiveROIs, INCLUSIVE_IOU_THRESHOLD)) {
			continue;
		}

		// Check confidence thresholds
		const bool isValidFire = (detectedObj.classId == 0) && (detectedObj.confidence >= _detectionScoreThreshold);
		const bool isValidSmoke = (detectedObj.classId == 2) && (detectedObj.confidence >= _smokeDetetectionThreshold);

		if (!isValidFire && !isValidSmoke) {
			UpdateNoDetectionCondition();
			continue;
		}

		// Check area overlap
		const float area_threshold = calculateIoU(_detectedArea, detectedObj.box);
		if (area_threshold >= MAX_AREA_OVERLAP) {
			UpdateNoDetectionCondition();
			continue;
		}

#ifdef FNS_DEBUG
		cv::Scalar color = (detectedObj.classId == 0) ? cv::Scalar(0, 255, 255) : cv::Scalar(255, 0, 255);
		cv::rectangle(draw, detectedObj.box, color, 2);
#endif

		// Check motion
		if (!skipMotionCheck && !validMovement) {
			UpdateNoDetectionCondition();
			continue;
		}

		if (!skipMotionCheck && !IsOverlapping(detectedObj, movementObjects, 0)) {
			UpdateNoDetectionCondition();
			continue;
		}

		// Process valid detection
		if (!ProcessValidDetection(frame, camera_id, draw, detectedObj, output)) {
			continue;
		}
	}

	return output;
}

bool ANSCustomFS::ProcessValidDetection(
	const cv::Mat& frame,
	const std::string& camera_id,
	cv::Mat& draw,
	ANSCENTER::Object& detectedObj,
	std::vector<ANSCENTER::Object>& output)
{
	if (_realFireCheck > FILTERFRAMES) {
		AddConfirmedDetection(detectedObj, output);
		return true;
	}

	std::vector<ANSCENTER::Object> excludedObjects = FindExcludedObjects(frame, camera_id, draw);

	if (excludedObjects.empty()) {
		AddConfirmedDetection(detectedObj, output);
		return true;
	}

	if (!IsOverlapping(detectedObj, excludedObjects, 0)) {
		AddConfirmedDetection(detectedObj, output);
		_realFireCheck++;
		return true;
	}

	_realFireCheck = std::max(0, _realFireCheck - 1);
	if (_realFireCheck <= 0) {
		_isRealFireFrame = false;
	}
	return false;
}

void ANSCustomFS::AddConfirmedDetection(ANSCENTER::Object& detectedObj, std::vector<ANSCENTER::Object>& output) {
	output.push_back(std::move(detectedObj));
	_isFireNSmokeDetected = true;
	_retainDetectedArea = 0;
	_isRealFireFrame = true;
}
