// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include "ANSOCRBase.h"
#include "ANSCpuOCR.h"
#include "ANSOnnxOCR.h"
#include "ANSRtOCR.h"
#include "ANSLibsLoader.h"
#include "ANSGpuFrameRegistry.h"
#include "NV12PreprocessHelper.h"
#include <unordered_map>
#include <condition_variable>
#include <cstdint>


// Handle registry with refcount — prevents use-after-free when
// ReleaseANSOCRHandle is called while inference is still running.
static std::unordered_map<ANSCENTER::ANSOCRBase*, int>& OCRHandleRegistry() {
	static std::unordered_map<ANSCENTER::ANSOCRBase*, int> s;
	return s;
}
static std::mutex& OCRHandleRegistryMutex() {
	static std::mutex m;
	return m;
}
static std::condition_variable& OCRHandleRegistryCV() {
	static std::condition_variable cv;
	return cv;
}

static void RegisterOCRHandle(ANSCENTER::ANSOCRBase* h) {
	std::lock_guard<std::mutex> lk(OCRHandleRegistryMutex());
	OCRHandleRegistry()[h] = 1;  // refcount = 1
}

static ANSCENTER::ANSOCRBase* AcquireOCRHandle(ANSCENTER::ANSOCRBase* h) {
	std::lock_guard<std::mutex> lk(OCRHandleRegistryMutex());
	auto it = OCRHandleRegistry().find(h);
	if (it == OCRHandleRegistry().end()) return nullptr;
	it->second++;
	return h;
}

static bool ReleaseOCRHandleRef(ANSCENTER::ANSOCRBase* h) {
	std::lock_guard<std::mutex> lk(OCRHandleRegistryMutex());
	auto it = OCRHandleRegistry().find(h);
	if (it == OCRHandleRegistry().end()) return false;
	it->second--;
	if (it->second <= 0) {
		OCRHandleRegistry().erase(it);
		OCRHandleRegistryCV().notify_all();
		return true;
	}
	return false;
}

static bool UnregisterOCRHandle(ANSCENTER::ANSOCRBase* h) {
	std::unique_lock<std::mutex> lk(OCRHandleRegistryMutex());
	auto it = OCRHandleRegistry().find(h);
	if (it == OCRHandleRegistry().end()) return false;
	it->second--;
	bool ok = OCRHandleRegistryCV().wait_for(lk, std::chrono::seconds(5), [&]() {
		auto it2 = OCRHandleRegistry().find(h);
		return it2 == OCRHandleRegistry().end() || it2->second <= 0;
	});
	if (!ok) {
		OutputDebugStringA("WARNING: UnregisterOCRHandle timed out waiting for in-flight inference\n");
	}
	OCRHandleRegistry().erase(h);
	return true;
}

// RAII guard — ensures ReleaseOCRHandleRef is always called, preventing
// refcount leaks that would cause UnregisterOCRHandle to deadlock.
class OCRHandleGuard {
	ANSCENTER::ANSOCRBase* engine;
public:
	explicit OCRHandleGuard(ANSCENTER::ANSOCRBase* e) : engine(e) {}
	~OCRHandleGuard() { if (engine) ReleaseOCRHandleRef(engine); }
	ANSCENTER::ANSOCRBase* get() const { return engine; }
	explicit operator bool() const { return engine != nullptr; }
	OCRHandleGuard(const OCRHandleGuard&) = delete;
	OCRHandleGuard& operator=(const OCRHandleGuard&) = delete;
};

BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

// Extended version with limitSideLen parameter — new callers should use this.
extern "C" ANSOCR_API int		CreateANSOCRHandleEx(ANSCENTER::ANSOCRBase** Handle, const char* licenseKey,
	const char* modelFilePath, const char* modelFileZipPassword,
	int language, int engineMode, // 0: autodetect, 1 gpu, 2 cpu
	int gpuId,
	double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio,
	double classifierThreshold, int useDilation, int limitSideLen) {
	try {
		// Ensure all shared DLLs (OpenCV, OpenVINO, TRT, ORT) are pre-loaded
		ANSCENTER::ANSLibsLoader::Initialize();
		ANSCENTER::EngineType engineType = ANSCENTER::ANSLicenseHelper::CheckHardwareInformation();

		// Release existing handle if called twice (prevents leak from LabVIEW)
		if (*Handle) {
			if (UnregisterOCRHandle(*Handle)) {
				(*Handle)->Destroy();
				delete *Handle;
			}
			*Handle = nullptr;
		}

		// Validate limitSideLen: must be in (0, 20000], default to 960
		if (limitSideLen <= 0 || limitSideLen > 20000)
			limitSideLen = 960;

		// Backward compatibility: legacy PaddleOCR model uses CPU OCR engine
		std::string modelPath(modelFilePath ? modelFilePath : "");
		bool isLegacyModel = (modelPath.find("ANS_GenericOCR_v1.0") != std::string::npos);

		if (isLegacyModel) {
			(*Handle) = new ANSCENTER::ANSCPUOCR();
		}
		else {
			switch (engineMode) {
			case 0:// Auto-detect, always use ONNX for better compatibility, especially on AMD GPUs and high-res images
				(*Handle) = new ANSCENTER::ANSONNXOCR();
				break;
			case 1:// GPU — use TensorRT engine.
				limitSideLen = 960;
				(*Handle) = new ANSCENTER::ANSRTOCR();
				break;
			case 2:// CPU
				(*Handle) = new ANSCENTER::ANSONNXOCR();
				break;
			default:
				(*Handle) = new ANSCENTER::ANSONNXOCR();
				break;
			}
		}
		if (*Handle == nullptr) return 0;
		else {
			RegisterOCRHandle(*Handle);
			ANSCENTER::OCRModelConfig modelConfig;
			switch (language) {
				case 0: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::ENGLISH;
					break;
				}

				case 1: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::CHINESE;
					break;
				}

				case 2: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::FRENCH;
					break;
				}

				case 3: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::GERMANY;
					break;
				}

				case 4: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::JAPANESE;
					break;
				}

				case 5: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::KOREAN;
					break;
				}

				case 6: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::CUSTOM;
					break;
				}

				default: {
					modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::ENGLISH;
					break;
				}
			}
			modelConfig.useDetector = true;
			modelConfig.useRecognizer = true;
			modelConfig.useCLS = true;
			modelConfig.useLayout = false;
			modelConfig.useTable = false;
			modelConfig.useTensorRT = false;
			modelConfig.enableMKLDNN = false;
			modelConfig.useDilation = false;
			modelConfig.useAngleCLS = false;
			modelConfig.gpuId = gpuId;
			modelConfig.detectionDBThreshold = detectorDBThreshold;
			modelConfig.detectionBoxThreshold = detectorDBBoxThreshold;
			modelConfig.detectionDBUnclipRatio = detectorDBUnclipRatio;
			modelConfig.clsThreshold = classifierThreshold;
			if (useDilation == 1)modelConfig.useDilation = true;
			modelConfig.limitSideLen = limitSideLen;
			int result = (*Handle)->Initialize(licenseKey, modelConfig, modelFilePath, modelFileZipPassword, engineMode);
			return result;
		}
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

// Original signature (without limitSideLen) — kept for backward compatibility
// with third-party apps already built against the old DLL.
extern "C" ANSOCR_API int		CreateANSOCRHandle(ANSCENTER::ANSOCRBase** Handle, const char* licenseKey,
	const char* modelFilePath, const char* modelFileZipPassword,
	int language, int engineMode, // 0: autodetect, 1 gpu, 2 cpu
	int gpuId,
	double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio,
	double classifierThreshold, int useDilation) {
	return CreateANSOCRHandleEx(Handle, licenseKey, modelFilePath, modelFileZipPassword,
		language, engineMode, gpuId,
		detectorDBThreshold, detectorDBBoxThreshold, detectorDBUnclipRatio,
		classifierThreshold, useDilation, 960);
}

extern "C" ANSOCR_API std::string  RunInference(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength) {
	if (!Handle || !*Handle) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR);
		if (frame.empty()) return "";
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame);
		std::string stResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}

extern "C" ANSOCR_API std::string  RunInferenceWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* cameraId) {
	if (!Handle || !*Handle) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR);
		if (frame.empty()) return "";
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame, cameraId);
		std::string stResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}

extern "C" ANSOCR_API int RunInferenceCV(ANSCENTER::ANSOCRBase** Handle, const cv::Mat& image, std::string& ocrResult) {
	if (!Handle || !*Handle) return -1;
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return -3;
	auto* engine = guard.get();
	try {
		if (image.empty()) return 0;
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(image, "cameraId");
		ocrResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		return 1;
	}
	catch (...) { return -2; }
}

extern "C" ANSOCR_API std::string  RunInferenceBinary(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height) {
	if (!Handle || !*Handle || !jpeg_bytes || width == 0 || height == 0) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::Mat(height, width, CV_8UC3, jpeg_bytes).clone();
		if (frame.empty()) return "";
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame);
		std::string stResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}
static int ReleaseANSOCRHandle_Impl(ANSCENTER::ANSOCRBase** Handle) {
	try {
		if (!Handle || !*Handle) return 0;
		if (!UnregisterOCRHandle(*Handle)) {
			*Handle = nullptr;
			return 0;  // Not in registry — already freed
		}
		(*Handle)->Destroy();
		delete *Handle;
		*Handle = nullptr;
		return 0;
	}
	catch (...) {
		if (Handle) *Handle = nullptr;
		return 1;
	}
}

extern "C" ANSOCR_API int ReleaseANSOCRHandle(ANSCENTER::ANSOCRBase** Handle) {
	__try {
		return ReleaseANSOCRHandle_Impl(Handle);
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		return 1;
	}
}

extern "C" ANSOCR_API std::string  RunInferenceImagePath(ANSCENTER::ANSOCRBase** Handle, const char* imageFilePath) {
	if (!Handle || !*Handle) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		std::string stImageFileName(imageFilePath);
		cv::Mat frame = cv::imread(stImageFileName, cv::ImreadModes::IMREAD_COLOR);
		if (frame.empty()) return "";
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame);
		std::string stResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}


extern "C" ANSOCR_API std::string  RunInferenceInCroppedImages(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes) {
	if (!Handle || !*Handle) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR);
		if (frame.empty()) return "";
		std::vector<cv::Rect> bBoxes = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame, bBoxes);
		std::string stResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}
extern "C" ANSOCR_API std::string  RunInferenceInCroppedImagesWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, const char* cameraId) {
	if (!Handle || !*Handle) return "";
	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return "";
	auto* engine = guard.get();
	try {
		cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR);
		if (frame.empty()) return "";
		std::vector<cv::Rect> bBoxes = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(frame, bBoxes, cameraId);
		std::string stResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		frame.release();
		outputs.clear();
		return stResult;
	}
	catch (...) { return ""; }
}
//// For LabVIEW API
extern "C" ANSOCR_API int          RunInference_LV(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, LStrHandle detectionResult) {
	try {
		std::string st = RunInference(Handle, jpeg_string, bufferLength);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

extern "C" ANSOCR_API int          RunInference_LVWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* cameraId, LStrHandle detectionResult) {
	try {
		std::string st = RunInferenceWithCamID(Handle, jpeg_string, bufferLength, cameraId);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}
extern "C" ANSOCR_API int          RunInferenceBinary_LV(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, LStrHandle detectionResult) {
	try {
		std::string st = RunInferenceBinary(Handle, jpeg_bytes, width, height);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}
extern "C" ANSOCR_API int		   RunInferenceImagePath_LV(ANSCENTER::ANSOCRBase** Handle, const char* imageFilePath, LStrHandle detectionResult) {
	try {
		std::string st = RunInferenceImagePath(Handle, imageFilePath);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}
extern "C" ANSOCR_API int		   ANSOCRUnitTest(const char* modelFilePath, const char* imageFilePath, LStrHandle detectionResult)
{
	try {
		ANSCENTER::ANSOCRBase* infHandle;
		std::string licenseKey = "";
		int language = 0;// English
		int engine = 0;
		int createResult = CreateANSOCRHandle(&infHandle, licenseKey.c_str(), modelFilePath, "", language, engine);
		std::string st = RunInferenceImagePath(&infHandle, imageFilePath);
		ReleaseANSOCRHandle(&infHandle);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

extern "C" ANSOCR_API int          RunInferenceInCroppedImages_LV(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, LStrHandle detectionResult) {
	try {
		std::string st = RunInferenceInCroppedImages(Handle, jpeg_string, bufferLength, strBboxes);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}
extern "C" ANSOCR_API int		   RunInferenceInCroppedImages_LVWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, const char* cameraId, LStrHandle detectionResult) {
	try {
		std::string st = RunInferenceInCroppedImagesWithCamID(Handle, jpeg_string, bufferLength, strBboxes, cameraId);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error;
		error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr)
		{
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (std::exception& e) {
		return 0;
	}
	catch (...) {
		return 0;
	}
}

extern "C" ANSOCR_API int RunInferenceComplete_LV(
	ANSCENTER::ANSOCRBase** Handle,
	cv::Mat** cvImage,
	const char* cameraId,
	int getJpegString,
	int jpegImageSize,
	LStrHandle detectionResult,
	LStrHandle imageStr)
{
	if (!Handle || !*Handle) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return -3;
	auto* engine = guard.get();

	try {
		// Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*)
		GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage);
		cv::Mat localImage = (**cvImage).clone();

		int originalWidth = localImage.cols;
		int originalHeight = localImage.rows;

		if (originalWidth == 0 || originalHeight == 0) return -2;

		tl_currentGpuFrame() = gpuFrame;
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(localImage, cameraId);
		tl_currentGpuFrame() = nullptr;


		bool getJpeg = (getJpegString == 1);
		std::string stImage;

		int maxImageSize = originalWidth;
		bool resizeNeeded = (jpegImageSize > 0) && (jpegImageSize < maxImageSize);

		float ratio = 1.0f;
		int newWidth = originalWidth;
		int newHeight = originalHeight;

		if (resizeNeeded) {
			newWidth = jpegImageSize;
			newHeight = static_cast<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
			ratio = static_cast<float>(newWidth) / originalWidth;

			for (auto& obj : outputs) {
				obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x * ratio), newWidth - 1));
				obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y * ratio), newHeight - 1));
				obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width * ratio), newWidth - obj.box.x));
				obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height * ratio), newHeight - obj.box.y));
			}
		}
		else {
			for (auto& obj : outputs) {
				obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x), originalWidth - 1));
				obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y), originalHeight - 1));
				obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width), originalWidth - obj.box.x));
				obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height), originalHeight - obj.box.y));
			}
		}

		if (getJpeg) {
			cv::Mat processedImage = localImage;
			if (resizeNeeded) {
				cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA);
			}

			std::vector<uchar> buf;
			if (cv::imencode(".jpg", processedImage, buf, { cv::IMWRITE_JPEG_QUALITY, 50 })) {
				stImage.assign(buf.begin(), buf.end());
			}
		}

		std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		if (stDetectionResult.empty()) return 0;

		int size = static_cast<int>(stDetectionResult.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;

		(*detectionResult)->cnt = size;
		memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);

		if (getJpeg) {
			if (stImage.empty()) return 0;
			size = static_cast<int>(stImage.length());
			error = DSSetHandleSize(imageStr, sizeof(int32) + size * sizeof(uChar));
			if (error != noErr) return 0;

			(*imageStr)->cnt = size;
			memcpy((*imageStr)->str, stImage.c_str(), size);
		}

		return 1;
	}
	catch (...) { return 0; }
}
extern "C" ANSOCR_API int RunInferencesComplete_LV(ANSCENTER::ANSOCRBase** Handle, cv::Mat** cvImage, const char* cameraId, int maxImageSize, const char* strBboxes, LStrHandle detectionResult) {
	if (!Handle || !*Handle) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

	OCRHandleGuard guard(AcquireOCRHandle(*Handle));
	if (!guard) return -3;
	auto* engine = guard.get();

	try {
		// Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*)
		GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage);
		cv::Mat localImage = (**cvImage).clone();

		std::vector<ANSCENTER::OCRObject> objectDetectionResults;
		std::vector<cv::Rect> bBox = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);

		const int originalWidth = localImage.cols;
		const int originalHeight = localImage.rows;

		const double scaleFactor = (maxImageSize > 0) ? static_cast<double>(originalWidth) / maxImageSize : 1.0;

		tl_currentGpuFrame() = gpuFrame;
		if (bBox.empty()) {
			objectDetectionResults = engine->RunInference(localImage, cameraId);
		}
		else {
			for (const auto& rect : bBox) {
				cv::Rect scaledRect;
				scaledRect.x = static_cast<int>(rect.x * scaleFactor);
				scaledRect.y = static_cast<int>(rect.y * scaleFactor);
				scaledRect.width = static_cast<int>(rect.width * scaleFactor);
				scaledRect.height = static_cast<int>(rect.height * scaleFactor);

				scaledRect &= cv::Rect(0, 0, originalWidth, originalHeight);
				if (scaledRect.width <= 0 || scaledRect.height <= 0)
					continue;

				const cv::Mat croppedImage = localImage(scaledRect);
				std::vector<ANSCENTER::OCRObject> croppedDetectionResults = engine->RunInference(croppedImage, cameraId);

				for (auto& obj : croppedDetectionResults) {
					obj.box.x = (obj.box.x + scaledRect.x) / scaleFactor;
					obj.box.y = (obj.box.y + scaledRect.y) / scaleFactor;
					obj.box.width /= scaleFactor;
					obj.box.height /= scaleFactor;

					objectDetectionResults.push_back(std::move(obj));
				}
			}
		}
		tl_currentGpuFrame() = nullptr;

		std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(objectDetectionResults);
		if (stDetectionResult.empty()) return 0;

		const int size = static_cast<int>(stDetectionResult.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;

		(*detectionResult)->cnt = size;
		memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);

		return 1;
	}
	catch (...) { return 0; }

}

// ============================================================================
// V2 LabVIEW API — Accept handle as uint64_t by value.
// Eliminates Handle** pointer-to-pointer instability when LabVIEW calls
// concurrently from multiple tasks.
// LabVIEW CLFN: set Handle parameter to Numeric / Unsigned Pointer-sized Integer / Pass: Value
// ============================================================================

// Helper: cast uint64_t handle to ANSOCRBase** for delegation to existing functions
#define OCR_V2_HANDLE_SETUP(handleVal) \
	ANSCENTER::ANSOCRBase* _v2Direct = reinterpret_cast<ANSCENTER::ANSOCRBase*>(handleVal); \
	if (_v2Direct == nullptr) return 0; \
	ANSCENTER::ANSOCRBase* _v2Arr[1] = { _v2Direct }; \
	ANSCENTER::ANSOCRBase** Handle = &_v2Arr[0];

extern "C" ANSOCR_API int RunInference_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, LStrHandle detectionResult) {
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInference(Handle, jpeg_string, bufferLength);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInference_LVWithCamID_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, const char* cameraId, LStrHandle detectionResult) {
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInferenceWithCamID(Handle, jpeg_string, bufferLength, cameraId);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferenceBinary_LV_V2(uint64_t handleVal, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, LStrHandle detectionResult) {
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInferenceBinary(Handle, jpeg_bytes, width, height);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferenceImagePath_LV_V2(uint64_t handleVal, const char* imageFilePath, LStrHandle detectionResult) {
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInferenceImagePath(Handle, imageFilePath);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferenceInCroppedImages_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, LStrHandle detectionResult) {
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInferenceInCroppedImages(Handle, jpeg_string, bufferLength, strBboxes);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferenceInCroppedImages_LVWithCamID_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, const char* cameraId, LStrHandle detectionResult) {
	try {
		OCR_V2_HANDLE_SETUP(handleVal);
		std::string st = RunInferenceInCroppedImagesWithCamID(Handle, jpeg_string, bufferLength, strBboxes, cameraId);
		if (st.empty()) return 0;
		int size = static_cast<int>(st.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error == noErr) {
			(*detectionResult)->cnt = size;
			memcpy((*detectionResult)->str, st.c_str(), size);
			return 1;
		}
		else return 0;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferenceComplete_LV_V2(
	uint64_t handleVal,
	cv::Mat** cvImage,
	const char* cameraId,
	int getJpegString,
	int jpegImageSize,
	LStrHandle detectionResult,
	LStrHandle imageStr)
{
	ANSCENTER::ANSOCRBase* directHandle = reinterpret_cast<ANSCENTER::ANSOCRBase*>(handleVal);
	if (directHandle == nullptr) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

	OCRHandleGuard guard(AcquireOCRHandle(directHandle));
	if (!guard) return -3;
	auto* engine = guard.get();

	try {
		// Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*)
		GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage);
		cv::Mat localImage = (**cvImage).clone();

		int originalWidth = localImage.cols;
		int originalHeight = localImage.rows;

		if (originalWidth == 0 || originalHeight == 0) return -2;

		tl_currentGpuFrame() = gpuFrame;
		std::vector<ANSCENTER::OCRObject> outputs = engine->RunInference(localImage, cameraId);
		tl_currentGpuFrame() = nullptr;

		bool getJpeg = (getJpegString == 1);
		std::string stImage;

		int maxImageSize = originalWidth;
		bool resizeNeeded = (jpegImageSize > 0) && (jpegImageSize < maxImageSize);

		float ratio = 1.0f;
		int newWidth = originalWidth;
		int newHeight = originalHeight;

		if (resizeNeeded) {
			newWidth = jpegImageSize;
			newHeight = static_cast<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
			ratio = static_cast<float>(newWidth) / originalWidth;

			for (auto& obj : outputs) {
				obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x * ratio), newWidth - 1));
				obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y * ratio), newHeight - 1));
				obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width * ratio), newWidth - obj.box.x));
				obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height * ratio), newHeight - obj.box.y));
			}
		}
		else {
			for (auto& obj : outputs) {
				obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x), originalWidth - 1));
				obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y), originalHeight - 1));
				obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width), originalWidth - obj.box.x));
				obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height), originalHeight - obj.box.y));
			}
		}

		if (getJpeg) {
			cv::Mat processedImage = localImage;
			if (resizeNeeded) {
				cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA);
			}

			std::vector<uchar> buf;
			if (cv::imencode(".jpg", processedImage, buf, { cv::IMWRITE_JPEG_QUALITY, 50 })) {
				stImage.assign(buf.begin(), buf.end());
			}
		}

		std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
		if (stDetectionResult.empty()) return 0;

		int size = static_cast<int>(stDetectionResult.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;

		(*detectionResult)->cnt = size;
		memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);

		if (getJpeg) {
			if (stImage.empty()) return 0;
			size = static_cast<int>(stImage.length());
			error = DSSetHandleSize(imageStr, sizeof(int32) + size * sizeof(uChar));
			if (error != noErr) return 0;

			(*imageStr)->cnt = size;
			memcpy((*imageStr)->str, stImage.c_str(), size);
		}

		return 1;
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API int RunInferencesComplete_LV_V2(uint64_t handleVal, cv::Mat** cvImage, const char* cameraId, int maxImageSize, const char* strBboxes, LStrHandle detectionResult) {
	ANSCENTER::ANSOCRBase* directHandle = reinterpret_cast<ANSCENTER::ANSOCRBase*>(handleVal);
	if (directHandle == nullptr) return -1;
	if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2;

	OCRHandleGuard guard(AcquireOCRHandle(directHandle));
	if (!guard) return -3;
	auto* engine = guard.get();

	try {
		// Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*)
		GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage);
		cv::Mat localImage = (**cvImage).clone();

		std::vector<ANSCENTER::OCRObject> objectDetectionResults;
		std::vector<cv::Rect> bBox = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);

		const int originalWidth = localImage.cols;
		const int originalHeight = localImage.rows;

		const double scaleFactor = (maxImageSize > 0) ? static_cast<double>(originalWidth) / maxImageSize : 1.0;

		tl_currentGpuFrame() = gpuFrame;
		if (bBox.empty()) {
			objectDetectionResults = engine->RunInference(localImage, cameraId);
		}
		else {
			for (const auto& rect : bBox) {
				cv::Rect scaledRect;
				scaledRect.x = static_cast<int>(rect.x * scaleFactor);
				scaledRect.y = static_cast<int>(rect.y * scaleFactor);
				scaledRect.width = static_cast<int>(rect.width * scaleFactor);
				scaledRect.height = static_cast<int>(rect.height * scaleFactor);

				scaledRect &= cv::Rect(0, 0, originalWidth, originalHeight);
				if (scaledRect.width <= 0 || scaledRect.height <= 0)
					continue;

				const cv::Mat croppedImage = localImage(scaledRect);
				std::vector<ANSCENTER::OCRObject> croppedDetectionResults = engine->RunInference(croppedImage, cameraId);

				for (auto& obj : croppedDetectionResults) {
					obj.box.x = (obj.box.x + scaledRect.x) / scaleFactor;
					obj.box.y = (obj.box.y + scaledRect.y) / scaleFactor;
					obj.box.width /= scaleFactor;
					obj.box.height /= scaleFactor;

					objectDetectionResults.push_back(std::move(obj));
				}
			}
		}
		tl_currentGpuFrame() = nullptr;

		std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(objectDetectionResults);
		if (stDetectionResult.empty()) return 0;

		const int size = static_cast<int>(stDetectionResult.length());
		MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;

		(*detectionResult)->cnt = size;
		memcpy((*detectionResult)->str, stDetectionResult.c_str(), size);

		return 1;
	}
	catch (...) { return 0; }
}

// ============================================================================
// V2 Create / Release — handle returned/passed as uint64_t by value.
// ============================================================================

extern "C" ANSOCR_API uint64_t CreateANSOCRHandleEx_V2(const char* licenseKey,
	const char* modelFilePath, const char* modelFileZipPassword,
	int language, int engineMode,
	int gpuId,
	double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio,
	double classifierThreshold, int useDilation, int limitSideLen) {
	try {
		ANSCENTER::ANSOCRBase* handle = nullptr;
		int result = CreateANSOCRHandleEx(&handle, licenseKey, modelFilePath, modelFileZipPassword,
			language, engineMode, gpuId,
			detectorDBThreshold, detectorDBBoxThreshold, detectorDBUnclipRatio,
			classifierThreshold, useDilation, limitSideLen);
		if (result == 0 || handle == nullptr) return 0;
		return reinterpret_cast<uint64_t>(handle);
	}
	catch (...) { return 0; }
}

extern "C" ANSOCR_API uint64_t CreateANSOCRHandle_V2(const char* licenseKey,
	const char* modelFilePath, const char* modelFileZipPassword,
	int language, int engineMode,
	int gpuId,
	double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio,
	double classifierThreshold, int useDilation) {
	return CreateANSOCRHandleEx_V2(licenseKey, modelFilePath, modelFileZipPassword,
		language, engineMode, gpuId,
		detectorDBThreshold, detectorDBBoxThreshold, detectorDBUnclipRatio,
		classifierThreshold, useDilation, 960);
}

extern "C" ANSOCR_API int ReleaseANSOCRHandle_V2(uint64_t handleVal) {
	try {
		ANSCENTER::ANSOCRBase* directHandle = reinterpret_cast<ANSCENTER::ANSOCRBase*>(handleVal);
		if (directHandle == nullptr) return 0;
		if (!UnregisterOCRHandle(directHandle)) {
			return 0;  // Not in registry — already freed
		}
		directHandle->Destroy();
		delete directHandle;
		return 0;
	}
	catch (...) {
		return 1;
	}
}

