#include "ANSOCR.h"
#include "Utility.h"
#include <opencv2/highgui.hpp>
#include <omp.h>


template <typename T>
T GetData(const boost::property_tree::ptree& pt, const std::string& key)
{
	T ret;
	if (boost::optional<T> data = pt.get_optional<T>(key))
	{
		ret = data.get();
	}
	else
	{
		throw std::runtime_error("Could not read the data from ptree: [key: " + key + "]");
	}
	return ret;
}
namespace ANSCENTER {
	/// <summary>
	/// Base class
	/// </summary>
	/// 
	/// 
	void ANSOCRBase::CheckLicense() {
		try {
			_licenseValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(_licenseKey, 16, "ANSOCR");//Default productId=10
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSOCRBase::CheckLicense()", e.what());
		}
	}
	bool ANSOCRBase::Initialize(std::string licenseKey, OCRModelConfig modelConfig, const std::string& modelZipFilePath, const std::string& modelZipPassword, int engineMode) {
		try {
			CheckLicense();
			if (!_licenseValid) {
				this->_logger.LogError("ANSOCRBase::Initialize", "Invalid License");
				return false;
			}
			_licenseKey = licenseKey;
			_engineMode = engineMode;
			_licenseValid = false;
			_modelFolder = "";
			_modelConfigFile = "";
			_modelConfig = modelConfig;
			_modelFolder.clear();
			_modelConfigFile.clear();
			CheckLicense();

			// 0. Check if the modelZipFilePath exist?
			if (!FileExist(modelZipFilePath))  return false;
			// 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);
			this->_logger.LogInfo("ANSOCRBase::Initialize. Model name", modelName);

			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 (!std::filesystem::exists(_modelFolder)) {
				this->_logger.LogError("ANSOCRBase::Initialize. Output model folder is not exist", _modelFolder);
				return false; // That means the model file is not exist or the password is not correct
			}
			// 3. Check if the model has the configuration file
			std::string modelConfigName = "model_config.json";
			_modelConfigFile = CreateFilePath(_modelFolder, modelConfigName);


			//4. For now we do have the model folder so we will assign paths to OCR models
			switch (_modelConfig.ocrLanguage) {
				case ANSCENTER::OCRLanguage::ENGLISH:{
					_modelConfig.detectionModelDir = CreateFilePath(_modelFolder, "EN_Det");
					_modelConfig.recognizerModelDir = CreateFilePath(_modelFolder, "ENv4_Rec");
					_modelConfig.recogizerCharDictionaryPath = CreateFilePath(_modelFolder, "dict_en.txt");
					_modelConfig.clsModelDir = CreateFilePath(_modelFolder, "CH_CLS");
					_modelConfig.layoutModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.layourDictionaryPath = CreateFilePath(_modelFolder, "");
					_modelConfig.tableModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.tableCharDictionaryPath = CreateFilePath(_modelFolder, "");
					break;
				}
				case ANSCENTER::OCRLanguage::CHINESE: {
					_modelConfig.detectionModelDir = CreateFilePath(_modelFolder, "CHv4_Det");
					_modelConfig.recognizerModelDir = CreateFilePath(_modelFolder, "CHv4_Rec");
					_modelConfig.recogizerCharDictionaryPath = CreateFilePath(_modelFolder, "dict_ch.txt");
					_modelConfig.clsModelDir = CreateFilePath(_modelFolder, "CH_CLS");
					_modelConfig.layoutModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.layourDictionaryPath = CreateFilePath(_modelFolder, "");
					_modelConfig.tableModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.tableCharDictionaryPath = CreateFilePath(_modelFolder, "");
					break;
				}

				case ANSCENTER::OCRLanguage::FRENCH: {
					_modelConfig.detectionModelDir = CreateFilePath(_modelFolder, "MPPv3_Det");
					_modelConfig.recognizerModelDir = CreateFilePath(_modelFolder, "FR_Rec");
					_modelConfig.recogizerCharDictionaryPath = CreateFilePath(_modelFolder, "dict_fr.txt");
					_modelConfig.clsModelDir = CreateFilePath(_modelFolder, "CH_CLS");
					_modelConfig.layoutModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.layourDictionaryPath = CreateFilePath(_modelFolder, "");
					_modelConfig.tableModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.tableCharDictionaryPath = CreateFilePath(_modelFolder, "");
					break;
				}

				case ANSCENTER::OCRLanguage::GERMANY: {
					_modelConfig.detectionModelDir = CreateFilePath(_modelFolder, "MPPv3_Det");
					_modelConfig.recognizerModelDir = CreateFilePath(_modelFolder, "GR_Rec");
					_modelConfig.recogizerCharDictionaryPath = CreateFilePath(_modelFolder, "dict_gr.txt");
					_modelConfig.clsModelDir = CreateFilePath(_modelFolder, "CH_CLS");
					_modelConfig.layoutModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.layourDictionaryPath = CreateFilePath(_modelFolder, "");
					_modelConfig.tableModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.tableCharDictionaryPath = CreateFilePath(_modelFolder, "");
					break;
				}

				case ANSCENTER::OCRLanguage::JAPANESE: {
					_modelConfig.detectionModelDir = CreateFilePath(_modelFolder, "MPPv3_Det");
					_modelConfig.recognizerModelDir = CreateFilePath(_modelFolder, "JP_Rec");
					_modelConfig.recogizerCharDictionaryPath = CreateFilePath(_modelFolder, "dict_jp.txt");
					_modelConfig.clsModelDir = CreateFilePath(_modelFolder, "CH_CLS");
					_modelConfig.layoutModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.layourDictionaryPath = CreateFilePath(_modelFolder, "");
					_modelConfig.tableModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.tableCharDictionaryPath = CreateFilePath(_modelFolder, "");
					break;
				}
			
				case ANSCENTER::OCRLanguage::KOREAN: {
					_modelConfig.detectionModelDir = CreateFilePath(_modelFolder, "MPPv3_Det");
					_modelConfig.recognizerModelDir = CreateFilePath(_modelFolder, "KR_Rec");
					_modelConfig.recogizerCharDictionaryPath = CreateFilePath(_modelFolder, "dict_kr.txt");
					_modelConfig.clsModelDir = CreateFilePath(_modelFolder, "CH_CLS");
					_modelConfig.layoutModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.layourDictionaryPath = CreateFilePath(_modelFolder, "");
					_modelConfig.tableModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.tableCharDictionaryPath = CreateFilePath(_modelFolder, "");
					break;
				}

				case ANSCENTER::OCRLanguage::CUSTOM: {
					_modelConfig.detectionModelDir = CreateFilePath(_modelFolder, "CT_Det");
					_modelConfig.recognizerModelDir = CreateFilePath(_modelFolder, "CT_Rec");
					_modelConfig.recogizerCharDictionaryPath = CreateFilePath(_modelFolder, "dict_ct.txt");
					_modelConfig.clsModelDir = CreateFilePath(_modelFolder, "CH_CLS");
					_modelConfig.layoutModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.layourDictionaryPath = CreateFilePath(_modelFolder, "");
					_modelConfig.tableModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.tableCharDictionaryPath = CreateFilePath(_modelFolder, "");
					break;
				}
				default: {
					_modelConfig.detectionModelDir = CreateFilePath(_modelFolder, "MPPv3_Det");
					_modelConfig.recognizerModelDir = CreateFilePath(_modelFolder, "CT_Rec");
					_modelConfig.recogizerCharDictionaryPath = CreateFilePath(_modelFolder, "dict_ct.txt");
					_modelConfig.clsModelDir = CreateFilePath(_modelFolder, "CH_CLS");
					_modelConfig.layoutModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.layourDictionaryPath = CreateFilePath(_modelFolder, "");
					_modelConfig.tableModelDir = CreateFilePath(_modelFolder, "");
					_modelConfig.tableCharDictionaryPath = CreateFilePath(_modelFolder, "");
					break;
				}
			}

			// For now we do have _modelConfig and _modelFolder
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSOCRBase::Initialize", e.what());
			return false;
		}
	}

	/// <summary>
	/// ANSOCR class
	/// </summary>
	bool ANSOCR::Initialize(std::string licenseKey, OCRModelConfig modelConfig, const std::string& modelZipFilePath, const std::string& modelZipPassword, int engineMode) {
		try 
		{
			bool result = ANSOCRBase::Initialize(licenseKey, modelConfig, modelZipFilePath, modelZipPassword,engineMode);
			if (!result) return false;

			// Add default values to modelConfig if required.
			_modelConfig.precisionType = "fp32";
			_modelConfig.gpuId = 0;
			_modelConfig.gpuMemory = 4000;
			_modelConfig.limitType = "max";
			_modelConfig.cpuThreads = 10;
			_modelConfig.useDilation = false;
			_modelConfig.useAngleCLS = false;

			_modelConfig.detectionScoreMode = "slow";

			if(_modelConfig.limitSideLen==0) _modelConfig.limitSideLen = 960;
			if (_modelConfig.detectionDBThreshold == 0) _modelConfig.detectionDBThreshold = 0.3;
			if (_modelConfig.detectionBoxThreshold == 0) _modelConfig.detectionBoxThreshold = 0.6;
			if (_modelConfig.detectionDBUnclipRatio == 0) _modelConfig.detectionDBUnclipRatio = 1.5;

			if (_modelConfig.clsThreshold == 0) _modelConfig.clsThreshold = 0.9;
			if (_modelConfig.clsBatchNumber == 0) _modelConfig.clsBatchNumber = 1;

			if (_modelConfig.recognizerBatchNum == 0) _modelConfig.recognizerBatchNum = 6;
			if (_modelConfig.recoginzerImageHeight == 0) _modelConfig.recoginzerImageHeight = 48;
			if (_modelConfig.recoginzerImageWidth == 0) _modelConfig.recoginzerImageWidth = 320;

			if (_modelConfig.layoutScoreThreshold == 0) _modelConfig.layoutScoreThreshold = 0.5;
			if (_modelConfig.layoutNMSThreshold == 0) _modelConfig.layoutNMSThreshold = 0.5;
			if (_modelConfig.tableBatchNum == 0) _modelConfig.tableBatchNum = 1;
			if (_modelConfig.cpuThreads == 0) _modelConfig.cpuThreads = 10;

			switch (_engineMode) 
			{
				case 0: {
					EngineType engineType = ANSLicenseHelper::CheckHardwareInformation();
					if (engineType == EngineType::NVIDIA_GPU) // NVIDIA CUDA
					{
						_modelConfig.userGPU = true;
					}
					else {
						_modelConfig.userGPU = false;
						_modelConfig.useTensorRT = false;
					}
					break;
				}
				
				case 1: {
					_modelConfig.userGPU = true;
					break;
				}
		
				case 2: {
					_modelConfig.userGPU = false;
					_modelConfig.useTensorRT = false;
					break;
				}
			}

			this->_logger.LogInfo("ANSOCR::Initialize. Model Dir",_modelConfig.detectionModelDir);

			if (_modelConfig.useDetector)
			{

				//this->detector_ = std::make_unique<DBDetector>();
				this->detector_.Initialize(_modelConfig.detectionModelDir,
					_modelConfig.userGPU,
					_modelConfig.gpuId,
					_modelConfig.gpuMemory,
					_modelConfig.cpuThreads,
					_modelConfig.enableMKLDNN,
					_modelConfig.limitType,
					_modelConfig.limitSideLen,
					_modelConfig.detectionDBThreshold,
					_modelConfig.detectionBoxThreshold,
					_modelConfig.detectionDBUnclipRatio,
					_modelConfig.detectionScoreMode,
					_modelConfig.useDilation,
					_modelConfig.useTensorRT,
					_modelConfig.precisionType);
			}
			if (_modelConfig.useCLS && _modelConfig.useAngleCLS)
			{
				//this->classifier_ = std::make_unique<Classifier>();
				this->classifier_.Initialize(_modelConfig.clsModelDir,
					_modelConfig.userGPU,
					_modelConfig.gpuId,
					_modelConfig.gpuMemory,
					_modelConfig.cpuThreads,
					_modelConfig.enableMKLDNN,
					_modelConfig.clsThreshold,
					_modelConfig.useTensorRT,
					_modelConfig.precisionType,
					_modelConfig.clsBatchNumber);
			}
			if (_modelConfig.useRecognizer)
			{
			/*	this->recognizer_ = std::make_unique <CRNNRecognizer>();*/
				this->recognizer_.Initialize(_modelConfig.recognizerModelDir,
					_modelConfig.userGPU,
					_modelConfig.gpuId,
					_modelConfig.gpuMemory,
					_modelConfig.cpuThreads,
					_modelConfig.enableMKLDNN,
					_modelConfig.recogizerCharDictionaryPath,
					_modelConfig.useTensorRT,
					_modelConfig.precisionType,
					_modelConfig.recognizerBatchNum,
					_modelConfig.recoginzerImageHeight,
					_modelConfig.recoginzerImageWidth);
			}
			if (_modelConfig.useLayout) {
				//this->layout_model_ = std::make_unique<StructureLayoutRecognizer>();
				this->layout_model_.Initialize(
					_modelConfig.layoutModelDir, _modelConfig.userGPU, _modelConfig.gpuId, _modelConfig.gpuMemory,
					_modelConfig.cpuThreads, _modelConfig.enableMKLDNN, _modelConfig.layourDictionaryPath,
					_modelConfig.useTensorRT, _modelConfig.precisionType, _modelConfig.layoutScoreThreshold,
					_modelConfig.layoutNMSThreshold);
			}
			if (_modelConfig.useTable) {
				//this->table_model_ = std::make_unique<StructureTableRecognizer>();
				this->table_model_.Initialize(
					_modelConfig.tableModelDir, _modelConfig.userGPU, _modelConfig.gpuId, _modelConfig.gpuMemory,
					_modelConfig.cpuThreads, _modelConfig.enableMKLDNN, _modelConfig.tableCharDictionaryPath,
					_modelConfig.useTensorRT, _modelConfig.precisionType, _modelConfig.tableBatchNum,
					_modelConfig.tableModelMaxLengh, _modelConfig.mergeNoSpanStructure);
			}
			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSOCR::Initialize", e.what());
			return false;
		}

	}
	std::vector<ANSCENTER::OCRObject> ANSOCR::RunInference(const cv::Mat& input) {
		std::vector<ANSCENTER::OCRObject> OCRObjects;
		OCRObjects.clear();
		try {
			std::vector<OCRPredictResult> res_ocr = OCRInference(input);
			if (res_ocr.size() > 0) {
				for (int n = 0; n < res_ocr.size(); n++) {
					cv::Point rook_points[4];
					for (int m = 0; m < res_ocr[n].box.size(); m++) {
						rook_points[m] = cv::Point(int(res_ocr[n].box[m][0]), int(res_ocr[n].box[m][1]));
					}
					ANSCENTER::OCRObject ocrObject;
					ocrObject.box.x = rook_points[0].x;
					ocrObject.box.y = rook_points[0].y;
					ocrObject.box.width = rook_points[1].x - rook_points[0].x;
					ocrObject.box.height = rook_points[2].y - rook_points[1].y;
					ocrObject.classId = 0;
					ocrObject.className = res_ocr[n].text;
					std::string extraInformation = "cls label:" +
						std::to_string(res_ocr[n].cls_label) +
						";" +
						"cls score:" + std::to_string(res_ocr[n].cls_score);
					ocrObject.extraInfo = extraInformation;
					// Add extra information for cls score  cls label
					OCRObjects.push_back(ocrObject);
				}
			}
			return OCRObjects;
		}

		catch (std::exception& e) {
			this->_logger.LogFatal("ANSOCR::RunInference", e.what());
			return OCRObjects;
		}
	}

	std::vector<std::vector<OCRPredictResult>> ANSOCR::OCRInference(std::vector<cv::Mat> img_list) {
		std::vector<std::vector<OCRPredictResult>> ocr_results;
		ocr_results.clear();
		try {
			if (!_modelConfig.useDetector)
			{
				std::vector<OCRPredictResult> ocr_result;
				ocr_result.resize(img_list.size());
				if (_modelConfig.useCLS)// && this->classifier_ != nullptr)
				{
					this->CLSInference(img_list, ocr_result);
					for (int i = 0; i < img_list.size(); i++)
					{
						if (ocr_result[i].cls_label % 2 == 1 &&
							ocr_result[i].cls_score > this->classifier_.cls_thresh)
						{
							cv::rotate(img_list[i], img_list[i], 1);
						}
					}
				}
				if (_modelConfig.useRecognizer)
				{
					this->RecognitionInference(img_list, ocr_result);
				}
				for (int i = 0; i < ocr_result.size(); ++i)
				{
					std::vector<OCRPredictResult> ocr_result_tmp;
					ocr_result_tmp.push_back(ocr_result[i]);
					ocr_results.push_back(ocr_result_tmp);
				}
			}
			else
			{
				for (int i = 0; i < img_list.size(); ++i)
				{
					std::vector<OCRPredictResult> ocr_result =
						this->OCRInference(img_list[i]);
					ocr_results.push_back(ocr_result);
				}
			}
			return ocr_results;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSOCR::Inference", e.what());
			return ocr_results;

		}
	}
	std::vector<OCRPredictResult> ANSOCR::OCRInference(cv::Mat img) {
		std::vector<OCRPredictResult> ocr_result;
		ocr_result.clear();
		try {
			std::vector<cv::Mat> img_list;
			// det
			if (_modelConfig.useDetector)
			{
				this->DetectionInference(img, ocr_result);
				for (int j = 0; j < ocr_result.size(); j++)
				{
					cv::Mat crop_img;
					crop_img = ANSOCRUtility::GetRotateCropImage(img, ocr_result[j].box);
					img_list.push_back(crop_img);
				}
			}
			else
			{
				std::vector<std::vector<int>> box = { {0, 0}, {img.cols - 1, 0}, {img.cols - 1, img.rows - 1}, {0, img.rows - 1} };
				OCRPredictResult res;
				res.box = box;
				ocr_result.push_back(res);
				img_list.push_back(img);
			}
			// cls
			if (_modelConfig.useCLS)//&& this->classifier_ != nullptr)
			{
				this->CLSInference(img_list, ocr_result);
				for (int i = 0; i < img_list.size(); i++)
				{
					if (ocr_result[i].cls_label % 2 == 1 &&
						ocr_result[i].cls_score > this->classifier_.cls_thresh)
					{
						cv::rotate(img_list[i], img_list[i], 1);
					}
				}
			}
			// rec
			if (_modelConfig.useRecognizer)
			{
				this->RecognitionInference(img_list, ocr_result);
			}
			return ocr_result;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSOCR::Inference", e.what());
			return ocr_result;

		}
	}
	void ANSOCR::DetectionInference(cv::Mat img, std::vector<OCRPredictResult>& ocr_results) {
		try {
			std::vector<std::vector<std::vector<int>>> boxes;
			this->detector_.Run(img, boxes);

			for (int i = 0; i < boxes.size(); i++)
			{
				OCRPredictResult res;
				res.box = boxes[i];
				ocr_results.push_back(res);
			}
			// sort boxes from top to bottom, from left to right
			ANSOCRUtility::sorted_boxes(ocr_results);
		
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("PPOCR::det", e.what());
		}
	}
	void ANSOCR::RecognitionInference(std::vector<cv::Mat> img_list, std::vector<OCRPredictResult>& ocr_results) {
		try {
			std::vector<std::string> rec_texts(img_list.size(), "");
			std::vector<float> rec_text_scores(img_list.size(), 0);
			this->recognizer_.Run(img_list, rec_texts, rec_text_scores);
			// output rec results
			for (int i = 0; i < rec_texts.size(); i++)
			{
				std::cout << "Debug rec: " << rec_texts[i] << std::endl;
				ocr_results[i].text = rec_texts[i];
				ocr_results[i].score = rec_text_scores[i];
			}


		}
		catch (std::exception& e) {
			this->_logger.LogFatal("PPOCR::rec", e.what());
		}
	}
	void ANSOCR::CLSInference(std::vector<cv::Mat> img_list, std::vector<OCRPredictResult>& ocr_results) {
		try {
			std::vector<int> cls_labels(img_list.size(), 0);
			std::vector<float> cls_scores(img_list.size(), 0);
			this->classifier_.Run(img_list, cls_labels, cls_scores);
			// output cls results
			for (int i = 0; i < cls_labels.size(); i++)
			{
				ocr_results[i].cls_label = cls_labels[i];
				ocr_results[i].cls_score = cls_scores[i];
			}
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("PPOCR::cls", e.what());
		}
	}
	std::vector<StructurePredictResult> ANSOCR::StructureInference(cv::Mat srcimg, bool ocr) {
		cv::Mat img;
		srcimg.copyTo(img);

		std::vector<StructurePredictResult> structure_results;

		if (_modelConfig.useLayout) {
			this->LayoutInference(img, structure_results);
		}
		else {
			StructurePredictResult res;
			res.type = "table";
			res.box = std::vector<float>(4, 0.0);
			res.box[2] = img.cols;
			res.box[3] = img.rows;
			structure_results.push_back(res);
		}
		cv::Mat roi_img;
		for (int i = 0; i < structure_results.size(); i++) {
			// crop image
			roi_img = ANSOCRUtility::crop_image(img, structure_results[i].box);
			if (structure_results[i].type == "table" && _modelConfig.useTable) {
				this->TableInference(roi_img, structure_results[i]);
			}
			else if (ocr) {
				structure_results[i].text_res = this->OCRInference(roi_img);
			}
		}

		return structure_results;
	};
	void ANSOCR::LayoutInference(cv::Mat img, std::vector<StructurePredictResult>& structure_result) {
		this->layout_model_.Run(img, structure_result);
	}
	void ANSOCR::TableInference(cv::Mat img, StructurePredictResult& structure_result) {
		// predict structure
		std::vector<std::vector<std::string>> structure_html_tags;
		std::vector<float> structure_scores(1, 0);
		std::vector<std::vector<std::vector<int>>> structure_boxes;
		std::vector<cv::Mat> img_list;
		img_list.push_back(img);

		this->table_model_.Run(img_list, structure_html_tags, structure_scores,structure_boxes);
		std::vector<OCRPredictResult> ocr_result;
		std::string html;
		int expand_pixel = 3;

		for (int i = 0; i < img_list.size(); i++) {
			// det
			this->DetectionInference(img_list[i], ocr_result);
			// crop image
			std::vector<cv::Mat> rec_img_list;
			std::vector<int> ocr_box;
			for (int j = 0; j < ocr_result.size(); j++) {
				ocr_box = ANSOCRUtility::xyxyxyxy2xyxy(ocr_result[j].box);
				ocr_box[0] = std::max(0, ocr_box[0] - expand_pixel);
				ocr_box[1] = std::max(0, ocr_box[1] - expand_pixel),
					ocr_box[2] = std::min(img_list[i].cols, ocr_box[2] + expand_pixel);
				ocr_box[3] = std::min(img_list[i].rows, ocr_box[3] + expand_pixel);

				cv::Mat crop_img = ANSOCRUtility::crop_image(img_list[i], ocr_box);
				rec_img_list.push_back(crop_img);
			}
			// rec
			this->RecognitionInference(rec_img_list, ocr_result);
			// rebuild table
			html = this->RebuildTable(structure_html_tags[i], structure_boxes[i],
				ocr_result);
			structure_result.html = html;
			structure_result.cell_box = structure_boxes[i];
			structure_result.html_score = structure_scores[i];	
		}

	}
	float ANSOCR::Dis(std::vector<int>& box1, std::vector<int>& box2) {
		int x1_1 = box1[0];
		int y1_1 = box1[1];
		int x2_1 = box1[2];
		int y2_1 = box1[3];

		int x1_2 = box2[0];
		int y1_2 = box2[1];
		int x2_2 = box2[2];
		int y2_2 = box2[3];

		float dis =
			abs(x1_2 - x1_1) + abs(y1_2 - y1_1) + abs(x2_2 - x2_1) + abs(y2_2 - y2_1);
		float dis_2 = abs(x1_2 - x1_1) + abs(y1_2 - y1_1);
		float dis_3 = abs(x2_2 - x2_1) + abs(y2_2 - y2_1);
		return dis + std::min(dis_2, dis_3);
	}
	bool ANSOCR::ComparisonDis(const std::vector<float>& dis1, const std::vector<float>& dis2) {

		if (dis1[1] < dis2[1])
		{
			return true;
		}
		else if (dis1[1] == dis2[1])
		{
			return dis1[0] < dis2[0];
		}
		else
		{
			return false;
		}
	}
	std::string ANSOCR::RebuildTable(std::vector<std::string> structure_html_tags, std::vector<std::vector<int>> structure_boxes, std::vector<OCRPredictResult>& ocr_result) {
		// match text in same cell
		std::vector<std::vector<std::string>> matched(structure_boxes.size(),
			std::vector<std::string>());

		std::vector<int> ocr_box;
		std::vector<int> structure_box;
		for (int i = 0; i < ocr_result.size(); i++) {
			ocr_box = ANSOCRUtility::xyxyxyxy2xyxy(ocr_result[i].box);
			ocr_box[0] -= 1;
			ocr_box[1] -= 1;
			ocr_box[2] += 1;
			ocr_box[3] += 1;
			std::vector<std::vector<float>> dis_list(structure_boxes.size(),
				std::vector<float>(3, 100000.0));
			for (int j = 0; j < structure_boxes.size(); j++) {
				if (structure_boxes[i].size() == 8) {
					structure_box = ANSOCRUtility::xyxyxyxy2xyxy(structure_boxes[j]);
				}
				else {
					structure_box = structure_boxes[j];
				}
				dis_list[j][0] = this->Dis(ocr_box, structure_box);
				dis_list[j][1] = 1 - ANSOCRUtility::iou(ocr_box, structure_box);
				dis_list[j][2] = j;
			}
			// find min dis idx
			std::sort(dis_list.begin(), dis_list.end(),
				ANSOCR::ComparisonDis);
			matched[dis_list[0][2]].push_back(ocr_result[i].text);
		}

		// get pred html
		std::string html_str = "";
		int td_tag_idx = 0;
		for (int i = 0; i < structure_html_tags.size(); i++) {
			if (structure_html_tags[i].find("</td>") != std::string::npos) {
				if (structure_html_tags[i].find("<td></td>") != std::string::npos) {
					html_str += "<td>";
				}
				if (matched[td_tag_idx].size() > 0) {
					bool b_with = false;
					if (matched[td_tag_idx][0].find("<b>") != std::string::npos &&
						matched[td_tag_idx].size() > 1) {
						b_with = true;
						html_str += "<b>";
					}
					for (int j = 0; j < matched[td_tag_idx].size(); j++) {
						std::string content = matched[td_tag_idx][j];
						if (matched[td_tag_idx].size() > 1) {
							// remove blank, <b> and </b>
							if (content.length() > 0 && content.at(0) == ' ') {
								content = content.substr(0);
							}
							if (content.length() > 2 && content.substr(0, 3) == "<b>") {
								content = content.substr(3);
							}
							if (content.length() > 4 &&
								content.substr(content.length() - 4) == "</b>") {
								content = content.substr(0, content.length() - 4);
							}
							if (content.empty()) {
								continue;
							}
							// add blank
							if (j != matched[td_tag_idx].size() - 1 &&
								content.at(content.length() - 1) != ' ') {
								content += ' ';
							}
						}
						html_str += content;
					}
					if (b_with) {
						html_str += "</b>";
					}
				}
				if (structure_html_tags[i].find("<td></td>") != std::string::npos) {
					html_str += "</td>";
				}
				else {
					html_str += structure_html_tags[i];
				}
				td_tag_idx += 1;
			}
			else {
				html_str += structure_html_tags[i];
			}
		}
		return html_str;
	}

	


	/////
	///
	bool ANSONNXOCR::Initialize(std::string licenseKey, OCRModelConfig modelConfig, const std::string& modelZipFilePath, const std::string& modelZipPassword, int engineMode) {
		try
		{
			//bool result = ANSOCRBase::Initialize(licenseKey, modelConfig, modelZipFilePath, modelZipPassword, engineMode);
			//if (!result) return false;
			modelsDir = "C:\\Projects\\Research\\OCR\\BaiPiaoOCR\\cpp\\BaiPiaoOcrOnnx\\build\\Release";
			modelDetPath = "C:\\Projects\\Research\\OCR\\BaiPiaoOCR\\cpp\\BaiPiaoOcrOnnx\\build\\Release\\EN_DET.onnx";
			modelClsPath = "C:\\Projects\\Research\\OCR\\BaiPiaoOCR\\cpp\\BaiPiaoOcrOnnx\\build\\Release\\CH_CLS.onnx";
			modelRecPath = "C:\\Projects\\Research\\OCR\\BaiPiaoOCR\\cpp\\BaiPiaoOcrOnnx\\build\\Release\\EN_REC.onnx";
			imgPath = "C:\\Projects\\Research\\OCR\\BaiPiaoOCR\\cpp\\BaiPiaoOcrOnnx\\build\\Release\\ocrsample.png";
			keysPath = "C:\\Projects\\Research\\OCR\\BaiPiaoOCR\\cpp\\BaiPiaoOcrOnnx\\build\\Release\\dict_en.txt";
			imgName = "ocrsample";
			omp_set_num_threads(numThread);
			OcrLite ocrLite;
			ocrLite.setNumThread(numThread);
			ocrLite.initLogger(
				true,//isOutputConsole
				false,//isOutputPartImg
				true);//isOutputResultImg

			ocrLite.enableResultTxt(imgPath.c_str(), imgName.c_str());
			ocrLite.Logger(
				"numThread(%d),padding(%d),maxSideLen(%d),boxScoreThresh(%f),boxThresh(%f),unClipRatio(%f),doAngle(%d),mostAngle(%d)\n",
				numThread, padding, maxSideLen, boxScoreThresh, boxThresh, unClipRatio, doAngle, mostAngle);
			ocrLite.initModels(modelDetPath, modelClsPath, modelRecPath, keysPath);

			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSOCR::Initialize", e.what());
			return false;
		}

	}
	std::vector<ANSCENTER::OCRObject> ANSONNXOCR::RunInference(const cv::Mat& input) {
		std::vector<ANSCENTER::OCRObject> OCRObjects;
		OCRObjects.clear();
		try {

			OcrResult result = ocrLite.detect(imgPath.c_str(), imgName.c_str(), padding, maxSideLen,
				boxScoreThresh, boxThresh, unClipRatio, doAngle, mostAngle);
			ocrLite.Logger("%s\n", result.strRes.c_str());

			return OCRObjects;
		}

		catch (std::exception& e) {
			this->_logger.LogFatal("ANSOCR::RunInference", e.what());
			return OCRObjects;
		}
	}


	bool ANSFDOCR::Initialize(std::string licenseKey, OCRModelConfig modelConfig, const std::string& modelZipFilePath, const std::string& modelZipPassword, int engineMode) {
		try
		{
			


			return true;
		}
		catch (std::exception& e) {
			this->_logger.LogFatal("ANSOCR::Initialize", e.what());
			return false;
		}

	}
	std::vector<ANSCENTER::OCRObject> ANSFDOCR::RunInference(const cv::Mat& input) {
		std::vector<ANSCENTER::OCRObject> OCRObjects;
		OCRObjects.clear();
		try {

			auto option = fastdeploy::RuntimeOption();
			option.UseGpu();
			option.UseOrtBackend();

			std::string det_model_dir = "C:\\Users\\Neo\\AppData\\Local\\Temp\\Models\\EngineModels\\ansocrmodels\\EN_Det";
			std::string cls_model_dir = "C:\\Users\\Neo\\AppData\\Local\\Temp\\Models\\EngineModels\\ansocrmodels\\CH_CLS";
			std::string rec_model_dir = "C:\\Users\\Neo\\AppData\\Local\\Temp\\Models\\EngineModels\\ansocrmodels\\ENv4_Rec";
			std::string rec_label_file = "C:\\Users\\Neo\\AppData\\Local\\Temp\\Models\\EngineModels\\ansocrmodels\\dict_en.txt";
			std::string test_image = "C:\\Projects\\AICamera\\Documentation\\TestOCR\\ocrsample.png";

			auto det_model_file = det_model_dir + sep + "inference.pdmodel";
			auto det_params_file = det_model_dir + sep + "inference.pdiparams";

			auto cls_model_file = cls_model_dir + sep + "inference.pdmodel";
			auto cls_params_file = cls_model_dir + sep + "inference.pdiparams";

			auto rec_model_file = rec_model_dir + sep + "inference.pdmodel";
			auto rec_params_file = rec_model_dir + sep + "inference.pdiparams";

			auto det_option = option;
			auto cls_option = option;
			auto rec_option = option;

			// The cls and rec model can inference a batch of images now.
			// User could initialize the inference batch size and set them after create
			// PP-OCR model.
			int cls_batch_size = 1;
			int rec_batch_size = 6;

			// If use TRT backend, the dynamic shape will be set as follow.
			// We recommend that users set the length and height of the detection model to
			// a multiple of 32.
			// We also recommend that users set the Trt input shape as follow.
			det_option.SetTrtInputShape("x", { 1, 3, 64, 64 }, { 1, 3, 640, 640 },
				{ 1, 3, 960, 960 });
			cls_option.SetTrtInputShape("x", { 1, 3, 48, 10 }, { cls_batch_size, 3, 48, 320 },
				{ cls_batch_size, 3, 48, 1024 });
			rec_option.SetTrtInputShape("x", { 1, 3, 48, 10 }, { rec_batch_size, 3, 48, 320 },
				{ rec_batch_size, 3, 48, 2304 });

			// Users could save TRT cache file to disk as follow.
			// det_option.SetTrtCacheFile(det_model_dir + sep + "det_trt_cache.trt");
			// cls_option.SetTrtCacheFile(cls_model_dir + sep + "cls_trt_cache.trt");
			// rec_option.SetTrtCacheFile(rec_model_dir + sep + "rec_trt_cache.trt");

			auto det_model = fastdeploy::vision::ocr::DBDetector(
				det_model_file, det_params_file, det_option);
			auto cls_model = fastdeploy::vision::ocr::Classifier(
				cls_model_file, cls_params_file, cls_option);
			auto rec_model = fastdeploy::vision::ocr::Recognizer(
				rec_model_file, rec_params_file, rec_label_file, rec_option);

			assert(det_model.Initialized());
			assert(cls_model.Initialized());
			assert(rec_model.Initialized());

			// Parameters settings for pre and post processing of Det/Cls/Rec Models.
			// All parameters are set to default values.
			det_model.GetPreprocessor().SetMaxSideLen(960);
			det_model.GetPostprocessor().SetDetDBThresh(0.3);
			det_model.GetPostprocessor().SetDetDBBoxThresh(0.6);
			det_model.GetPostprocessor().SetDetDBUnclipRatio(1.5);
			det_model.GetPostprocessor().SetDetDBScoreMode("slow");
			det_model.GetPostprocessor().SetUseDilation(0);
			cls_model.GetPostprocessor().SetClsThresh(0.9);

			// The classification model is optional, so the PP-OCR can also be connected
			// in series as follows
			auto ppocr_v3 = fastdeploy::pipeline::PPOCRv3(&det_model, &rec_model);
			//auto ppocr_v3 =
			//	fastdeploy::pipeline::PPOCRv3(&det_model, &cls_model, &rec_model);

			// Set inference batch size for cls model and rec model, the value could be -1
			// and 1 to positive infinity.
			// When inference batch size is set to -1, it means that the inference batch
			// size
			// of the cls and rec models will be the same as the number of boxes detected
			// by the det model.
			ppocr_v3.SetClsBatchSize(cls_batch_size);
			ppocr_v3.SetRecBatchSize(rec_batch_size);

			if (!ppocr_v3.Initialized()) {
				std::cerr << "Failed to initialize PP-OCR." << std::endl;
				return OCRObjects;
			}

			auto im = cv::imread(test_image);
			auto im_bak = im.clone();

			fastdeploy::vision::OCRResult result;
			if (!ppocr_v3.Predict(&im, &result)) {
				std::cerr << "Failed to predict." << std::endl;
			}

			std::cout << result.Str() << std::endl;

			auto vis_im = fastdeploy::vision::VisOcr(im_bak, result);
			cv::imwrite("C:\\Projects\\AICamera\\Documentation\\TestOCR\\vis_result.jpg", vis_im);
			//std::cout << "Visualized result saved in ./vis_result.jpg" << std::endl;
			vis_im.release();
			im.release();
			im_bak.release();
			det_model.ReleaseReusedBuffer();
			cls_model.ReleaseReusedBuffer();
			rec_model.ReleaseReusedBuffer();
			ppocr_v3.ReleaseReusedBuffer();
			return OCRObjects;
		}

		catch (std::exception& e) {
			this->_logger.LogFatal("ANSOCR::RunInference", e.what());
			return OCRObjects;
		}
	}



};


extern "C" ANSOCR_API int		CreateANSOCRHandle(ANSCENTER::ANSOCRBase * *Handle, 
												   const char* licenseKey, 
												   const char* modelFilePath, 
												   const char* modelFileZipPassword, 
												   int language, int useDetector, int useRecognizer, 
												   int useCLS, int useLayout, int useTable, 
												   int engineMode, int useTensorRT, int useMKLDNN) {
	(*Handle) = new ANSCENTER::ANSOCR();
	if (*Handle == NULL) return 0;
	else {
		ANSCENTER::OCRModelConfig modelConfig;

	/*	enum OCRLanguage {
			ENGLISH = 0,
			CHINESE = 1,
			FRENCH = 2,
			GERMANY = 3,
			JAPANESE = 4,
			KOREAN = 5,
			CUSTOM = 6
		};*/
		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 = false;
		modelConfig.useRecognizer = false;
		modelConfig.useCLS = false;
		modelConfig.useLayout = false;
		modelConfig.useTable = false;
		modelConfig.useTensorRT = false;
		modelConfig.enableMKLDNN = false;

		if (useDetector == 1)modelConfig.useDetector = true;
		if (useRecognizer == 1)modelConfig.useRecognizer = true;
		if (useCLS == 1)modelConfig.useCLS = true;
		if (useLayout == 1)modelConfig.useLayout = true;
		if (useTable == 1)modelConfig.useTable = true;
		if (useTensorRT == 1)modelConfig.useTensorRT = true;
		if (useMKLDNN == 1)modelConfig.enableMKLDNN = true;

		int result = (*Handle)->Initialize(licenseKey, modelConfig,modelFilePath, modelFileZipPassword, engineMode);
		return result;
	}
}
extern "C" ANSOCR_API std::string  RunInference(ANSCENTER::ANSOCRBase * *Handle, unsigned char* jpeg_string, unsigned int bufferLength) {
	cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_UNCHANGED);
	if (frame.empty()) {
		std::string result = "";
		result.clear();
		return result;
	}
	std::vector<ANSCENTER::OCRObject> outputs = (*Handle)->RunInference(frame);
	frame.release();
	return ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
}
extern "C" ANSOCR_API std::string  RunInferenceBinary(ANSCENTER::ANSOCRBase * *Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height) {
	cv::Mat frame = cv::Mat(height, width, CV_8UC3, jpeg_bytes).clone(); // make a copy
	if (frame.empty()) {
		std::string result = "";
		result.clear();
		return result;
	}
	std::vector<ANSCENTER::OCRObject> outputs = (*Handle)->RunInference(frame);
	frame.release();
	return ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
}
extern "C" ANSOCR_API int          ReleaseANSOCRHandle(ANSCENTER::ANSOCRBase * *Handle) {
	delete* Handle;
	if (*Handle == NULL) return 0;
	else return 1;
}

extern "C" ANSOCR_API std::string  RunInferenceImagePath(ANSCENTER::ANSOCRBase * *Handle, const char* imageFilePath) {
	std::string stImageFileName(imageFilePath);
	cv::Mat frame = cv::imread(stImageFileName, cv::ImreadModes::IMREAD_COLOR);
	if (frame.empty()) {
		std::string result = "";
		result.clear();
		return result;
	}
	std::vector<ANSCENTER::OCRObject> outputs = (*Handle)->RunInference(frame);
	frame.release();
	return ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs);
}
//// For LabVIEW API
extern "C" ANSOCR_API int          RunInference_LV(ANSCENTER::ANSOCRBase * *Handle, unsigned char* jpeg_string, unsigned int bufferLength, LStrHandle detectionResult) {
	std::string st = RunInference(Handle, jpeg_string, bufferLength);
	if (st.empty()) return 0;
	int size = st.length();
	MgErr error;
	int32 i;
	error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
	if (error != mFullErr && error != mZoneErr)
	{
		(*detectionResult)->cnt = size;
		for (i = 0; i < size; i++)
			(*detectionResult)->str[i] = (uChar)(st.c_str()[i]);  //assign the st value
		return 1;
	}
	else return 0;
}

extern "C" ANSOCR_API int		   RunInferenceImagePath_LV(ANSCENTER::ANSOCRBase * *Handle, const char* imageFilePath, LStrHandle detectionResult) {
	std::string st = RunInferenceImagePath(Handle, imageFilePath);
	if (st.empty()) return 0;
	int size = st.length();
	MgErr error;
	int32 i;
	error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
	if (error != mFullErr && error != mZoneErr)
	{
		(*detectionResult)->cnt = size;
		for (i = 0; i < size; i++)
			(*detectionResult)->str[i] = (uChar)(st.c_str()[i]);  //assign the st value
		return 1;
	}
	else return 0;
}

extern "C" ANSOCR_API int ANSOCRUnitTest(const char* modelFilePath, const char* imageFilePath, LStrHandle detectionResult)
{
	ANSCENTER::ANSOCRBase* infHandle;
	std::string licenseKey = "";
	int language = 0;// English
	int createResult = CreateANSOCRHandle(&infHandle, licenseKey.c_str(), modelFilePath, "", language, 1, 1, 0, 0, 0, 0,1,1);
	std::string st = RunInferenceImagePath(&infHandle, imageFilePath);
	ReleaseANSOCRHandle(&infHandle);
	if (st.empty()) return 0;
	int size = st.length();
	MgErr error;
	int32 i;
	error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar));
	if (error != mFullErr && error != mZoneErr)
	{
		(*detectionResult)->cnt = size;
		for (i = 0; i < size; i++)
			(*detectionResult)->str[i] = (uChar)(st.c_str()[i]);  //assign the st value
		return 1;
	}
	else return 0;
}

extern "C" ANSOCR_API int  ANSONNXOCRUnitTest(const char* modelFilePath, const char* imageFilePath) {
	
	ANSCENTER::ANSONNXOCR myOCR;
	std::string licenseKey = "";
	std::string modelPath = "";
	std::string modelZip;
	ANSCENTER::OCRModelConfig modelConfig;
	int engineMode = 0;
	myOCR.Initialize(licenseKey, modelConfig, modelPath, modelZip, engineMode);
	std::string stImageFileName(imageFilePath);
	cv::Mat frame = cv::imread(stImageFileName, cv::ImreadModes::IMREAD_COLOR);
	myOCR.RunInference(frame);
	return 0;
}
extern "C" ANSOCR_API int		   ANSFDOCRUnitTest() {
	ANSCENTER::ANSFDOCR myOCR;
	cv::Mat frame;
	myOCR.RunInference(frame);
	return 0;
}
