#include "ANSODHUB.h"
#include "Utility.h"
namespace ANSCENTER
{
	// ODHUBAPI
	bool ODHUBAPI::OptimizeModel(bool fp16, std::string& optimizedModelFolder) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		if (!ANSODBase::OptimizeModel(fp16, optimizedModelFolder)) {
			return false;
		}
		if (FileExist(_modelFilePath)) {
			optimizedModelFolder = GetParentFolder(_modelFilePath);
			this->_logger.LogDebug("ODHUBAPI::OptimizeModel", "This model is optimized. No need other optimization.", __FILE__, __LINE__);
			return true;
		}
		else {
			optimizedModelFolder = "";
			this->_logger.LogFatal("ODHUBAPI::OptimizeModel", "This model is not exist. Please check the model path again.", __FILE__, __LINE__);
			return false;
		}
	}
	bool ODHUBAPI::LoadModel(const std::string& modelZipFilePath, const std::string& modelZipPassword) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		try {
			bool result = ANSODBase::LoadModel(modelZipFilePath, modelZipPassword);
			if (!result) return false;
			// 0. Check weigth file
			std::string weightfile = CreateFilePath(_modelFolder, "train_last.weights");
			if (FileExist(weightfile)) {
				// This is the darknet Yolov4
				_modelFilePath = weightfile;
				_classFilePath = CreateFilePath(_modelFolder, "classes.names");
				_modelCfgPath = CreateFilePath(_modelFolder, "test.cfg");
				if (std::filesystem::exists(_modelCfgPath)) {
					this->_logger.LogDebug("ODHUBAPI::LoadModel.  Loading ODHUB weight", _modelCfgPath, __FILE__, __LINE__);
				}
				else {
					this->_logger.LogError("ODHUBAPI::LoadModel.  Model config file is not exist", _modelCfgPath, __FILE__, __LINE__);
					return false;
				}
			}
			else
			{
				this->_logger.LogError("ODHUBAPI::LoadModel.  Model file is not exist", _modelFilePath, __FILE__, __LINE__);
				return false;

			}
			std::ifstream isValidFileName(_classFilePath);
			if (!isValidFileName)
			{
				LoadClassesFromString();
			}
			else {
				LoadClassesFromFile();
			}
			//1. Load the model
			EngineType engineType = ANSLicenseHelper::CheckHardwareInformation();
			if (engineType == EngineType::NVIDIA_GPU) // NVIDIA CUDA
			{
				_useGPU = true;
				_net = new Detector();
				_net->Init(_modelCfgPath.c_str(), _modelFilePath.c_str(), 0, 1);
			}
			else {
				_useGPU = false;
				_netCPU = new CPUDetector();
				_netCPU->Init(_modelCfgPath.c_str(), _modelFilePath.c_str(), -1, 1);
			}
			_isInitialized = true;
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ODHUBAPI::LoadModel", e.what(), __FILE__, __LINE__);
			return false;
		}

	}
	bool ODHUBAPI::LoadModelFromFolder(std::string licenseKey, ModelConfig modelConfig, std::string modelName, std::string className, const std::string& modelFolder, std::string& labelMap) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		try {
			bool result = ANSODBase::LoadModelFromFolder(licenseKey, modelConfig, modelName, className, modelFolder, labelMap);
			if (!result) return false;
			std::string _modelName = modelName;
			if (_modelName.empty()) {
				_modelName = "train_last";
			}
			std::string modelFullName = _modelName + ".weights";
			_modelConfig = modelConfig;
			_modelConfig.detectionType = ANSCENTER::DetectionType::DETECTION;
			_modelConfig.inpHeight = 416;
			_modelConfig.inpWidth = 416;
			// 0. Check weigth file
			std::string weightfile = CreateFilePath(_modelFolder, modelFullName);
			if (FileExist(weightfile)) {
				// This is the darknet Yolov4
				_modelFilePath = weightfile;
				_classFilePath = CreateFilePath(_modelFolder, className);
				_modelCfgPath = CreateFilePath(_modelFolder, "test.cfg");
				if (std::filesystem::exists(_modelCfgPath)) {
					this->_logger.LogDebug("ODHUBAPI::LoadModel.  Loading ODHUB weight", _modelCfgPath, __FILE__, __LINE__);
				}
				else {
					this->_logger.LogError("ODHUBAPI::LoadModel.  Model config file is not exist", _modelCfgPath, __FILE__, __LINE__);
					return false;
				}
			}
			else
			{
				this->_logger.LogError("ODHUBAPI::LoadModel.  Model file is not exist", _modelFilePath, __FILE__, __LINE__);
				return false;

			}
			std::ifstream isValidFileName(_classFilePath);
			if (!isValidFileName)
			{
				LoadClassesFromString();
			}
			else {
				LoadClassesFromFile();
			}

			// 1. Load labelMap and engine
			labelMap.clear();
			if (!_classes.empty())
				labelMap = VectorToCommaSeparatedString(_classes);

			//2. Load the model
			EngineType engineType = ANSLicenseHelper::CheckHardwareInformation();
			if (engineType == EngineType::NVIDIA_GPU) // NVIDIA CUDA
			{
				_useGPU = true;
				_net = new Detector();
				_net->Init(_modelCfgPath.c_str(), _modelFilePath.c_str(), 0, 1);
			}
			else {
				_useGPU = false;
				_netCPU = new CPUDetector();
				_netCPU->Init(_modelCfgPath.c_str(), _modelFilePath.c_str(), -1, 1);
			}
			_isInitialized = true;
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ODHUBAPI::LoadModel", e.what(), __FILE__, __LINE__);
			return false;
		}
	}
	bool ODHUBAPI::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 {

			bool result = ANSODBase::Initialize(licenseKey, modelConfig, modelZipFilePath, modelZipPassword, labelMap);
			if (!result) return false;
			// Parsing for YOLO only here
			_modelConfig = modelConfig;
			_modelConfig.detectionType = ANSCENTER::DetectionType::DETECTION;
			_modelConfig.inpHeight = 416;
			_modelConfig.inpWidth = 416;
			// 0. Check weigth file
			std::string weightfile = CreateFilePath(_modelFolder, "train_last.weights");
			if (FileExist(weightfile)) {
				// This is the darknet Yolov4
				_modelFilePath = weightfile;
				_classFilePath = CreateFilePath(_modelFolder, "classes.names");
				_modelCfgPath = CreateFilePath(_modelFolder, "test.cfg");
				if (std::filesystem::exists(_modelCfgPath)) {
					this->_logger.LogDebug("ODHUBAPI::LoadModel.  Loading ODHUB weight", _modelCfgPath, __FILE__, __LINE__);
				}
				else {
					this->_logger.LogError("ODHUBAPI::LoadModel.  Model config file is not exist", _modelCfgPath, __FILE__, __LINE__);
					return false;
				}
			}
			else
			{
				this->_logger.LogError("ODHUBAPI::LoadModel.  Model file is not exist", _modelFilePath, __FILE__, __LINE__);
				return false;

			}
			std::ifstream isValidFileName(_classFilePath);
			if (!isValidFileName)
			{
				LoadClassesFromString();
			}
			else {
				LoadClassesFromFile();
			}

			// 1. Load labelMap and engine
			labelMap.clear();
			if (!_classes.empty())
				labelMap = VectorToCommaSeparatedString(_classes);

			//2. Load the model
			EngineType engineType = ANSLicenseHelper::CheckHardwareInformation();
			if (engineType == EngineType::NVIDIA_GPU) // NVIDIA CUDA
			{
				_useGPU = true;
				_net = new Detector();
				_net->Init(_modelCfgPath.c_str(), _modelFilePath.c_str(), 0, 1);
			}
			else {
				_useGPU = false;
				_netCPU = new CPUDetector();
				_netCPU->Init(_modelCfgPath.c_str(), _modelFilePath.c_str(), -1, 1);
			}
			_isInitialized = true;
			return true;

		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ODHUBAPI::Initialize", e.what(), __FILE__, __LINE__);
			return false;
		}
	}
	std::vector<Object> ODHUBAPI::RunInference(const cv::Mat& input) {
		return RunInference(input, "ODHUBCam");
	}
	std::vector<Object> ODHUBAPI::RunInference(const cv::Mat& input, const std::string& camera_id)
	{
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		std::vector<Object> result;

		try {
			// Early validation
			if (!_licenseValid) {
				_logger.LogError("ODHUBAPI::RunInference", "Invalid License",
					__FILE__, __LINE__);
				return result;
			}

			if (!_isInitialized) {
				_logger.LogError("ODHUBAPI::RunInference", "Model is not initialized",
					__FILE__, __LINE__);
				return result;
			}

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

			// Prepare image for inference
			cv::Mat processedImage;
			if (input.channels() == 1) {
				cv::cvtColor(input, processedImage, cv::COLOR_GRAY2BGR);
			}
			else {
				// Shallow copy is safe if detect() doesn't modify input
				// If it does, use: processedImage = input.clone();
				processedImage = input;
			}

			// Get detections (unified call)
			std::vector<bbox_t> boxes = _useGPU
				? _net->detect(processedImage, _modelConfig.detectionScoreThreshold, false)
				: _netCPU->detect(processedImage, _modelConfig.detectionScoreThreshold, false);

			// Pre-allocate result vector
			result.reserve(boxes.size());

			// Pre-calculate image bounds
			const int maxWidth = input.cols;
			const int maxHeight = input.rows;
			const cv::Rect imageBounds(0, 0, maxWidth, maxHeight);
			const int classNameSize = static_cast<int>(_classes.size());

			// Convert detections to Objects
			for (const auto& box : boxes) {
				Object obj;

				// Set class info
				obj.classId = box.obj_id;
				if (!_classes.empty()) {
					obj.className = (box.obj_id < classNameSize)
						? _classes[box.obj_id]
						: _classes[classNameSize - 1];
				}
				else {
					obj.className = "Unknown";
				}

				// Set detection info
				obj.confidence = box.prob;
				obj.cameraId = camera_id;

				// Clamp bounding box to image bounds
				obj.box = cv::Rect(
					static_cast<int>(box.x),
					static_cast<int>(box.y),
					static_cast<int>(box.w),
					static_cast<int>(box.h)
				) & imageBounds;  // Single intersection operation

				// Skip invalid boxes (completely outside image)
				if (obj.box.area() > 0) {
					result.push_back(std::move(obj));
				}
			}

			if (_trackerEnabled) {
				result = ApplyTracking(result, camera_id);
				if (_stabilizationEnabled) result = StabilizeDetections(result, camera_id);
			}
			return result;

		}
		catch (const std::exception& e) {
			_logger.LogFatal("ODHUBAPI::RunInference", e.what(), __FILE__, __LINE__);
			return {};  // Return empty vector
		}
	}
	ODHUBAPI::~ODHUBAPI() {
		try {
			this->_logger.LogDebug("ODHUBAPI::~ODHUBAPI()", "Release ODHUBAPI ", __FILE__, __LINE__);

		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ODHUBAPI::~ODHUBAPI()", e.what(), __FILE__, __LINE__);
		}
	}
	bool ODHUBAPI::Destroy() {
		try {
			this->_logger.LogDebug("ODHUBAPI::Destroy()", "Release ODHUBAPI ", __FILE__, __LINE__);
			if (_net != nullptr)delete _net;
			if (_netCPU != nullptr)delete _netCPU;
			_net = nullptr;
			_netCPU = nullptr;
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ODHUBAPI::Destroy()", e.what(), __FILE__, __LINE__);
			return false;
		}
	}
}

//std::vector<Object> ODHUBAPI::RunInference(const cv::Mat& input, const std::string& camera_id) {
	//	std::lock_guard<std::recursive_mutex> lock(_mutex);
	//	std::vector<Object> result;
	//	try {
	//		if (!_licenseValid) {
	//			this->_logger.LogError("ODHUBAPI::RunInference", "Invalid License", __FILE__, __LINE__);
	//			return result;
	//		}
	//		if (!_isInitialized) {
	//			this->_logger.LogError("ODHUBAPI::RunInference", "Model is not initialized", __FILE__, __LINE__);
	//			return result;
	//		}
	//		if (input.empty()) return result;
	//		if ((input.cols < 10) || (input.rows < 10)) return result;
	//		int maxWidth = input.cols;
	//		int maxHeight = input.rows;
	//		cv::Mat processedImage;
	//		if (input.channels() == 1) {
	//			cv::cvtColor(input, processedImage, cv::COLOR_GRAY2BGR);
	//		}
	//		else {
	//			processedImage = input;
	//		}
	//		int classNameSize = _classes.size();
	//		// Run inference
	//		if (_useGPU) // Use GPU
	//		{
	//			std::vector<bbox_t> boxes = _net->detect(processedImage, _modelConfig.detectionScoreThreshold, false);
	//			for (auto& box : boxes) {
	//				Object obj;
	//				// Check if the box is out of the image
	//				if (box.x < 0) box.x = 0;
	//				if (box.y < 0) box.y = 0;
	//				if (box.x + box.w > maxWidth) box.w = maxWidth - box.x - 5;
	//				if (box.y + box.h > maxHeight) box.h = maxHeight - box.y - 5;
	//				obj.classId = box.obj_id;
	//				
	//				if (!_classes.empty()) {
	//					if (box.obj_id < classNameSize) {
	//						obj.className = _classes[box.obj_id];
	//					}
	//					else {
	//						obj.className = _classes[classNameSize - 1]; // Use last valid class name if out of range
	//					}
	//				}
	//				else {
	//					obj.className = "Unknown"; // Fallback if _classes is empty
	//				}
	//				obj.confidence = box.prob;
	//				obj.box.x = box.x;
	//				obj.box.y = box.y;
	//				obj.box.width = box.w;
	//				obj.box.height = box.h;
	//				obj.box.x = std::max(0, obj.box.x);
	//				obj.box.y = std::max(0, obj.box.y);
	//				obj.box.width = std::min(maxWidth - obj.box.x, obj.box.width);
	//				obj.box.height = std::min(maxHeight - obj.box.y, obj.box.height);
//				obj.cameraId = camera_id;
	//				result.push_back(obj);
	//			}
	//			//EnqueueDetection(result, camera_id);
	//			return result;
	//		}
	//		else // Use CPU
	//		{
	//			std::vector<bbox_t> boxes = _netCPU->detect(processedImage, _modelConfig.detectionScoreThreshold, false);
	//			for (auto& box : boxes) {
	//				Object obj;
	//				// Check if the box is out of the image
	//				if (box.x < 0) box.x = 0;
	//				if (box.y < 0) box.y = 0;
	//				if (box.x + box.w > maxWidth) box.w = maxWidth - box.x - 5;
	//				if (box.y + box.h > maxHeight) box.h = maxHeight - box.y - 5;
	//				obj.classId = box.obj_id;
	//				if (!_classes.empty()) {
	//					if (box.obj_id < classNameSize) {
	//						obj.className = _classes[box.obj_id];
	//					}
	//					else {
	//						obj.className = _classes[classNameSize - 1]; // Use last valid class name if out of range
	//					}
	//				}
	//				else {
	//					obj.className = "Unknown"; // Fallback if _classes is empty
	//				}
	//				obj.confidence = box.prob;
	//				obj.box.x = box.x;
	//				obj.box.y = box.y;
	//				obj.box.width = box.w;
	//				obj.box.height = box.h;
	//				obj.box.x = std::max(0, obj.box.x);
	//				obj.box.y = std::max(0, obj.box.y);
	//				obj.box.width = std::min(maxWidth - obj.box.x, obj.box.width);
	//				obj.box.height = std::min(maxHeight - obj.box.y, obj.box.height);
	//				obj.cameraId = camera_id;
	//				result.push_back(obj);
	//			}
	//			//EnqueueDetection(result, camera_id);
	//			return result;
	//		}
	//	}
	//	catch (std::exception& e) {
	//		result.clear();
	//		this->_logger.LogFatal("ODHUBAPI::RunInference", e.what(), __FILE__, __LINE__);
	//		return result;
	//	}
	//}