#ifndef ANSLPR_H
#define ANSLPR_H
#define ANSLPR_API __declspec(dllexport)
#pragma once
#include <iostream>
#include <cassert>
#include <fstream>
#include <string>
#include <filesystem>
#include <opencv2/opencv.hpp>
#include "Utility.h"
#include "ANSLicense.h"
#include "LabVIEWHeader/extcode.h"
#include "ANSEngineCommon.h"
#define  MAX_ALPR_FRAME 60
namespace ANSCENTER
{
    // Country enum is now defined in ANSLicense.h (ANSCENTER namespace)

    class ALPRChecker {
    private:
        int maxFrames;
        int minVotesToStabilize = 3;    // need at least 3 identical reads to lock
        float iouMatchThreshold = 0.3f; // IoU to match same physical plate across frames
		std::recursive_mutex		_mutex;

        // Per-plate spatial tracking: each tracked plate has a location + text history
        struct TrackedPlate {
            cv::Rect lastBox;
            std::deque<std::string> textHistory;  // rolling history of OCR reads
            std::string lockedText;               // majority-vote winner (empty = not locked yet)
            int lockCount = 0;                    // how many consecutive frames the lock held
            int framesSinceLastSeen = 0;          // how many checkPlate calls since this plate was last matched
        };
        int _pruneCounter = 0;                    // counts checkPlate calls for periodic pruning
        // cameraId -> list of tracked plates
        std::unordered_map<std::string, std::vector<TrackedPlate>> trackedPlates;

        [[nodiscard]] int levenshteinDistance(const std::string& s1, const std::string& s2);
        [[nodiscard]] float computeIoU(const cv::Rect& a, const cv::Rect& b);
        [[nodiscard]] std::string majorityVote(const std::deque<std::string>& history);

        // --- TrackId-based plate tracking (hybrid: trackId primary, Levenshtein fallback) ---
        struct TrackedPlateById {
            int trackId = 0;
            std::deque<std::string> textHistory;
            std::string lockedText;
            int lockCount = 0;
            int framesSinceLastSeen = 0;
            // Frames since the last full OCR was used to verify the lock.  When
            // skip-when-locked is active in ANSALPR_OCR::RunInference, this
            // counter increments on every frame whose OCR was skipped; once it
            // reaches the re-verify period, the next frame falls through to a
            // real OCR run (resetting the counter) so a stale or wrong lock
            // can self-correct via majority-vote re-lock.  Zero when not in
            // use (e.g. raw OCR pass-through, pipeline mode).
            int framesSinceVerify = 0;
        };
        // cameraId → (trackId → tracked plate)
        std::unordered_map<std::string, std::unordered_map<int, TrackedPlateById>> trackedPlatesById;
        int _pruneCounterById = 0;                // counts checkPlateByTrackId calls for periodic pruning

    public:
		void Init(int framesToStore = MAX_ALPR_FRAME);
		ALPRChecker(int framesToStore = MAX_ALPR_FRAME) : maxFrames(framesToStore) {}
		// Original API (backward compatible) — no spatial tracking
		[[nodiscard]] std::string checkPlate(const std::string& cameraId, const std::string& detectedPlate);
		// Enhanced API with bounding box for spatial plate tracking
		[[nodiscard]] std::string checkPlate(const std::string& cameraId, const std::string& detectedPlate, const cv::Rect& plateBox);
		// Hybrid API: trackId as primary identity, Levenshtein fallback for lost tracks
		[[nodiscard]] std::string checkPlateByTrackId(const std::string& cameraId, const std::string& detectedPlate, int trackId);

		// Skip-when-locked query: returns the locked plate text for (cameraId, trackId)
		// if the caller may safely SKIP running OCR this frame, or an empty string if
		// the caller must run OCR and feed the result back through checkPlateByTrackId.
		//
		// Behaviour:
		//   * Track not found, or found but not locked yet  → returns "".
		//   * Locked, and < `reverifyEvery` frames since the last full OCR for this
		//     track   → returns the locked text and increments the per-track
		//     re-verify counter.  Caller skips the recognizer entirely.
		//   * Locked, and ≥ `reverifyEvery` frames since the last full OCR  →
		//     returns "" and resets the counter, so the caller runs OCR this frame
		//     and the regular voting/re-lock logic in checkPlateByTrackId can
		//     self-correct a wrong lock or notice a vehicle swap on the same trackId.
		//
		// Side-effect: also resets framesSinceLastSeen so the periodic prune pass
		// in checkPlateByTrackId does not retire a track just because we skipped
		// OCR on it.  Without this the locked plate would silently disappear from
		// the registry after a few hundred skipped frames.
		[[nodiscard]] std::string tryReuseLockedText(const std::string& cameraId, int trackId, int reverifyEvery = 30);
    };

	class ANSLPR_API ANSALPR {

	protected:
		SPDLogger& _logger = SPDLogger::GetInstance("ALPR", false);
		bool   _debugFlag{ false };
		bool   _licenseValid{ false };
		bool   _isInitialized{ false};
		double _detectorThreshold{ 0.5 };
		double _ocrThreshold{ 0.5 };
		double _colorThreshold{ 0.0 };// If we use the colour information, this threshold should be greater  than 0.0
		std::vector<std::string> _plateFormats;
		std::string _licenseKey{};
		std::string _modelFolder;
		struct ImageSection {
			cv::Rect region;
			int priority;
			ImageSection(const cv::Rect& r) : region(r), priority(0) {}
		};
		cv::Size previousImageSize = cv::Size(0, 0);
		std::vector<ImageSection> cachedSections;
		int                     _currentPriority{ 0 }; // None 
		cv::Rect                _detectedArea;// Area where license plate are detected
		Country					_country;
		std::recursive_mutex	_mutex;

		// ALPRChecker toggle (Layer 2 + Layer 3):
		//   true  = enabled for full-frame, auto-disabled for pipeline/crop
		//   false = force disabled everywhere (raw OCR pass-through)
		bool _enableALPRChecker{ true };

	public:
		[[nodiscard]] virtual bool Initialize(const std::string& licenseKey, const std::string& modelZipFilePath, const std::string& modelZipPassword, double detectorThreshold, double ocrThreshold, double colourTheshold=0);
		[[nodiscard]] virtual bool LoadEngine();
		[[nodiscard]] virtual bool Inference(const cv::Mat& input, std::string& lprResult) = 0;
		[[nodiscard]] virtual bool Inference(const cv::Mat& input, std::string& lprResult,const std::string &cameraId) = 0;
		[[nodiscard]] virtual bool Inference(const cv::Mat& input, const std::vector<cv::Rect> & Bbox, std::string& lprResult) = 0;
		[[nodiscard]] virtual bool Inference(const cv::Mat& input, const std::vector<cv::Rect> & Bbox, std::string& lprResult,const std::string & cameraId) = 0;
		[[nodiscard]] virtual std::vector<Object> RunInference(const cv::Mat& input, const std::string &cameraId) = 0;

		/// Stateless batch inference for pipeline mode.
		/// For each vehicle ROI in `vehicleBoxes` (in FRAME coordinates), crop
		/// the vehicle, run LP detection and text recognition, and return
		/// detected plates in FULL-FRAME coordinates.
		///
		/// Tracker, voting, spatial dedup, and per-camera accumulating state
		/// are all bypassed — this is the fast-path for callers that already
		/// have precise vehicle bboxes and want raw per-frame results with no
		/// cross-frame memory. The implementation issues a single batched
		/// `_lpDetector->RunInferencesBatch` call for detection and a single
		/// batched recognizer call for OCR, so the ORT/TRT allocator sees
		/// exactly one shape per frame regardless of how many vehicles the
		/// caller passes.
		///
		/// Default implementation falls back to calling `RunInference` in a
		/// loop per crop so older subclasses keep compiling.
		[[nodiscard]] virtual std::vector<Object> RunInferencesBatch(
			const cv::Mat& input,
			const std::vector<cv::Rect>& vehicleBoxes,
			const std::string& cameraId);

		[[nodiscard]] std::string VectorDetectionToJsonString(const std::vector<Object>& dets);
		void SetPlateFormats(const std::vector<std::string>& formats);
		void SetPlateFormat(const std::string& format);
		[[nodiscard]] std::vector<std::string> GetPlateFormats() const;
		virtual ~ANSALPR();
		void   CheckLicense();

		/// Enable/disable deep pipeline benchmarking.
		/// When enabled, logs per-stage timing for the full ALPR pipeline
		/// (LP detection, OCR, color classification, validation, serialization).
		/// Also propagates the flag to sub-detectors (_lpDetector, _ocrDetector, etc.).
		virtual void ActivateDebugger(bool debugFlag) { _debugFlag = debugFlag; }

		/// Enable/disable ALPRChecker (Layer 2 text stabilization + Layer 3 dedup):
		///   true  = enabled for full-frame, auto-disabled for pipeline/crop
		///   false = force disabled everywhere (raw OCR pass-through)
		void SetALPRCheckerEnabled(bool enable) { _enableALPRChecker = enable; }
		bool IsALPRCheckerEnabled() const { return _enableALPRChecker; }

		virtual void SetCountry(Country country) { _country = country; }
		Country GetCountry() const { return _country; }

		[[nodiscard]] virtual bool Destroy() = 0;
		[[nodiscard]] static std::vector<cv::Rect> GetBoundingBoxes(const std::string& strBBoxes);
		[[nodiscard]] static std::string PolygonToString(const std::vector<cv::Point2f>& polygon);
		[[nodiscard]] static std::string KeypointsToString(const std::vector<float>& kps);
		[[nodiscard]] static std::vector<cv::Point2f> RectToNormalizedPolygon(const cv::Rect& rect, float imageWidth, float imageHeight);
	protected:
		// Utilities for scanning license plate
		  // Function to seperate screen size
		[[nodiscard]] double calculateDistanceToCenter(const cv::Point& center, const cv::Rect& rect);
		[[nodiscard]] std::vector<ImageSection> divideImage(const cv::Mat& image);
		[[nodiscard]] std::vector<ANSALPR::ImageSection> createSlideScreens(const cv::Mat& image);
		[[nodiscard]] int getHighestPriorityRegion();
		[[nodiscard]] int getLowestPriorityRegion();
		[[nodiscard]] cv::Rect getRegionByPriority(int priority);
		[[nodiscard]] std::vector<Object> AdjustLicensePlateBoundingBoxes(const std::vector<Object>& detectionsInROI,
															const cv::Rect& roi, const cv::Size& fullImageSize,
															float aspectRatio = 2.0f, // width at least 2x height
															int padding = 10 // base padding
															);

	};

}

extern "C" ANSLPR_API int		   CreateANSALPRHandle(ANSCENTER::ANSALPR **Handle, const char* licenseKey, const char* modelZipFilePath, const char* modelZipPassword, int engineType, double detectorThreshold, double ocrThreshold, double colourThreshold);
extern "C" ANSLPR_API int		   LoadANSALPREngineHandle(ANSCENTER::ANSALPR** Handle);

extern "C" ANSLPR_API std::string  ANSALPR_RunInference(ANSCENTER::ANSALPR **Handle, unsigned char* jpeg_string, unsigned int bufferLength);
extern "C" ANSLPR_API std::string  ANSALPR_RunInferenceWithCamID(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* cameraId);
extern "C" ANSLPR_API std::string  ANSALPR_RunInferenceBinary(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height);
extern "C" ANSLPR_API std::string  ANSALPR_RunInferenceBinaryInCroppedImages(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, const char* strBboxes);
extern "C" ANSLPR_API std::string  ANSALPR_RunInferenceInCroppedImages(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes);
extern "C" ANSLPR_API std::string  ANSALPR_RunInferenceInCroppedImagesWithCamID(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, const char* cameraId);

extern "C" ANSLPR_API int          ReleaseANSALPRHandle(ANSCENTER::ANSALPR **Handle);
//// For LabVIEW API
extern "C" ANSLPR_API int          ANSALPR_RunInference_LV(ANSCENTER::ANSALPR **Handle, unsigned char* jpeg_string, unsigned int bufferLength, LStrHandle detectionResult);
extern "C" ANSLPR_API int          ANSALPR_RunInferenceWithCamID_LV(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* cameraId, LStrHandle detectionResult);
extern "C" ANSLPR_API int		   ANSALPR_RunInferenceBinary_LV(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, LStrHandle detectionResult);
extern "C" ANSLPR_API int		   ANSALPR_RunInferenceInCroppedImages_LV(ANSCENTER::ANSALPR * *Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, LStrHandle detectionResult);
extern "C" ANSLPR_API int		   ANSALPR_RunInferenceInCroppedImagesWithCamID_LV(ANSCENTER::ANSALPR** Handle, unsigned char* jpeg_string, unsigned int bufferLength, const char* strBboxes, const char* cameraId, LStrHandle detectionResult);
extern "C" ANSLPR_API int		   ANSALPR_RunInferenceComplete_LV(ANSCENTER::ANSALPR** Handle, cv::Mat** cvImage, const char* cameraId, int getJpegString, int jpegImageSize, LStrHandle detectionResult, LStrHandle imageStr);
extern "C" ANSLPR_API int		   ANSALPR_RunInferenceComplete_CPP(ANSCENTER::ANSALPR** Handle, cv::Mat** cvImage, const char* cameraId, int getJpegString, int jpegImageSize,std::string& detectionResult, std::string& imageStr);
extern "C" ANSLPR_API int		   ANSALPR_RunInferencesComplete_LV(ANSCENTER::ANSALPR** Handle, cv::Mat** cvImage, const char* cameraId, int maxImageSize,const char* strBboxes, LStrHandle detectionResult);

// Dedicated pipeline-mode batch inference:
//   - always runs with tracker OFF, voting OFF, spatial dedup OFF
//   - issues ONE batched LP-detect call and ONE batched recognizer call
//     across every vehicle in `strBboxes`, instead of looping per crop
//   - returns plate bboxes in the caller's resized coordinate space (same
//     semantics as ANSALPR_RunInferencesComplete_LV)
// Use this in LabVIEW whenever the caller already has vehicle bboxes and
// wants raw per-frame results. Fixes the cross-frame allocator churn that
// causes ANSALPR_OCR memory growth under the per-bbox loop path.
extern "C" ANSLPR_API int		   ANSALPR_RunInferencesBatch_LV(ANSCENTER::ANSALPR** Handle, cv::Mat** cvImage, const char* cameraId, int maxImageSize, const char* strBboxes, LStrHandle detectionResult);

// Get/Set format
extern "C" ANSLPR_API int		   ANSALPR_SetFormat(ANSCENTER::ANSALPR** Handle, const char* format);
extern "C" ANSLPR_API int		   ANSALPR_SetFormats(ANSCENTER::ANSALPR** Handle, const char* formats);// comma separated formats
extern "C" ANSLPR_API int		   ANSALPR_GetFormats(ANSCENTER::ANSALPR** Handle, LStrHandle formats);// comma separated formats

// ALPRChecker: 1 = enabled (full-frame auto-detected), 0 = disabled (raw OCR)
extern "C" ANSLPR_API int		   ANSALPR_SetALPRCheckerEnabled(ANSCENTER::ANSALPR** Handle, int enable);

// Country: 0=VIETNAM, 1=CHINA, 2=AUSTRALIA, 3=USA, 4=INDONESIA, 5=JAPAN
extern "C" ANSLPR_API int		   ANSALPR_SetCountry(ANSCENTER::ANSALPR** Handle, int country);

// Unicode conversion utilities for LabVIEW wrapper classes
extern "C" ANSLPR_API int         ANSLPR_ConvertUTF8ToUTF16LE(const char* utf8Str, LStrHandle result, int includeBOM = 1);
extern "C" ANSLPR_API int         ANSLPR_ConvertUTF16LEToUTF8(const unsigned char* utf16leBytes, int byteLen, LStrHandle result);

#endif