#include "ANSLPR_CPU.h"
#include "ANSYOLOV10OVOD.h"
#include "ANSOPENVINOOD.h"
#include "ANSTENSORRTOD.h"
#include "ANSYOLO12OD.h"

//#define FNS_DEBUG
namespace ANSCENTER {
    void ANSOVDetector::PreprocessImage(cv::Mat& frame) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
			if (frame.empty()) return;
			if ((frame.rows <= 5) || (frame.cols <= 5)) return;
            cv::resize(frame, resized_frame_, model_input_shape_, 0, 0, cv::INTER_AREA);
            factor_.x = static_cast<float>(frame.cols / model_input_shape_.width);
            factor_.y = static_cast<float>(frame.rows / model_input_shape_.height);
            float* input_data = reinterpret_cast<float*>(resized_frame_.data);
            input_tensor_ = ov::Tensor(compiled_model_.input().get_element_type(), compiled_model_.input().get_shape(), input_data);
            inference_request_.set_input_tensor(input_tensor_);
        }
        catch (const std::exception& e) {
            std::cout << "ANSOVDetector::PreprocessImage. " << e.what() << std::endl;
        }
        catch (...) {
            std::cout << "ANSOVDetector::PreprocessImage. " << "unknown exception" << std::endl;
        }
    }
    cv::Rect ANSOVDetector::GetBoundingBox(const cv::Rect& src) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            cv::Rect box = src;
            box.x = (box.x - 0.5 * box.width) * factor_.x;
            box.y = (box.y - 0.5 * box.height) * factor_.y;
            box.width *= factor_.x;
            box.height *= factor_.y;
            return box;
		}
		catch (const std::exception& e) {
			std::cout << "ANSOVDetector::GetBoundingBox. " << e.what() << std::endl;
			return src;
		}

    }
    std::vector <std::string> ANSOVDetector::LoadClassesFromFile() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        std::vector <std::string> _classes;
        try {
            std::ifstream inputFile(_classFilePath);
            if (inputFile.is_open())
            {
                _classes.clear();
                std::string classLine;
                while (std::getline(inputFile, classLine))
                    _classes.push_back(classLine);
                inputFile.close();
            }
            return _classes;
        }
        catch (std::exception& e) {
            std::cout << "ANSOVDetector::LoadClassesFromFile. " << e.what() << std::endl;
            return _classes;
        }
    }
    std::vector<Object>ANSOVDetector::PostProcessing() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            std::vector<int> class_list;
            std::vector<float> confidence_list;
            std::vector<cv::Rect> box_list;

            float* detections = inference_request_.get_output_tensor().data<float>();
            const cv::Mat detection_outputs(model_output_shape_, CV_32F, detections);

            for (int i = 0; i < detection_outputs.cols; ++i) {
                const cv::Mat classes_scores = detection_outputs.col(i).rowRange(4, detection_outputs.rows);
                cv::Point class_id;
                double score;
                cv::minMaxLoc(classes_scores, nullptr, &score, nullptr, &class_id);
                if (score > _detectionScoreThreshold) {
                    class_list.push_back(class_id.y);
                    confidence_list.push_back(score);
                    const float x = detection_outputs.at<float>(0, i);
                    const float y = detection_outputs.at<float>(1, i);
                    const float w = detection_outputs.at<float>(2, i);
                    const float h = detection_outputs.at<float>(3, i);
                    cv::Rect box;
                    box.x = static_cast<int>(x);
                    box.y = static_cast<int>(y);
                    box.width = static_cast<int>(w);
                    box.height = static_cast<int>(h);
                    box_list.push_back(box);
                }
            }

            std::vector<int> NMS_result;
            cv::dnn::NMSBoxes(box_list, confidence_list, _modelConfThreshold, _modelMNSThreshold, NMS_result);
            std::vector<Object> output;
            for (int i = 0; i < NMS_result.size(); i++)
            {
                Object result;
                int id = NMS_result[i];
                result.classId = class_list[id];
                result.confidence = confidence_list[id];
                result.box = GetBoundingBox(box_list[id]);
                output.push_back(result);
            }
            return output;
        }

        catch (const std::exception& e) {
            std::vector<Object> result;
            result.clear();
            std::cout << "ANSOVDetector::PostprocessImage. " << e.what() << std::endl;
            return result;
        }

    }
    std::string ANSOVDetector::GetOpenVINODevice() {
        std::vector<std::string> available_devices = _core.get_available_devices();
        bool device_found = false;
        std::string deviceName = "CPU";
        // Search for NPU
        auto it = std::find(available_devices.begin(), available_devices.end(), "NPU");
        if (it != available_devices.end()) {
            _core.set_property("NPU", ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY));
            _core.set_property("GPU", ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY));
            deviceName = "AUTO:NPU,GPU";
            device_found = true;
            return deviceName;
        }
        // If NPU not found, search for GPU
        if (!device_found) {
            it = std::find(available_devices.begin(), available_devices.end(), "GPU");
            if (it != available_devices.end()) {
                _core.set_property("GPU", ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY));
                deviceName = "GPU";
                device_found = true;
                return deviceName;
            }
        }

        // If GPU not found, search for GPU.0
        if (!device_found) {
            it = std::find(available_devices.begin(), available_devices.end(), "GPU.0");
            if (it != available_devices.end()) {
                _core.set_property("GPU", ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY));
                deviceName = "GPU";
                device_found = true;
                return deviceName;
            }
        }

        // If neither NPU nor GPU found, default to CPU
        if (!device_found) {
            _core.set_property("GPU", ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY));
            deviceName = "CPU";
            return deviceName;
        }
        return deviceName;

    }
    bool ANSOVDetector::Initialize(const std::string& modelFilePath, const std::string& classFilePath, std::vector <std::string>& _classes)
    {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _detectionScoreThreshold = 0.5;
        _modelConfThreshold = 0.48;
        _modelMNSThreshold = 0.5;
        // 1. Load labelMap and engine
        _classes.clear();
        _modelFilePath = modelFilePath;
        _classFilePath = classFilePath;
        _classes = LoadClassesFromFile();
        try {

            std::shared_ptr<ov::Model> model = _core.read_model(_modelFilePath);
            ov::preprocess::PrePostProcessor ppp = ov::preprocess::PrePostProcessor(model);
            ppp.input().tensor().set_element_type(ov::element::u8).set_layout("NHWC").set_color_format(ov::preprocess::ColorFormat::BGR);
            ppp.input().preprocess().convert_element_type(ov::element::f32).convert_color(ov::preprocess::ColorFormat::RGB).scale({ 255, 255, 255 });
            ppp.input().model().set_layout("NCHW");
            ppp.output().tensor().set_element_type(ov::element::f32);

            model = ppp.build();
            std::string deviceName = GetOpenVINODevice();
            compiled_model_ = _core.compile_model(model, deviceName);

            inference_request_ = compiled_model_.create_infer_request();
            const std::vector<ov::Output<ov::Node>> inputs = model->inputs();
            const ov::Shape input_shape = inputs[0].get_shape();

            short height = input_shape[1];
            short width = input_shape[2];
            model_input_shape_ = cv::Size2f(width, height);

            const std::vector<ov::Output<ov::Node>> outputs = model->outputs();
            const ov::Shape output_shape = outputs[0].get_shape();

            height = output_shape[1];
            width = output_shape[2];
            model_output_shape_ = cv::Size(width, height);
            _isInitialized = true;
            return true;

        }
        catch (std::exception& e) {
            std::cout << "ANSOVDetector::InitialModel. " << e.what() << std::endl;
            return false;
        }
    }
    std::vector<Object> ANSOVDetector::RunInference(const cv::Mat& input) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        std::vector<Object> output;
        output.clear();
        if (!_isInitialized) return output;
        try {

            // Step 0: Prepare input
			if (input.empty()) return output;
			if ((input.rows <= 5) || (input.cols <= 5)) return output;
            cv::Mat frame = input;
            this->PreprocessImage(frame);
            //Synchronous mode
            inference_request_.infer();
            //inference_request_.start_async();
			//inference_request_.wait();
            return PostProcessing();
        }
        catch (std::exception& e) {
            std::cout << "ANSOVDetector::RunInference. " << e.what() << std::endl;
            return output;
        }
    }
    /// <summary>
    /// ANSLRP_CPU
    /// </summary>
    ANSALPR_CPU::ANSALPR_CPU() {
        valid = false;
    };
    ANSALPR_CPU::~ANSALPR_CPU() {
        try {
            Destroy();
        }
        catch (std::exception& e) {
            this->_logger.LogFatal("ANSALPR_CPU::~ANSALPR_CPU", e.what(), __FILE__, __LINE__);
        }
    };
    bool ANSALPR_CPU::Destroy() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
			if (ppocr) ppocr.reset();
            return true;
        }
        catch (std::exception& e) {
            this->_logger.LogFatal("ANSALPR_CPU::Destroy", e.what(), __FILE__, __LINE__);
            return false;
        }
    };
    bool ANSALPR_CPU::Initialize(const std::string& licenseKey, const std::string& modelZipFilePath, const std::string& modelZipPassword, double detectorThreshold, double ocrThreshold, double colourThreshold) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            _licenseKey = licenseKey;
            _licenseValid = false;
			_detectorThreshold = detectorThreshold;
			_ocrThreshold = ocrThreshold;
			_colorThreshold = colourThreshold;

			if (_detectorThreshold < 0.25) _detectorThreshold = 0.25;
			if (_detectorThreshold > 0.95) _detectorThreshold = 0.95;
			if (_ocrThreshold < 0.25) _ocrThreshold = 0.25;
			if (_ocrThreshold > 0.95) _ocrThreshold = 0.95;
            _country = Country::VIETNAM;

            CheckLicense();
            if (!_licenseValid) {
                this->_logger.LogError("ANSALPR_CPU::Initialize.", "License is not valid.", __FILE__, __LINE__);
                return false;
            }
            // Extract model folder 
            // 0. Check if the modelZipFilePath exist?
            if (!FileExist(modelZipFilePath)) {
                this->_logger.LogFatal("ANSALPR_CPU::Initialize", "Model zip file is not exist", __FILE__, __LINE__);
            }
            else {
                this->_logger.LogTrace("ANSALPR_CPU::Initialize. Model zip file found: ", modelZipFilePath, __FILE__, __LINE__);
            }
            // 1. Unzip model zip file to a special location with folder name as model file (and version)
            std::string outputFolder;
            std::vector<std::string> passwordArray;
            if (!modelZipPassword.empty()) passwordArray.push_back(modelZipPassword);
            passwordArray.push_back("AnsDemoModels20@!");
            passwordArray.push_back("Sh7O7nUe7vJ/417W0gWX+dSdfcP9hUqtf/fEqJGqxYL3PedvHubJag==");
            passwordArray.push_back("3LHxGrjQ7kKDJBD9MX86H96mtKLJaZcTYXrYRdQgW8BKGt7enZHYMg==");
            std::string modelName = GetFileNameWithoutExtension(modelZipFilePath);

            size_t vectorSize = passwordArray.size();
            for (size_t i = 0; i < vectorSize; i++) {
                if (ExtractPasswordProtectedZip(modelZipFilePath, passwordArray[i], modelName, _modelFolder, false))
                    break; // Break the loop when the condition is met. 
            }
            // 2. Check if the outputFolder exist
            if (!FolderExist(_modelFolder)) {
                this->_logger.LogError("ANSALPR_CPU::Initialize. Output model folder is not exist", _modelFolder, __FILE__, __LINE__);
                return false; // That means the model file is not exist or the password is not correct
            }

			// 3. Load LD model
            alprChecker.Init(MAX_ALPR_FRAME);
            return true;
        }
        catch (std::exception& e) {
            this->_logger.LogFatal("ANSALPR_CPU::Initialize", e.what(), __FILE__, __LINE__);
            return false;
        }
    }  
    bool ANSALPR_CPU::LoadEngine() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
			// 1. Load license plate detection model (using CPU OpenVINO)
            if (_lprDetector) _lprDetector.reset();
            std::string lprModel = CreateFilePath(_modelFolder, "lprModel.xml");
            std::string lprClassesFile = CreateFilePath(_modelFolder, "lprClasses.names");
            if (FileExist(lprModel))
            {
                ANSCENTER::ModelConfig modelConfig;
                modelConfig.inpHeight = 640;
                modelConfig.inpHeight = 640;
                modelConfig.detectionScoreThreshold = _detectorThreshold;
                modelConfig.modelConfThreshold = 0.5;
                modelConfig.modelMNSThreshold = 0.5;
                modelConfig.detectionType = DetectionType::DETECTION;
                modelConfig.modelType = ModelType::OPENVINO;

                std::string _lprClasses;
                _lprDetector = std::make_unique<ANSCENTER::OPENVINOOD>();// OpenVINO
                _lprDetector->LoadModelFromFolder(_licenseKey, modelConfig, "lprModel", "lprClasses.names", _modelFolder, _lprClasses);
                // Disable tracker/stabilization — ANSLPR sub-detectors are internal
                _lprDetector->SetTracker(TrackerType::BYTETRACK, false);
                valid = true;
            }
            else {
                this->_logger.LogFatal("ANSALPR_CPU::Initialize", "Failed to load LPR model", __FILE__, __LINE__);
                valid = false;
                return false;
			}
            // 2. Load OCR model
            std::string recognizerModelFile = CreateFilePath(_modelFolder, "ENV4_REC.pdmodel");
            std::string recognizerModelParam = CreateFilePath(_modelFolder, "ENV4_REC.pdiparams");
            std::string recogizerCharDictionaryPath = CreateFilePath(_modelFolder, "dict_en.txt");
            std::string detectionModelFile = CreateFilePath(_modelFolder, "EN_DET.pdmodel");
            std::string detectionModelParam = CreateFilePath(_modelFolder, "EN_DET.pdiparams");
            std::string clsModelFile = CreateFilePath(_modelFolder, "CH_CLS.pdmodel");
            std::string clsModelParam = CreateFilePath(_modelFolder, "CH_CLS.pdiparams");
            try {
                std::string limit_type = "max";
                std::string det_db_score_mode = "slow";
                bool is_scale = true;
                double det_db_thresh = 0.9;
                double det_db_box_thresh = _ocrThreshold;
                double det_db_unclip_ratio = 2;
                bool use_dilation = false;
                int cls_batch_num = 1;
                double cls_thresh = 0.98;
                int rec_batch_num = 1;
                _isInitialized = ppocr->Initialize(detectionModelFile, clsModelFile, recognizerModelFile, recogizerCharDictionaryPath);
                ppocr->SetParameters(limit_type, det_db_score_mode, is_scale,
                    det_db_thresh, det_db_box_thresh, det_db_unclip_ratio,
                    use_dilation, cls_batch_num, cls_thresh, rec_batch_num);
                return _isInitialized;
            }
            catch (...) {
                _licenseValid = false;
                this->_logger.LogFatal("ANSALPR_CPU::Initialize", "Failed to create OCR objects", __FILE__, __LINE__);
                return false;
            }
        }
        catch (std::exception& e) {
            this->_logger.LogFatal("ANSALPR_CPU::Initialize", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
    std::vector<Object> ANSALPR_CPU::RunInference(const cv::Mat& input, const std::string &cameraId) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        std::vector<Object> output;
        output.clear();
        // Initial validation
        if (!_licenseValid) {
            this->_logger.LogError("ANSALPR_CPU::Inference", "Invalid license", __FILE__, __LINE__);
            return output;
        }
        if (!valid) {
            this->_logger.LogError("ANSALPR_CPU::Inference", "Invalid model", __FILE__, __LINE__);
            return output;
        }
        if (!_isInitialized) {
            this->_logger.LogError("ANSALPR_CPU::Inference", "Model is not initialized", __FILE__, __LINE__);
            return output;
        }

        try {
            if (input.empty()) {
                this->_logger.LogError("ANSALPR_CPU::Inference", "Input image is empty", __FILE__, __LINE__);
                return output;
            }

            if ((input.cols < 5) || (input.rows < 5)) {
                this->_logger.LogError("ANSALPR_CPU::Inference", "Input image size is too small", __FILE__, __LINE__);
                return output;
            }

            // Convert grayscale to 3-channel BGR if needed
            cv::Mat frame;
            if (input.channels() == 1) {
                cv::cvtColor(input, frame, cv::COLOR_GRAY2BGR);
            }
            else {
                frame = input;// input.clone();
            }
            //int fWidth = frame.cols;
            //int fHeight = frame.rows;
#ifdef FNS_DEBUG  // Corrected preprocessor directive
            cv::Mat draw = input.clone();
#endif 
            _detectedArea = cv::Rect(0, 0, frame.cols, frame.rows);
            if ((_detectedArea.width > 50) && (_detectedArea.height > 50)) {
#ifdef FNS_DEBUG  // Corrected preprocessor directive
                cv::rectangle(draw, _detectedArea, cv::Scalar(0, 0, 255), 2); // RED for detectedArea
#endif 
                // Ensure _lprDetector is valid
                if (!_lprDetector) {
                    this->_logger.LogFatal("ANSALPR_CPU::Inference", "_lprDetector is null", __FILE__, __LINE__);
                    return output;
                }
                cv::Mat activeFrame = frame(_detectedArea).clone();

                //std::vector<Object> lprOutputRaw = _lpDetector->RunInference(activeFrame, cameraId);
                //std::vector<Object> lprOutput = AdjustLicensePlateBoundingBoxes(lprOutputRaw, _detectedArea, frame.size(), 3.0);

                std::vector<Object> lprOutputRaw = _lprDetector->RunInference(activeFrame, cameraId);
                float iouThreshold = 0.4;
                std::vector<Object> lprOutput = ANSUtilityHelper::ApplyNMS(lprOutputRaw, iouThreshold);


                if (!lprOutput.empty()) {
                    if (!ppocr) {
                        this->_logger.LogFatal("ANSALPR_CPU::Inference", "PPOCR instance is null", __FILE__, __LINE__);
                        return output;
                    }
                    for (size_t i = 0; i < lprOutput.size(); ++i) {
                        Object lprObject = lprOutput[i];
                        cv::Rect box = lprObject.box;
#ifdef FNS_DEBUG  // Corrected preprocessor directive

                        cv::rectangle(draw, box, cv::Scalar(0, 255, 255), 2); // Yellow for fire
#endif	
                        // Crop region with padding
                        int padding = 0;
                        int x1 = std::max(box.x - padding, 0);
                        int y1 = std::max(box.y - padding, 0);
                        int x2 = std::min(box.x + box.width + padding, frame.cols);
                        int y2 = std::min(box.y + box.height + padding, frame.rows);
                        int width = std::max(0, x2 - x1);
                        int height = std::max(0, y2 - y1);
                        x1 = std::max(0, x1);
                        y1 = std::max(0, y1);
                        width = std::min(frame.cols - x1, width);
                        height = std::min(frame.rows - y1, height);

                        if (width > padding && height > padding) {
                            cv::Rect lprPos(x1, y1, width, height);
                            cv::Mat alignedLPR = alignPlateForOCR(frame, lprPos);
                            // Set metadata
                            lprObject.cameraId = cameraId;
                            lprObject.polygon = RectToNormalizedPolygon(lprObject.box, input.cols, input.rows);

                            // OCR inference
                            std::vector<PaddleOCR::OCRPredictResult> res_ocr = ppocr->ocr(alignedLPR);
                            std::string ocrText;

                            if (!res_ocr.empty() && res_ocr.size() < 3) {
                                for (const auto& r : res_ocr) {
                                    ocrText.append(r.text);
                                }
                                std::string rawText = AnalyseLicensePlateText(ocrText);
                                lprObject.className = alprChecker.checkPlate(cameraId, rawText, lprObject.box);
                                if (!lprObject.className.empty()) {
                                    output.push_back(lprObject);
                                }
                            }
                            alignedLPR.release();
                        }
                    }
                }
                activeFrame.release();
            }
            frame.release();
#ifdef FNS_DEBUG  // Corrected preprocessor directive

            //resize the draw image to fit the screen
            cv::resize(draw, draw, cv::Size(1920, 1080));
            cv::imshow("Detected Areas", draw);
            cv::waitKey(1);// Debugging: Diplsay the frame with the combined detected areas
            draw.release();// Debugging: Diplsay the frame with the combined detected areas
#endif	
            return output;
        }
        catch (const cv::Exception& e) {
            this->_logger.LogFatal("ANSALPR_CPU::Inference - OpenCV Exception", e.what(), __FILE__, __LINE__);
        }
        catch (const std::exception& e) {
            this->_logger.LogFatal("ANSALPR_CPU::Inference - std::exception", e.what(), __FILE__, __LINE__);
        }
        catch (...) {
            this->_logger.LogFatal("ANSALPR_CPU::Inference", "Unknown exception occurred", __FILE__, __LINE__);
        }

        return output;
    }
    bool ANSALPR_CPU::Inference(const cv::Mat& input, std::string& lprResult) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        if (input.empty()) return false;
        if ((input.cols < 5) || (input.rows < 5)) return false;
		return Inference(input, lprResult, "CustomCam");
    }
    bool ANSALPR_CPU::Inference(const cv::Mat& input, std::string& lprResult, const std::string & cameraId) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        std::vector<Object> output;
        output.clear();
        if (!_licenseValid) {
            this->_logger.LogError("ANSALPR_CPU::Inference", "Invalid license", __FILE__, __LINE__);
            return false;
        }
        if (!valid) {
            this->_logger.LogError("ANSALPR_CPU::Inference", "Invalid model", __FILE__, __LINE__);
            return false;
        }
        if (!_isInitialized) {
            this->_logger.LogError("ANSALPR_CPU::Inference", "Model is not initialized", __FILE__, __LINE__);
            return false;
        }
        try {
            if (input.empty()) {
				this->_logger.LogError("ANSALPR_CPU::Inference", "Input image is empty", __FILE__, __LINE__);
				return false;
            }
            if ((input.cols < 5) || (input.rows < 5)) {
                this->_logger.LogError("ANSALPR_CPU::Inference", "Input image size is too small", __FILE__, __LINE__);
                return false;
            } 
            // Convert grayscale images to 3-channel BGR if needed
            cv::Mat frame;
            if (input.channels() == 1) {
                cv::cvtColor(input, frame, cv::COLOR_GRAY2BGR);
            }
            else {
                frame = input;// input.clone();
            }
            int fWidth = frame.cols;
            int fHeight = frame.rows;
			cv::Rect roi(0,0,0,0);
            std::vector<Object> lprOutputRaw = _lprDetector->RunStaticInference(frame, roi, cameraId);

            //4. Apply Non-Maximum Suppression (NMS) to merge overlapping results
            float iouThreshold = 0.1;
            std::vector<Object> lprOutput = ANSUtilityHelper::ApplyNMS(lprOutputRaw, iouThreshold);
         
            if (lprOutput.size() > 0) {
                std::vector<Object> ocrOutput;
                ocrOutput.clear();
                std::string ocrText = "";
                for (int i = 0; i < lprOutput.size(); i++) {
                    ocrText = "";
                    Object lprObject = lprOutput.at(i);
                    cv::Rect box = lprObject.box;
                    //get frame
                    int padding = 10;
                    int x1 = std::max(box.x - padding, 0);
                    int y1 = std::max(box.y - padding, 0);
                    int x2 = std::min(box.x + box.width + padding, frame.cols);
                    int y2 = std::min(box.y + box.height + padding, frame.rows);
                    int width = std::max(0, x2 - x1);
                    int height = std::max(0, y2 - y1);
					x1 = std::max(0, x1);
					y1 = std::max(0, y1);
					width = std::min(frame.cols - x1, width);
					height = std::min(frame.rows - y1, height);
                    if ((width > padding) && (height > padding)) {
                        cv::Rect lprPos(x1, y1, width, height);
                        cv::Mat lprImage = frame(lprPos).clone();
                        lprObject.cameraId = cameraId;
                        lprObject.polygon = RectToNormalizedPolygon(lprObject.box, input.cols, input.rows);
                        std::vector<PaddleOCR::OCRPredictResult>  res_ocr = ppocr->ocr(lprImage);
                        int detectionSize = res_ocr.size();
                        if ((detectionSize > 0) && (detectionSize < 3)) {
                            for (int n = 0; n < res_ocr.size(); n++) { // number of detections                        
                                ocrText.append(res_ocr[n].text);
                            }
							std::string rawText = AnalyseLicensePlateText(ocrText);
                            lprObject.className = alprChecker.checkPlate(cameraId, rawText, lprObject.box);
                            if (!lprObject.className.empty() && (lprObject.className != ""))
                                output.push_back(lprObject);
                        }
                        lprImage.release();
                    }  
                }
            }
            frame.release();
            lprResult = VectorDetectionToJsonString(output);
            return true;
        }
        catch (std::exception& e) {
            lprResult = VectorDetectionToJsonString(output);
            this->_logger.LogFatal("ANSALPR_CPU::Inference", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
    bool ANSALPR_CPU::Inference(const cv::Mat& input, const std::vector<cv::Rect> & Bbox, std::string& lprResult) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        if (input.empty()) return false;
        if ((input.cols < 5) || (input.rows < 5)) return false;
        return Inference(input, Bbox, lprResult, "CustomCam");
    }
    
    bool ANSALPR_CPU::Inference(const cv::Mat& input, const std::vector<cv::Rect>& Bbox,
        std::string& lprResult, const std::string& cameraId)
    {
        std::lock_guard<std::recursive_mutex> lock(_mutex);

        // Early validation
        if (!_licenseValid) {
            this->_logger.LogError("ANSALPR_CPU::Inference", "Invalid license", __FILE__, __LINE__);
            lprResult.clear();
            return false;
        }

        if (!valid) {
            this->_logger.LogError("ANSALPR_CPU::Inference", "Invalid model", __FILE__, __LINE__);
            lprResult.clear();
            return false;
        }

        if (!_isInitialized) {
            this->_logger.LogError("ANSALPR_CPU::Inference", "Model is not initialized", __FILE__, __LINE__);
            lprResult.clear();
            return false;
        }

        if (input.empty()) {
            this->_logger.LogError("ANSALPR_CPU::Inference", "Input image is empty", __FILE__, __LINE__);
            lprResult.clear();
            return false;
        }

        if (input.cols < 5 || input.rows < 5) {
            this->_logger.LogError("ANSALPR_CPU::Inference", "Input image size is too small", __FILE__, __LINE__);
            lprResult.clear();
            return false;
        }

        if (!_lprDetector) {
            this->_logger.LogFatal("ANSALPR_CPU::Inference", "_lprDetector is null", __FILE__, __LINE__);
            lprResult.clear();
            return false;
        }

        if (!ppocr) {
            this->_logger.LogFatal("ANSALPR_CPU::Inference", "ppocr is null", __FILE__, __LINE__);
            lprResult.clear();
            return false;
        }

        try {
            // Convert grayscale to BGR if necessary
            const cv::Mat* framePtr;
            if (input.channels() == 1) {
                cv::cvtColor(input, this->_frameBuffer, cv::COLOR_GRAY2BGR);
                framePtr = &this->_frameBuffer;
            }
            else {
                framePtr = &input;
            }
            const cv::Mat& frame = *framePtr;

            const int frameWidth = frame.cols;
            const int frameHeight = frame.rows;
            constexpr int padding = 10;

            std::vector<Object> detectedObjects;

            if (!Bbox.empty()) {
                // Process each bounding box region
                detectedObjects.reserve(Bbox.size());

                for (const auto& bbox : Bbox) {
                    const int x1c = std::max(0, bbox.x);
                    const int y1c = std::max(0, bbox.y);
                    const int cropWidth = std::min(frameWidth - x1c, bbox.width);
                    const int cropHeight = std::min(frameHeight - y1c, bbox.height);

                    if (cropWidth < 5 || cropHeight < 5) {
                        continue;
                    }

                    cv::Mat croppedObject = frame(cv::Rect(x1c, y1c, cropWidth, cropHeight));
                    std::vector<Object> lprOutput = _lprDetector->RunInference(croppedObject);

                    for (auto& lprObject : lprOutput) {
                        const cv::Rect& box = lprObject.box;

                        // Calculate padded region within cropped image
                        const int x1 = std::max(0, box.x - padding);
                        const int y1 = std::max(0, box.y - padding);
                        const int x2 = std::min(cropWidth, box.x + box.width + padding);
                        const int y2 = std::min(cropHeight, box.y + box.height + padding);

                        // Adjust to original frame coordinates
                        lprObject.box.x = std::max(0, x1c + x1);
                        lprObject.box.y = std::max(0, y1c + y1);
                        lprObject.box.width = std::min(frameWidth - lprObject.box.x, x2 - x1);
                        lprObject.box.height = std::min(frameHeight - lprObject.box.y, y2 - y1);

                        if (lprObject.box.width <= padding || lprObject.box.height <= padding) {
                            continue;
                        }

                        // Run OCR
                        std::string ocrText = runOCROnPlate(frame, lprObject.box);
                        if (ocrText.empty()) {
                            continue;
                        }

                        std::string rawText = AnalyseLicensePlateText(ocrText);
                        lprObject.className = alprChecker.checkPlate(cameraId, rawText, lprObject.box);

                        if (lprObject.className.empty()) {
                            continue;
                        }

                        lprObject.cameraId = cameraId;
                        lprObject.polygon = RectToNormalizedPolygon(lprObject.box, input.cols, input.rows);
                        detectedObjects.push_back(std::move(lprObject));
                    }
                }
            }
            else {
                // No bounding boxes - run on full frame
                std::vector<Object> lprOutput = _lprDetector->RunInference(frame);
                detectedObjects.reserve(lprOutput.size());

                for (auto& lprObject : lprOutput) {
                    const cv::Rect& box = lprObject.box;

                    // Calculate padded region
                    const int x1 = std::max(0, box.x - padding);
                    const int y1 = std::max(0, box.y - padding);
                    const int width = std::min(frameWidth - x1, box.width + 2 * padding);
                    const int height = std::min(frameHeight - y1, box.height + 2 * padding);

                    if (width <= padding || height <= padding) {
                        continue;
                    }

                    // Run OCR
                    cv::Rect lprPos(x1, y1, width, height);
                    std::string ocrText = runOCROnPlate(frame, lprPos);
                    if (ocrText.empty()) {
                        continue;
                    }

                    std::string rawText = AnalyseLicensePlateText(ocrText);
                    lprObject.className = alprChecker.checkPlate(cameraId, rawText, lprObject.box);

                    if (lprObject.className.empty()) {
                        continue;
                    }

                    lprObject.cameraId = cameraId;
                    lprObject.polygon = RectToNormalizedPolygon(lprObject.box, input.cols, input.rows);
                    detectedObjects.push_back(std::move(lprObject));
                }
            }

            lprResult = VectorDetectionToJsonString(detectedObjects);
            return true;

        }
        catch (const std::exception& e) {
            lprResult.clear();
            this->_logger.LogFatal("ANSALPR_CPU::Inference", e.what(), __FILE__, __LINE__);
            return false;
        }
    }

    // Helper function to run OCR on a plate region
    std::string ANSALPR_CPU::runOCROnPlate(const cv::Mat& frame, const cv::Rect& plateRect) {
        cv::Mat lprImage = frame(plateRect);
        cv::Mat alignedLPR = enhanceForOCR(lprImage);

        std::vector<PaddleOCR::OCRPredictResult> res_ocr = ppocr->ocr(alignedLPR);

        const size_t detectionSize = res_ocr.size();
        if (detectionSize == 0 || detectionSize >= 3) {
            return {};
        }

        std::string ocrText;
        ocrText.reserve(32);  // Typical license plate length

        for (const auto& ocr : res_ocr) {
            ocrText.append(ocr.text);
        }

        return ocrText;
    }
 
    std::string ANSALPR_CPU::AnalyseLicensePlateText(const std::string& ocrText) {
        std::string analysedLP = "";
        try {
            switch (_country) {
            case Country::VIETNAM:
                std::string cleanOCRText = "";
                // int ind = findSubstringIndex(ocrText); // If ind >2, then LicenseType is deplomat 
                for (size_t i = 0; i < ocrText.size(); ++i) {
                    char c = ocrText[i];
                    if (std::isalnum(c))cleanOCRText += c;
                }
                int ocrSize = cleanOCRText.size();
                std::transform(cleanOCRText.begin(), cleanOCRText.end(), cleanOCRText.begin(), ::toupper);
                if (ocrSize == 8) {// car
                    std::string suburbCode = cleanOCRText.substr(0, 2);// from 11 to 99
                    std::string seriesCode = cleanOCRText.substr(2, 1);//A, B, C, D, E, F, G, H, K, L, M, N, P, S, T, U, V, X, Y, Z 
                    std::string numberCode = cleanOCRText.substr(3, 5);// 00000 to 99999
                    std::string newSuburbCode = convertStringToDigits(suburbCode);
                    // Convert the newSuburbCode to an integer
                    int numericValue = std::stoi(newSuburbCode);
                    // Check if it is within the range 11 to 99
                    if (numericValue >= 11 && numericValue <= 99) {
                        if (numericValue == 13)newSuburbCode = "73";
                        if (numericValue == 42)newSuburbCode = "62";
                        if (numericValue == 44)newSuburbCode = "64";
                        if (numericValue == 45)newSuburbCode = "65";
                        if (numericValue == 46)newSuburbCode = "66";
                        if (numericValue == 87)newSuburbCode = "81";
                        if (numericValue == 91)newSuburbCode = "97";
                        analysedLP = newSuburbCode + convertStringToLetters(seriesCode) + convertStringToDigits(numberCode);
                    }
                    else {
                        analysedLP = "";

                    }
                }
                else if (ocrSize == 9) {// motorbike
                    analysedLP = cleanOCRText;
                    //int dIndex = searchDiplomacyLP(cleanOCRText);
                    //if (dIndex != 5) {
                    //    std::string suburbCode = cleanOCRText.substr(0, 2);// from 11 to 99
                    //    std::string seriesCodeLetter = cleanOCRText.substr(2, 1);//A, B, C, D, E, F, G, H, K, L, M, N, P, S, T, U, V, X, Y, Z 
                    //    std::string seriesCodeNumberOrLetter = cleanOCRText.substr(3, 1);//number or A, B, C, D, E, F, G, H, K, L, M, N, P, S, T, U, V, X, Y, Z 
                    //    std::string numberCode = cleanOCRText.substr(4, 5);// 00000 to 99999
                    //    std::string newSuburbCode = convertStringToDigits(suburbCode);
                    //    std::string newseriesCodeNumberOrLetter;

                    //    if (std::isdigit(seriesCodeNumberOrLetter[0])) {
                    //        newseriesCodeNumberOrLetter = seriesCodeNumberOrLetter;
                    //    }
                    //    else {
                    //        newseriesCodeNumberOrLetter = convertStringToLetters(seriesCodeNumberOrLetter);
                    //    }
                    //    std::string combineSeriesCode = convertStringToLetters(seriesCodeLetter) + newseriesCodeNumberOrLetter;
                    //    if (combineSeriesCode == "RP")combineSeriesCode = "PR";

                    //    // Convert the newSuburbCode to an integer
                    //    int numericValue = std::stoi(newSuburbCode);
                    //    // Check if it is within the range 11 to 99
                    //    if (numericValue >= 11 && numericValue <= 99) {
                    //        if (numericValue == 13)newSuburbCode = "73";
                    //        if (numericValue == 42)newSuburbCode = "62";
                    //        if (numericValue == 44)newSuburbCode = "64";
                    //        if (numericValue == 45)newSuburbCode = "65";
                    //        if (numericValue == 46)newSuburbCode = "66";
                    //        if (numericValue == 87)newSuburbCode = "81";
                    //        if (numericValue == 91)newSuburbCode = "97";
                    //        analysedLP = newSuburbCode + combineSeriesCode + convertStringToDigits(numberCode);
                    //    }
                    //    else {
                    //        analysedLP = "";
                    //    }
                    //}
                    //else {// Diplomacy LP
                    //    std::string suburbCode = cleanOCRText.substr(0, 2);// from 11 to 99
                    //    std::string nationalCode = cleanOCRText.substr(2, 3);//012,123 
                    //    std::string specialCode = cleanOCRText.substr(5, 2);//NN, NG, CV, QT 
                    //    std::string numberCode = cleanOCRText.substr(7, 2);// 00 to 99
                    //    std::string newSuburbCode = convertStringToDigits(suburbCode);


                    //    // Convert the newSuburbCode to an integer
                    //    int numericValue = std::stoi(newSuburbCode);
                    //    // Check if it is within the range 11 to 99
                    //    if (numericValue >= 11 && numericValue <= 99) {
                    //        if (numericValue == 13)newSuburbCode = "73";
                    //        if (numericValue == 42)newSuburbCode = "62";
                    //        if (numericValue == 44)newSuburbCode = "64";
                    //        if (numericValue == 45)newSuburbCode = "65";
                    //        if (numericValue == 46)newSuburbCode = "66";
                    //        if (numericValue == 87)newSuburbCode = "81";
                    //        if (numericValue == 91)newSuburbCode = "97";
                    //        analysedLP = newSuburbCode + nationalCode + convertStringToDigits(numberCode);
                    //    }
                    //    else {
                    //        analysedLP = "";
                    //    }
                    //}
                }
                else if (ocrSize == 10) {// special case for depolmat LP
                    analysedLP = cleanOCRText;
                    //int dIndex = searchDiplomacyLP(cleanOCRText);
                    //if (dIndex != 5) {
                    //    std::string suburbCode = cleanOCRText.substr(0, 2);// from 11 to 99
                    //    std::string seriesCodeLetter = cleanOCRText.substr(2, 1);//A, B, C, D, E, F, G, H, K, L, M, N, P, S, T, U, V, X, Y, Z 
                    //    std::string seriesCodeNumberOrLetter = cleanOCRText.substr(3, 1);//number or A, B, C, D, E, F, G, H, K, L, M, N, P, S, T, U, V, X, Y, Z 
                    //    std::string numberCode = cleanOCRText.substr(4, 6);// 00000 to 99999
                    //    std::string newSuburbCode = convertStringToDigits(suburbCode);
                    //    std::string newseriesCodeNumberOrLetter;

                    //    if (std::isdigit(seriesCodeNumberOrLetter[0])) {
                    //        newseriesCodeNumberOrLetter = seriesCodeNumberOrLetter;
                    //    }
                    //    else {
                    //        newseriesCodeNumberOrLetter = convertStringToLetters(seriesCodeNumberOrLetter);
                    //    }
                    //    std::string combineSeriesCode = convertStringToLetters(seriesCodeLetter) + newseriesCodeNumberOrLetter;
                    //    if (combineSeriesCode == "RP")combineSeriesCode = "PR";

                    //    // Convert the newSuburbCode to an integer
                    //    int numericValue = std::stoi(newSuburbCode);
                    //    // Check if it is within the range 11 to 99
                    //    if (numericValue >= 11 && numericValue <= 99) {
                    //        if (numericValue == 13)newSuburbCode = "73";
                    //        if (numericValue == 42)newSuburbCode = "62";
                    //        if (numericValue == 44)newSuburbCode = "64";
                    //        if (numericValue == 45)newSuburbCode = "65";
                    //        if (numericValue == 46)newSuburbCode = "66";
                    //        if (numericValue == 87)newSuburbCode = "81";
                    //        if (numericValue == 91)newSuburbCode = "97";
                    //        analysedLP = newSuburbCode + combineSeriesCode + convertStringToDigits(numberCode);
                    //    }
                    //    else {
                    //        analysedLP = "";
                    //    }
                    //}
                    //else {// Diplomacy LP
                    //    std::string suburbCode = cleanOCRText.substr(0, 2);// from 11 to 99
                    //    std::string nationalCode = cleanOCRText.substr(2, 3);//012,123 
                    //    std::string specialCode = cleanOCRText.substr(5, 2);//NN, NG, CV, QT 
                    //    std::string numberCode = cleanOCRText.substr(7, 3);// 00 to 99
                    //    std::string newSuburbCode = convertStringToDigits(suburbCode);


                    //    // Convert the newSuburbCode to an integer
                    //    int numericValue = std::stoi(newSuburbCode);
                    //    // Check if it is within the range 11 to 99
                    //    if (numericValue >= 11 && numericValue <= 99) {
                    //        if (numericValue == 13)newSuburbCode = "73";
                    //        if (numericValue == 42)newSuburbCode = "62";
                    //        if (numericValue == 44)newSuburbCode = "64";
                    //        if (numericValue == 45)newSuburbCode = "65";
                    //        if (numericValue == 46)newSuburbCode = "66";
                    //        if (numericValue == 87)newSuburbCode = "81";
                    //        if (numericValue == 91)newSuburbCode = "97";
                    //        analysedLP = newSuburbCode + nationalCode + convertStringToDigits(numberCode);
                    //    }
                    //    else {
                    //        analysedLP = "";
                    //    }
                    //}
                }
                else {
                    // We will need to analyse the text for special cases
                    analysedLP = "";
                }
                break;
                //case Country::CHINA:
                //    break;
                //case Country::AUSTRALIA:
                //    break;
                //case Country::USA: 
                //    break;
            }
            return analysedLP;
        }
        catch (std::exception& e) {
            this->_logger.LogFatal("ANSALPR_OD::AnalyseLicensePlateText", e.what(), __FILE__, __LINE__);
            return "";
        }
    }
    
    int ANSALPR_CPU::findSubstringIndex(const std::string& str) {
        //std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            // List of substrings to search for
            std::string substrings[] = { "NN", "CV", "NG", "QT" };

            // Iterate through each substring
            for (const std::string& sub : substrings) {
                // Use std::string::find to search for the substring in the given string
                std::size_t pos = str.find(sub);

                // If the substring is found, return the index
                if (pos != std::string::npos) {
                    return static_cast<int>(pos); // Cast to int and return the index
                }
            }

            // If none of the substrings is found, return -1
            return -1;
        }
        catch (std::exception& e) {
			this->_logger.LogFatal("ANSALPR_CPU::findSubstringIndex", e.what(), __FILE__, __LINE__);
			return -1;

        }
    }
    char ANSALPR_CPU::fixLPDigit(char c) {
      //std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            switch (c) {
            case 'b':
                return '6';
            case 'c':
                return '0';
            case 'f':
            case 't':
                return '4';
            case 'j':
            case 'i':
            case 'l':
                return '1';
            case 's':
                return '5';
            case 'g':
            case 'q':
            case 'y':
                return '9';
            case 'o':
                return '0';
            default:
                return c; // If the character is not a letter to convert, return it unchanged
            }
		}
        catch (std::exception& e) {
            this->_logger.LogFatal("ANSALPR_CPU::fixLPDigit", e.what(), __FILE__, __LINE__);
            return c;
        } 
    }
    //only accept these letters: A, B, C, D, E, F, G, H, K, L, M, N, P, S, T, U, V, X, Y, Z 
	// I, J, O, Q, R, W 
    char ANSALPR_CPU::convertDigitToLetter(char c) {
        //std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            switch (c) {
            case '0':
            case 'o':
            case 'O':
            case 'Q':
                return 'C';    // '0' is typically mapped to 'O' or 'C', choosing 'O' to match letter set
            case '1':
            case 'I':
            case 'i':
            case 'l':
            case 'J':
                return 'L';    // '1' is commonly confused with 'I'
            case '2':
            case 'z':
                return 'Z';    // '2' resembles 'Z' in some fonts
            case '3':
                return 'E';    // '3' can resemble 'E' in some cases
            case '4':
                return 'A';    // '4' can resemble 'A' or 'H', choosing 'A'
            case '5':
            case 's':
                return 'S';    // '5' looks similar to 'S'
            case '6':
            case 'g':
                return 'G';    // '6' resembles 'G'
            case '7':
                return 'T';    // '7' is often confused with 'T'
            case '8':
            case 'b':
                return 'B';    // '8' resembles 'B'
            case '9':
            case 'R':

                return 'P';    // '9' is close to 'P'
            case 'W':
            case 'w':
                return 'V';    // 'W' is close to 'V'
            default:
                return c; // If the character is not a digit to convert, return it unchanged
            }
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSALPR_CPU::convertDigitToLetter", e.what(), __FILE__, __LINE__);
			return c;
		}
    }
    char ANSALPR_CPU::convertLetterToDigit(char c) {
       // std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            switch (c) {
                // Convert common letter confusions with digits
            case 'B':
            case 'b':           // Adding lowercase 'b' to match common mistypes
                return '8';
            case 'I':
            case 'i':
            case 'J':           // Capital 'J' can also resemble '1'
            case 'j':
            case 'L':
            case 'l':
                return '1';
            case 'S':
            case 's':
                return '5';
            case 'G':
            case 'g':           // Adding lowercase 'g' for better matching
                return '6';
            case 'O':
            case 'o':
            case 'Q':           // 'Q' can also be misread as '0'
            case 'U':
            case 'u':           // Adding lowercase 'u' as it resembles '0'
                return '0';
            case 'T':           // Capital 'T' sometimes looks like '7'
                return '7';
            case 'F':
            case 'f':
            case 't':
                return '4';
            case 'Y':           // Capital 'Y' may resemble '9'
            case 'y':
            case 'q':
                return '9';
            default:
                return '0'; // If no conversion, return the character unchanged
            }
        }
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSALPR_CPU::convertLetterToDigit", e.what(), __FILE__, __LINE__);
			return c;
		}
    }
    // Function to convert string to digits, skipping conversion if the character is already a digit
    std::string ANSALPR_CPU::convertStringToDigits(const std::string& input) {
       // std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            std::string result;
            for (char c : input) {
                if (std::isdigit(c)) {
                    result += c;  // Skip conversion if the character is a digit
                }
                else {
                    result += convertLetterToDigit(c);  // Convert if it's a letter
                }
            }
            return result;
        }
        catch (std::exception& e) {
			this->_logger.LogFatal("ANSALPR_CPU::convertStringToDigits", e.what(), __FILE__, __LINE__);
			return input;
        }
    }
    // Function to convert string to letters, skipping conversion if the character is already a letter
    std::string ANSALPR_CPU::convertStringToLetters(const std::string& input) {
        //std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            std::string result;
            for (char c : input) {
                if (std::isalpha(c)) {
                    result += c;  // Skip conversion if the character is already a letter
                }
                else {
                    result += convertDigitToLetter(c);  // Convert if it's a digit
                }
            }

            return result;
        }
        catch (std::exception& e) {
			this->_logger.LogFatal("ANSALPR_CPU::convertStringToLetters", e.what(), __FILE__, __LINE__);
			return input;
        }
       
    }
    int ANSALPR_CPU::searchDiplomacyLP(const std::string& input) {
        //std::lock_guard<std::recursive_mutex> lock(_mutex);
        // List of substrings to search for
        try {
            std::string substrings[] = { "NN", "NG", "CV", "QT" };
            // Initialize index to -1 (not found)
            int foundIndex = -1;

            // Loop through the substrings
            for (const auto& sub : substrings) {
                // Find the index of the current substring
                size_t index = input.find(sub);

                // If the substring is found and either no other substrings have been found,
                // or this substring occurs at an earlier position, update foundIndex.
                if (index != std::string::npos && (foundIndex == -1 || index < foundIndex)) {
                    foundIndex = index;
                }
            }

            return foundIndex; // If none are found, returns -1
        }
        catch (std::exception& e) {
			this->_logger.LogFatal("ANSALPR_CPU::searchDiplomacyLP", e.what(), __FILE__, __LINE__);
			return -1;
        }
    }
    bool ANSALPR_CPU::ValidateVNMotobikeLP(const std::string& input) {
       // std::lock_guard<std::recursive_mutex> lock(_mutex);
        // Search for the string in the list
        auto it = std::find(ValidVNMotobikeList.begin(), ValidVNMotobikeList.end(), input);
        // Check if found
        if (it != ValidVNMotobikeList.end()) {
            return true;
		}
		else {
			return false;
		}
    }
    bool ANSALPR_CPU::ValidateVNCarLP(const std::string& input) {
       // std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            // Search for the string in the list
            auto it = std::find(ValidVNCarList.begin(), ValidVNCarList.end(), input);
            // Check if found
            if (it != ValidVNCarList.end()) {
                return true;
            }
            else {
                return false;
            }
		}
        catch (std::exception& e) {
			this->_logger.LogFatal("ANSALPR_CPU::ValidateVNCarLP", e.what(), __FILE__, __LINE__);
			return false;
        }    
    }


    // Align plate
    cv::Mat ANSALPR_CPU::alignPlateForOCR(const cv::Mat& fullImage, const cv::Rect& bbox) {
        try {
            cv::Rect safeBox = bbox & cv::Rect(0, 0, fullImage.cols, fullImage.rows);
            if (safeBox.width < 10 || safeBox.height < 10)
                return fullImage(safeBox);// fullImage(safeBox).clone();

            cv::Mat roi = fullImage(safeBox);// fullImage(safeBox).clone();
            cv::Mat gray;
            cv::cvtColor(roi, gray, cv::COLOR_BGR2GRAY);

            // Enhance contours
            cv::Mat binary;
            cv::adaptiveThreshold(gray, binary, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV, 15, 10);

            std::vector<std::vector<cv::Point>> contours;
            cv::findContours(binary, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
            if (contours.empty()) {
                cv::Mat enhancedROI = enhanceForOCR(roi); // fallback
#ifdef FNS_DEBUG
                try {
                    cv::Mat debugLeft, debugRight;
                    cv::resize(roi, debugLeft, cv::Size(240, 80));
                    cv::resize(enhancedROI, debugRight, cv::Size(240, 80));
                    cv::Mat combined;
                    cv::hconcat(debugLeft, debugRight, combined);
                    cv::putText(combined, "Raw", cv::Point(10, 15), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 1);
                    cv::putText(combined, "Aligned", cv::Point(250, 15), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 0, 0), 1);
                    cv::imshow("LPR Cropped + Rotated", combined);
                    cv::waitKey(1);
                }
                catch (const std::exception& e) {
                    std::cerr << "LPR Debug Error: " << e.what() << std::endl;
                }
#endif
                return enhancedROI;
            }
            
            cv::Point2f roiCenter(static_cast<float>(roi.cols) / 2, static_cast<float>(roi.rows) / 2);
            float minDist = std::numeric_limits<float>::max();
            int bestIdx = -1;

            const float minWidth = roi.cols * 0.5f;
            const float minHeight = roi.rows * 0.5f;
            const float minAreaRatio = 0.3f;

            for (size_t i = 0; i < contours.size(); ++i) {
                cv::RotatedRect rect = cv::minAreaRect(contours[i]);
                float width = rect.size.width;
                float height = rect.size.height;
                float areaRect = width * height;
                float areaContour = cv::contourArea(contours[i]);

                if (width < minWidth || height < minHeight) continue;
                if (areaContour / areaRect < minAreaRatio) continue;

                float dist = cv::norm(rect.center - roiCenter);
                if (dist < minDist) {
                    minDist = dist;
                    bestIdx = static_cast<int>(i);
                }
            }

            if (bestIdx == -1) {
                cv::Mat enhancedROI=  enhanceForOCR(roi); // fallback
#ifdef FNS_DEBUG
                try {
                    cv::Mat debugLeft, debugRight;
                    cv::resize(roi, debugLeft, cv::Size(240, 80));
                    cv::resize(enhancedROI, debugRight, cv::Size(240, 80));
                    cv::Mat combined;
                    cv::hconcat(debugLeft, debugRight, combined);
                    cv::putText(combined, "Raw", cv::Point(10, 15), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 1);
                    cv::putText(combined, "Aligned", cv::Point(250, 15), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 0, 0), 1);
                    cv::imshow("LPR Cropped + Rotated", combined);
                    cv::waitKey(1);
                }
                catch (const std::exception& e) {
                    std::cerr << "LPR Debug Error: " << e.what() << std::endl;
                }
#endif
                return enhancedROI;
            }

            // Align and crop using best rotated rect
            cv::RotatedRect bestRect = cv::minAreaRect(contours[bestIdx]);
            float angle = bestRect.angle;
            if (bestRect.size.width < bestRect.size.height) {
                angle += 90.0f;
                std::swap(bestRect.size.width, bestRect.size.height);
            }
            angle = std::clamp(angle, -45.0f, 45.0f);

            // Rotate the image
            cv::Mat rotationMatrix = cv::getRotationMatrix2D(cv::Point2f(roi.cols / 2.0f, roi.rows / 2.0f), angle, 1.0);
            cv::Mat rotated;
            cv::warpAffine(roi, rotated, rotationMatrix, roi.size(), cv::INTER_LINEAR, cv::BORDER_REPLICATE);

            // Transform the rect center after rotation
            cv::Point2f newCenter;
            {
                cv::Mat ptMat = (cv::Mat_<double>(3, 1) << bestRect.center.x, bestRect.center.y, 1.0);
                cv::Mat newCenterMat = rotationMatrix * ptMat;
                newCenter = cv::Point2f(static_cast<float>(newCenterMat.at<double>(0)), static_cast<float>(newCenterMat.at<double>(1)));
            }

            // Apply small padding
            const int padding = 2;
            cv::Size paddedSize(
                std::min(rotated.cols, static_cast<int>(bestRect.size.width + 2 * padding)),
                std::min(rotated.rows, static_cast<int>(bestRect.size.height + 2 * padding))
            );

            cv::Mat rawCropped;
            cv::getRectSubPix(rotated, paddedSize, newCenter, rawCropped);
			cv::Mat cropped = enhanceForOCR(rawCropped);
#ifdef FNS_DEBUG
            try {
                cv::Mat debugRoi = roi.clone();
                cv::drawContours(debugRoi, contours, bestIdx, cv::Scalar(0, 255, 0), 1);

                cv::Point2f points[4];
                bestRect.points(points);
                for (int j = 0; j < 4; ++j)
                    cv::line(debugRoi, points[j], points[(j + 1) % 4], cv::Scalar(255, 0, 0), 1);
                cv::Mat debugLeft, debugRight;
                cv::resize(debugRoi, debugLeft, cv::Size(240, 80));
                cv::resize(cropped, debugRight, cv::Size(240, 80));
                cv::Mat combined;
                cv::hconcat(debugLeft, debugRight, combined);
                cv::putText(combined, "Raw", cv::Point(10, 15), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 1);
                cv::putText(combined, "Aligned", cv::Point(250, 15), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 0, 0), 1);
                cv::imshow("LPR Cropped + Rotated", combined);
                cv::waitKey(1);
            }
            catch (const std::exception& e) {
                std::cerr << "LPR Debug Error: " << e.what() << std::endl;
            }
#endif

            return cropped;
        }
        catch (const std::exception& e) {
            std::cerr << "alignPlateForOCR (rotated crop) exception: " << e.what() << std::endl;
            return fullImage(bbox & cv::Rect(0, 0, fullImage.cols, fullImage.rows)).clone(); // fallback
        }
    }
    cv::Mat ANSALPR_CPU::enhanceForOCR(const cv::Mat& plateROIOriginal) {
        if (plateROIOriginal.empty()) {
            std::cerr << "Error: plateROI is empty!" << std::endl;
            return cv::Mat();
        }

        cv::Mat plateROI;
        // Step 1: Upscale for OCR clarity
        cv::resize(plateROIOriginal, plateROI, cv::Size(), 2.0, 2.0, cv::INTER_LANCZOS4);

        // Step 2: Grayscale
        cv::Mat gray;
        if (plateROI.channels() == 3) {
            cv::cvtColor(plateROI, gray, cv::COLOR_BGR2GRAY);
        }
        else {
            gray = plateROI;// plateROI.clone();
        }

        // Step 3: Gentle denoise to preserve edges
        cv::Mat denoised;
        cv::bilateralFilter(gray, denoised, 7, 50, 50);  // Less aggressive

        // Step 4: First sharpening (Unsharp Masking)
        cv::Mat blurred, unsharp;
        cv::GaussianBlur(denoised, blurred, cv::Size(0, 0), 1.5);
        cv::addWeighted(denoised, 1.8, blurred, -0.8, 0, unsharp);

        // Step 5: Enhance contrast locally using CLAHE
        cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE(4.0, cv::Size(8, 8));  // stronger clip
        cv::Mat contrastEnhanced;
        clahe->apply(unsharp, contrastEnhanced);

        // Step 6: Additional edge sharpening using Laplacian
        cv::Mat lap, lapAbs, sharpened;
        cv::Laplacian(contrastEnhanced, lap, CV_16S, 3);
        cv::convertScaleAbs(lap, lapAbs);
        cv::addWeighted(contrastEnhanced, 1.2, lapAbs, -0.3, 0, sharpened);  // softer laplacian weight

        // Optional: Slight threshold to suppress low values (minor noise cleaning)
        // cv::threshold(sharpened, sharpened, 10, 255, cv::THRESH_TOZERO);

        // Step 7: Convert back to 3-channel RGB for OCR models like PaddleOCR
        cv::Mat ocrInput;
        cv::cvtColor(sharpened, ocrInput, cv::COLOR_GRAY2BGR);
        return ocrInput;
    }
};
