#include "ANSPOSE.h"
#include <pipelines/metadata.h>
#include <models/input_data.h>
#include <models/results.h>
namespace ANSCENTER {
	bool ANSPOSE::OptimizeModel(bool fp16, std::string& optimizedModelFolder) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		if (!ANSODBase::OptimizeModel(fp16, optimizedModelFolder)) {
			return false;
		}
		if (FileExist(_modelFilePath)) {
			std::string modelName = GetFileNameWithoutExtension(_modelFilePath);
			std::string binaryModelName = modelName + ".bin";
			std::string modelFolder = GetParentFolder(_modelFilePath);
			std::string optimizedModelPath = CreateFilePath(modelFolder, binaryModelName);
			if (FileExist(optimizedModelPath)) {
				this->_logger.LogDebug("ANSPOSE::OptimizeModel", "This model is optimized. No need other optimization.", __FILE__, __LINE__);
				optimizedModelFolder = modelFolder;
				return true;
			}
			else {
				this->_logger.LogFatal("ANSPOSE::OptimizeModel", "This model can not be optimized.", __FILE__, __LINE__);
				optimizedModelFolder = modelFolder;
				return false;
			}
		}
		else {
			this->_logger.LogFatal("ANSPOSE::OptimizeModel", "This model is not exist. Please check the model path again.", __FILE__, __LINE__);
			optimizedModelFolder = "";
			return false;
		}
	}

	bool ANSPOSE::LoadModel(const std::string& modelZipFilePath, const std::string& modelZipPassword) {
		std::lock_guard<std::recursive_mutex> lock(_mutex);
		ModelLoadingGuard mlg(_modelLoading);
		try {
			bool result = ANSODBase::LoadModel(modelZipFilePath, modelZipPassword);
			if (!result) return false;
			_device = GetOpenVINODevice(_core);
			// 0. Check if the configuration file exist
			if (FileExist(_modelConfigFile)) {
				ModelType modelType;
				std::vector<int> inputShape;
				_classes = ANSUtilityHelper::GetConfigFileContent(_modelConfigFile, modelType, inputShape);
				if (inputShape.size() == 2) {
					if (inputShape[0] > 0)_modelConfig.inpHeight = inputShape[0];
					if (inputShape[1] > 0)_modelConfig.inpWidth = inputShape[1];
				}
			}
			else {// This is old version of model zip file
				std::string xmlfile = CreateFilePath(_modelFolder, "ansposesetimation.xml");
				std::string binaryfile = CreateFilePath(_modelFolder, "ansposesetimation.bin");
				if (std::filesystem::exists(xmlfile)) {
					_modelFilePath = xmlfile;
					this->_logger.LogDebug("ANSPOSE::Initialize. Loading OpenVINO weight", _modelFilePath, __FILE__, __LINE__);
					if (FileExist(binaryfile)) {
						this->_logger.LogDebug("ANSPOSE::Initialize binary weight", binaryfile, __FILE__, __LINE__);
					}
					else {
						this->_logger.LogError("ANSPOSE::Initialize. Model binary file does not exist", binaryfile, __FILE__, __LINE__);
						return false;
					}
				}
				else {
					this->_logger.LogError("ANSPOSE::Initialize. Model file does exist", _modelFilePath, __FILE__, __LINE__);
					return false;
				}
			}
			_isInitialized = true;
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSPOSE::LoadModel", e.what(), __FILE__, __LINE__);
			return false;
		}
	}
	bool ANSPOSE::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);
		ModelLoadingGuard mlg(_modelLoading);
		try {
			bool result = ANSODBase::LoadModelFromFolder(licenseKey, modelConfig, modelName, className, modelFolder, labelMap);
			if (!result) return false;
			std::string _modelName = modelName;
			if (_modelName.empty()) {
				_modelName = "ansposesetimation";
			}
			std::string xmlFileName = _modelName + ".xml";
			std::string binFileName = _modelName + ".bin";
			_modelConfig = modelConfig;
			_modelConfig.detectionType = ANSCENTER::DetectionType::KEYPOINT;
			_modelConfig.modelType = ModelType::POSE;
			_modelConfig.inpHeight = 640;
			_modelConfig.inpWidth = 640;
			if (_modelConfig.modelMNSThreshold < 0.2)
				_modelConfig.modelMNSThreshold = 0.5;
			if (_modelConfig.modelConfThreshold < 0.2)
				_modelConfig.modelConfThreshold = 0.5;
			_device = GetOpenVINODevice(_core);
			// 0. Check if the configuration file exist
			if (FileExist(_modelConfigFile)) {
				ModelType modelType;
				std::vector<int> inputShape;
				_classes = ANSUtilityHelper::GetConfigFileContent(_modelConfigFile, modelType, inputShape);
				if (inputShape.size() == 2) {
					if (inputShape[0] > 0)_modelConfig.inpHeight = inputShape[0];
					if (inputShape[1] > 0)_modelConfig.inpWidth = inputShape[1];
				}
			}
			else {// This is old version of model zip file
				std::string xmlfile = CreateFilePath(_modelFolder, xmlFileName);
				std::string binaryfile = CreateFilePath(_modelFolder, binFileName);
				if (std::filesystem::exists(xmlfile)) {
					_modelFilePath = xmlfile;
					this->_logger.LogDebug("ANSPOSE::Initialize. Loading OpenVINO weight", _modelFilePath, __FILE__, __LINE__);
					if (FileExist(binaryfile)) {
						this->_logger.LogDebug("ANSPOSE::Initialize binary weight", binaryfile, __FILE__, __LINE__);
					}
					else {
						this->_logger.LogError("ANSPOSE::Initialize. Model binary file does not exist", binaryfile, __FILE__, __LINE__);
						return false;
					}
				}
				else {
					this->_logger.LogError("ANSPOSE::Initialize. Model file does exist", _modelFilePath, __FILE__, __LINE__);
					return false;
				}
			}
			labelMap = "Person";
			_isInitialized = true;
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSPOSE::LoadModel", e.what(), __FILE__, __LINE__);
			return false;
		}
	}

	bool ANSPOSE::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);
		ModelLoadingGuard mlg(_modelLoading);
		try {
			std::string openVINOVersion = ov::get_openvino_version().buildNumber;
			this->_logger.LogDebug("ANSPOSE::Initialize. OpenVINO version", openVINOVersion, __FILE__, __LINE__);
			bool result = ANSODBase::Initialize(licenseKey, modelConfig, modelZipFilePath, modelZipPassword, labelMap);
			if (!result) return false;
			// Parsing for ABNORMAL only here
			_modelConfig = modelConfig;
			_modelConfig.detectionType = ANSCENTER::DetectionType::KEYPOINT;
			_modelConfig.modelType = ModelType::POSE;
			_modelConfig.inpHeight = 640;
			_modelConfig.inpWidth = 640;
			if (_modelConfig.modelMNSThreshold < 0.2)
				_modelConfig.modelMNSThreshold = 0.5;
			if (_modelConfig.modelConfThreshold < 0.2)
				_modelConfig.modelConfThreshold = 0.5;
			_device = GetOpenVINODevice(_core);
			// 0. Check if the configuration file exist
			if (FileExist(_modelConfigFile)) {
				ModelType modelType;
				std::vector<int> inputShape;
				_classes = ANSUtilityHelper::GetConfigFileContent(_modelConfigFile, modelType, inputShape);
				if (inputShape.size() == 2) {
					if (inputShape[0] > 0)_modelConfig.inpHeight = inputShape[0];
					if (inputShape[1] > 0)_modelConfig.inpWidth = inputShape[1];
				}
			}
			else {// This is old version of model zip file
				std::string xmlfile = CreateFilePath(_modelFolder, "ansposesetimation.xml");
				std::string binaryfile = CreateFilePath(_modelFolder, "ansposesetimation.bin");
				if (std::filesystem::exists(xmlfile)) {
					_modelFilePath = xmlfile;
					this->_logger.LogDebug("ANSPOSE::Initialize. Loading OpenVINO weight", _modelFilePath, __FILE__, __LINE__);
					if (FileExist(binaryfile)) {
						this->_logger.LogDebug("ANSPOSE::Initialize binary weight", binaryfile, __FILE__, __LINE__);
					}
					else {
						this->_logger.LogError("ANSPOSE::Initialize. Model binary file does not exist", binaryfile, __FILE__, __LINE__);
						return false;
					}
				}
				else {
					this->_logger.LogError("ANSPOSE::Initialize. Model file does exist", _modelFilePath, __FILE__, __LINE__);
					return false;
				}
			}
			labelMap = "Person";
			_isInitialized = true;
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSANOMALIB::Initialize", e.what(), __FILE__, __LINE__);
			labelMap = "Normal,Defect";
			return false;
		}
	}
	std::vector<Object> ANSPOSE::RunInference(const cv::Mat& input) {
		return RunInference(input, "CustomCam");
	}

	std::vector<Object> ANSPOSE::RunInference(const cv::Mat& input,
		const std::string& camera_id)
	{
		if (!PreInferenceCheck("ANSPOSE::RunInference")) return {};

		try {
			// Validation
			if (!_licenseValid) {
				_logger.LogError("ANSPOSE::RunInference", "Invalid License",
					__FILE__, __LINE__);
				return {};
			}

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

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

			// Convert to BGR if needed
			cv::Mat processedImage;
			if (input.channels() == 1) {
				cv::cvtColor(input, processedImage, cv::COLOR_GRAY2BGR);
			}
			else {
				processedImage = input;  // Shallow copy
			}

			const int width = processedImage.cols;
			const int height = processedImage.rows;

			// Initialize pipeline once
			if (!_poseEstimationPipeline) {
				double aspectRatio = width / static_cast<double>(height);

				auto model = std::make_unique<HPEOpenPose>(
					_modelFilePath, aspectRatio, 0, 0.5f, ""
				);

				try {
					_poseEstimationPipeline = new AsyncPipeline(std::move(model), ConfigFactory::getUserConfig(_device, 1, "", 4), _core);
				}
				catch (const std::exception& e) {
					// Fall back to CPU
					_logger.LogWarn("ANSPOSE::RunInference",
						"GPU init failed, using CPU: " + std::string(e.what()),
						__FILE__, __LINE__);

					model = std::make_unique<HPEOpenPose>(
						_modelFilePath, aspectRatio, 0, 0.5f, ""
					);
					_poseEstimationPipeline = new AsyncPipeline(std::move(model), ConfigFactory::getUserConfig("CPU", 0, "", 0), _core);
				}
			}

			if (!_poseEstimationPipeline) {
				_logger.LogError("ANSPOSE::RunInference", "Pipeline initialization failed",
					__FILE__, __LINE__);
				return {};
			}

			// Run inference
			auto startTime = std::chrono::steady_clock::now();
			int64_t frameNum = _poseEstimationPipeline->submitData(
				ImageInputData(processedImage),
				std::make_shared<ImageMetaData>(processedImage, startTime)
			);

			_poseEstimationPipeline->waitForTotalCompletion();
			std::unique_ptr<ResultBase> result = _poseEstimationPipeline->getResult();

			// Process results
			std::vector<Object> output;
			auto& poses = result->asRef<HumanPoseResult>().poses;
			output.reserve(poses.size());

			const cv::Rect imageBounds(0, 0, width, height);

			for (const auto& pose : poses) {
				if (pose.keypoints.empty()) {
					continue;
				}

				// Calculate bounding box from keypoints
				int x_min = width, y_min = height;
				int x_max = 0, y_max = 0;

				for (const auto& kp : pose.keypoints) {
					if (kp.x > 0) {
						x_min = std::min(x_min, static_cast<int>(kp.x));
						x_max = std::max(x_max, static_cast<int>(kp.x));
					}
					if (kp.y > 0) {
						y_min = std::min(y_min, static_cast<int>(kp.y));
						y_max = std::max(y_max, static_cast<int>(kp.y));
					}
				}

				// Validate bounding box
				if (x_max <= x_min || y_max <= y_min) {
					continue;
				}

				Object obj;
				obj.classId = 0;
				obj.className = "Person";
				obj.confidence = 1.0f;
				obj.cameraId = camera_id;

				// Set bounding box
				obj.box = cv::Rect(x_min, y_min, x_max - x_min, y_max - y_min);
				obj.box &= imageBounds;  // Clamp to image bounds

				// Extract mask ROI (CRITICAL: Clone to avoid dangling reference!)
				cv::Rect maskRect = obj.box & imageBounds;
				if (maskRect.area() > 0) {
					obj.mask = processedImage(maskRect).clone();  // Must clone!
				}

				// Store keypoints efficiently
				obj.kps.reserve(pose.keypoints.size() * 2);
				obj.polygon.reserve(pose.keypoints.size());

				for (const auto& kp : pose.keypoints) {
					obj.kps.push_back(kp.x);
					obj.kps.push_back(kp.y);
					obj.polygon.emplace_back(kp.x, kp.y);
				}

				// Build extraInfo string efficiently (if really needed)
				if (!pose.keypoints.empty()) {
					std::string keypointX, keypointY;
					keypointX.reserve(pose.keypoints.size() * 10);  // Estimate
					keypointY.reserve(pose.keypoints.size() * 10);

					for (size_t i = 0; i < pose.keypoints.size(); i++) {
						keypointX += std::to_string(pose.keypoints[i].x);
						keypointY += std::to_string(pose.keypoints[i].y);

						if (i < pose.keypoints.size() - 1) {
							keypointX += ";";
							keypointY += ";";
						}
					}

					obj.extraInfo = keypointX + "|" + keypointY;
				}

				output.push_back(std::move(obj));
			}

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

		}
		catch (const std::exception& e) {
			_logger.LogFatal("ANSPOSE::RunInference", e.what(), __FILE__, __LINE__);
			return {};
		}
	}

	ANSPOSE::~ANSPOSE() {
		if (_poseEstimationPipeline) {
			delete _poseEstimationPipeline;
			_poseEstimationPipeline = nullptr;
		}
		if (FolderExist(_modelFolder)) {
			if (!DeleteFolder(_modelFolder)) {
				this->_logger.LogError("ANSPOSE::~ANSPOSE", "Failed to release ANSPOSE Models", __FILE__, __LINE__);
			}
		}
	}
	bool ANSPOSE::Destroy() {
		if (_poseEstimationPipeline) {
			delete _poseEstimationPipeline;
			_poseEstimationPipeline = nullptr;
		}
		if (FolderExist(_modelFolder)) {
			if (!DeleteFolder(_modelFolder)) {
				this->_logger.LogError("ANSPOSE::Destroy", "Failed to release ANSPOSE Models", __FILE__, __LINE__);
			}
		}
		return true;
	}

}

// std::vector<Object> ANSPOSE::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("ANSPOSE::RunInference", "Invalid License", __FILE__, __LINE__);
 //         return output;
 //     }
 //     if (!_isInitialized) {
 //         this->_logger.LogError("ANSPOSE::RunInference", "Model is not initialized", __FILE__, __LINE__);
 //         return output;
 //     }
 //     try {
 //         if (input.empty()) return output;
 //         if ((input.cols < 10) || (input.rows < 10)) return output;
 //         // Convert grayscale to 3-channel BGR if needed
 //         cv::Mat processedImage;
 //         if (input.channels() == 1) {
 //             cv::cvtColor(input, processedImage, cv::COLOR_GRAY2BGR);
 //         }
 //         else {
 //             processedImage = input;
 //         }

 //         cv::Mat im = processedImage;
 //         int width = im.cols;
 //         int height = im.rows;
 //         if (!_poseEstimationPipeline) {
 //             // Load Model from Here
 //             std::unique_ptr<ModelBase> model;
 //             double aspectRatio = width / static_cast<double>(height);
 //             model.reset(new HPEOpenPose(_modelFilePath, aspectRatio, 0, static_cast<float>(0.5), ""));
 //             //ov::Core core;
 //             try {
 //                 _poseEstimationPipeline = new AsyncPipeline(std::move(model), ConfigFactory::getUserConfig(_device, 1, "", 4), _core);
		  //	}
		  //	catch (const std::exception& e) {
		  //		// Fall back to CPU
 //                 _poseEstimationPipeline = new AsyncPipeline(std::move(model), ConfigFactory::getUserConfig("CPU", 0, "", 0), _core);

 //             }
 //         }
 //         if (_poseEstimationPipeline) {
 //             auto startTime = std::chrono::steady_clock::now();
 //             int64_t frameNum = _poseEstimationPipeline->submitData(ImageInputData(im), std::make_shared<ImageMetaData>(im, startTime));
 //             _poseEstimationPipeline->waitForTotalCompletion();
 //             std::unique_ptr<ResultBase> result = _poseEstimationPipeline->getResult();

 //             /*    static const std::pair<int, int> keypointsOP[] = { 
 //                                                                    {1, 2},
 //                                                                    {1, 5},
 //                                                                    {2, 3},
 //                                                                    {3, 4},
 //                                                                    {5, 6},
 //                                                                    {6, 7},
 //                                                                    {1, 8},
 //                                                                    {8, 9},
 //                                                                    {9, 10},
 //                                                                    {1, 11},
 //                                                                    {11, 12},
 //                                                                    {12, 13},
 //                                                                    {1, 0},
 //                                                                    {0, 14},
 //                                                                    {14, 16},
 //                                                                    {0, 15},
 //                                                                    {15, 17}
 //                                                                   };*/
 //             const cv::Point2f absentKeypoint(-1.0f, -1.0f);
 //             for (auto& obj : result->asRef<HumanPoseResult>().poses) {
 //                 Object result;
 //                 float confidence = 1.0;
 //                 if (confidence > 0)
 //                 {
 //                     // Intepret keypoints 
 //                     int x_min = 10000;
 //                     int y_min = 10000;
 //                     int x_max = 0;
 //                     int y_max = 0;
 //                     std::stringstream keypointXss;
 //                     std::stringstream keypointYss;
		  //			std::vector<cv::Point2f> kps;
		  //			std::vector<float> keypoints;
 //                     if (obj.keypoints.size() > 0) {
 //                         for (size_t keypointIdx = 0; keypointIdx < obj.keypoints.size(); keypointIdx++) {
 //                             if (obj.keypoints[keypointIdx].x > 0) {
 //                                 if (x_min > obj.keypoints[keypointIdx].x) x_min = obj.keypoints[keypointIdx].x;
 //                                 if (x_max < obj.keypoints[keypointIdx].x) x_max = obj.keypoints[keypointIdx].x;
 //                             }
 //                             if (obj.keypoints[keypointIdx].y > 0) {
 //                                 if (y_min > obj.keypoints[keypointIdx].y) y_min = obj.keypoints[keypointIdx].y;
 //                                 if (y_max < obj.keypoints[keypointIdx].y) y_max = obj.keypoints[keypointIdx].y;
 //                             }
 //                             keypointXss << obj.keypoints[keypointIdx].x; // Add semicolon after each x value
 //                             keypointYss << obj.keypoints[keypointIdx].y; // Add semicolon after each y value
 //                             // Add semicolon after each value except the last one
 //                             if (keypointIdx < obj.keypoints.size() - 1) 
 //                             {
 //                                 keypointXss << ";";
 //                                 keypointYss << ";";
 //                             }
 //                             cv::Point2f kp;
		  //					kp.x = obj.keypoints[keypointIdx].x;
		  //					kp.y = obj.keypoints[keypointIdx].y;
		  //					kps.push_back(kp);
		  //					keypoints.push_back(kp.x);
		  //					keypoints.push_back(kp.y);
 //                         }

 //                         // Retrieve strings from string streams
 //                         std::string keypointXString = keypointXss.str();
 //                         std::string keypointYString = keypointYss.str();
 //                         std::string keypointString = keypointXString + "|" + keypointYString;

 //                         result.classId = 0; // human keypoint
 //                         result.className = "Person";
 //                         result.confidence = confidence;
 //                         result.box.x = x_min;
 //                         result.box.y = y_min;
 //                         result.box.width = x_max - x_min;
 //                         result.box.height = y_max - y_min;

		  //				result.box.x = std::max(0, result.box.x);
		  //				result.box.y = std::max(0, result.box.y);
		  //				result.box.width = std::min(width - result.box.x, result.box.width);
		  //				result.box.height = std::min(height - result.box.y, result.box.height);

 //                         cv::Rect maskImage(cv::Point(x_min, y_min), cv::Point(x_max, y_max));
 //                         result.mask = im(maskImage);
		  //				result.polygon = kps;
		  //				result.kps = keypoints;
 //                         result.extraInfo = keypointString;//Convert keypoint to st;
		  //				result.cameraId = camera_id;
 //                         output.push_back(result);
 //                     }
 //                 }
 //             }
 //             im.release();
 //         }
		  ////EnqueueDetection(output,camera_id);
 //         return output;
 //     }
 //     catch (std::exception& e) {
 //         this->_logger.LogFatal("ANSPOSE::RunInference", e.what(), __FILE__, __LINE__);
 //         return output;
 //     }
 // }