#include "OcrEngineYnd.h"
#include <DbgLog.h>
#include <wchar.h>

#define YDOCRLIB_REGKEY		TEXT("SOFTWARE\\EPSON\\EPSON Scan OCR")
#define YDOCRLIB_REGNAME	TEXT("InstallPath")
#define YDOCRLIB			TEXT("OCRSYS.DLL")

#define MIN(a,b)	((a) < (b) ? (a) : (b))


#pragma pack(push, 2)
    /*! Information about the type, size, and layout of a file that contains a DIB
     *
     *   More detailed description: http://msdn.microsoft.com/ja-jp/library/windows/desktop/dd183374(v=vs.85).aspx
     */
    typedef struct tagBITMAPFILEHEADER {
        WORD  bfType;
        DWORD bfSize;
        WORD  bfReserved1;
        WORD  bfReserved2;
        DWORD bfOffBits;
    } BITMAPFILEHEADER, *PBITMAPFILEHEADER;

	typedef struct tagBITMAPINFOHEADER{
        DWORD      biSize;
        int32_t       biWidth;
        int32_t       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        int32_t       biXPelsPerMeter;
        int32_t       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
} BITMAPINFOHEADER;

#pragma pack(pop)

#define SDI_TRACE_LOG(message, ...) \
        AfxGetLog()->MessageLog(ENUM_LOG_LEVEL::LogLevelTrace,__FUNCTION__,__FILE__,__LINE__, message, ##__VA_ARGS__ )


BOOL CALLBACK OCRCallback(WORD)
{
	return TRUE;
}

COcrEngineYnd::COcrEngineYnd(void) :
m_hYndLib(NULL),
m_pYndInit(NULL),
m_pYndEnd(NULL),
m_pYndSetImage(NULL),
m_pYndRecog(NULL),
m_pYndGetResultCount(NULL),
m_pYndGetResult(NULL),
m_ErrorCode(0)
{
	SDI_TRACE_LOG("COcrEngineYnd::COcrEngineYnd");
}

COcrEngineYnd::~COcrEngineYnd(void)
{
	SDI_TRACE_LOG("COcrEngineYnd::~COcrEngineYnd");

	if (m_pYndEnd) {
		m_pYndEnd();
	}

	if (m_hYndLib) {
		::FreeLibrary(m_hYndLib);
	}
}

DWORD COcrEngineYnd::init(eOCRLanguage OCRLang)
{
	SDI_TRACE_LOG("COcrEngineYnd::init");

	// Check OCR Language is supported
	WORD mode = 0; // Dummy parameter
	if (this->getLangMode(OCRLang, &mode)) {
		m_OCRLang = OCRLang;
	} else {
		SDI_TRACE_LOG("[ERROR]Invalid OCR Language\n");
		return ESL_ERR_INVALID_PARAM;
	}

	// Load OCR Engine
	if (!this->loadEngine()) {
		SDI_TRACE_LOG("[ERROR]loadEngine failed\n");
		return ESL_ERR_FILE_MISSING;
	}

	// Initialize OCR module
	BOOL ret = m_pYndInit(&m_ErrorCode);
	if (!ret) {
		SDI_TRACE_LOG("[ERROR]YndInit failed(%d)\n", m_ErrorCode);
		return ESL_ERR_INVALID_PARAM;
	}

	return ESL_SUCCESS;
}

DWORD COcrEngineYnd::exeRecognize(HANDLE hDIB, size_t size, RECT* pRect, ESStringUTF16& Result)
{
	SDI_TRACE_LOG("COcrEngineYnd::exeRecognize");

	// Get mode parameter
	WORD mode = 0;
	if (!this->getLangMode(m_OCRLang, &mode)) {
		SDI_TRACE_LOG("ESL_ERR_INVALID_PARAM");
		return ESL_ERR_INVALID_PARAM;
	}

	// Initialize Recognize parameter
	PRMINFO prm = {0};
	prm.rcTarget.left = 0;
	prm.rcTarget.top = 0;
	prm.rcTarget.right = 0xFFFF;
	prm.rcTarget.bottom = 0xFFFF;
	prm.wMode = mode;
	prm.wRgnKind = YND_RGN_CHAR;		// 文字領域（自動領域抽出をするので無意味） 
	prm.wStyle = YND_STL_AUTO;			// 縦横自動判別 
	prm.wDangumi = YND_DANGUMI_ON;		// 段組みなし（自動領域抽出をするので無意味） 
	prm.wKasure = YND_KSR_OFF;			// かすれ補正なし 
	prm.wWdchk = YND_WDC_ON;			// 単語辞書使用 
	prm.wChrKind = YND_CHR_ALL;			// すべての文字種を使用 
	prm.wChrPart = YND_CHRPT_ALL;		// 同上（wChrKind = YND_CHR_ALLなので無意味） 
	prm.wCreturn = YND_CR_LINE;			// 改行は行単位で 
	prm.wDelimit = YND_DLM_TAB;			// 表の場合の区切り文字はタブ 
	prm.wSpaceChar = YND_SPCHR_ON;		// スペース文字を入れる 
	prm.wSpaceLine = YND_SPLINE_ON;		// スペース行を入れる 
	prm.wHlfSymbol = YND_HLFSYMBOL_OFF;	// 記号は全角で出力 
	prm.wHlfAlpha = YND_HLFALPHA_ON;	// アルファベットは半角で出力 
	prm.wHlfNum = YND_HLFNUM_ON;		// 数字は半角で出力 
	prm.wHlfKata = YND_HLFKATA_OFF;		// カタカナは全角で出力 


	// Update RECT if provided
	if (pRect) {
		if (
			pRect->left >= pRect->right ||
			pRect->top >= pRect->bottom ||
			pRect->left < 0 			||
			pRect->top < 0 				||
			pRect->right > 0xFFFF 		||
			pRect->bottom > 0xFFFF
		){
			SDI_TRACE_LOG("pRect ESL_ERR_INVALID_PARAM");
			return ESL_ERR_INVALID_PARAM;
		}
		prm.rcTarget.left = pRect->left;
		prm.rcTarget.top = pRect->top;
		prm.rcTarget.right = pRect->right;
		prm.rcTarget.bottom = pRect->bottom;
	}

	SDI_TRACE_LOG("prm.rcTarget.left = %d\n", prm.rcTarget.left);
	SDI_TRACE_LOG("prm.rcTarget.top = %d\n", prm.rcTarget.top);
	SDI_TRACE_LOG("prm.rcTarget.right = %d\n", prm.rcTarget.right);
	SDI_TRACE_LOG("prm.rcTarget.bottom = %d\n", prm.rcTarget.bottom);


	// Execute Recognize
	if (this->setImage(hDIB, size, &prm)) {
		BOOL ret = m_pYndRecog(1, &prm, YND_ANALYSISPRIORITY_SPEED, OCRCallback, &m_ErrorCode);
		if (ret) {
			WORD count = m_pYndGetResultCount(&m_ErrorCode);
			SDI_TRACE_LOG("[INFO] count = %d\n", count);

			if (count <= 0) {
				SDI_TRACE_LOG("[INFO]No characters are detected\n");
				return ESL_SUCCESS;
			}
			// Check detected characters
			HANDLE hResult = m_pYndGetResult(YND_CODE_UNICODE, &m_ErrorCode);
			if (hResult) {
				RSTCHAREX* pChar = (RSTCHAREX*)hResult;
				if (pChar) {
					for (WORD i = 0; i < count; i++) {
						if (pChar[i].wChar)
						{
							//printf("%x ", pChar[i].wChar);
							//uint16_t swapped = (pChar[i].wChar >> 8) | (pChar[i].wChar << 8);
							Result = Result.append(1, pChar[i].wChar);
						}
					}
				} else {
					SDI_TRACE_LOG("[ERROR]GlobalLock failed(Result)\n");
					return ESL_ERR_LOW_MEMORY;
				}
			} else {
				SDI_TRACE_LOG("[ERROR]m_pYndGetResult failed(%d)\n", m_ErrorCode);
				return ESL_ERR_INVALID_PARAM;
			}
		} else {
			SDI_TRACE_LOG("[ERROR]m_pYndRecog failed(%d)\n", m_ErrorCode);
			return ESL_ERR_INVALID_PARAM;
		}
	} else {
		return ESL_ERR_INVALID_PARAM;
	}

	return ESL_SUCCESS;
}

bool COcrEngineYnd::getLangMode(eOCRLanguage OCRLang, WORD* pMode)
{
	if (pMode == NULL) {
		return false;
	}

	switch (OCRLang) {
	case OL_EN:	// English
		*pMode = YND_LNG_1BYTE_EN | YND_MOD_1BYTE;
		break;
	/*case OL_JA:	// Japanese
		*pMode = YND_LNG_2BYTE_JP | YND_MOD_2BYTE | YND_LNG_1BYTE_EN;
		break;
	case OL_ZH_TW:	// Traditional Chinese
		*pMode = YND_LNG_2BYTE_ZHT | YND_MOD_2BYTE | YND_LNG_1BYTE_EN;
		break;*/
	case OL_ZH_CN:	// Simplified Chinese
		*pMode = YND_LNG_2BYTE_ZHS | YND_MOD_2BYTE | YND_LNG_1BYTE_EN;
		break;
	/*case OL_KO:	// Korean
		*pMode = YND_LNG_2BYTE_KO | YND_MOD_2BYTE | YND_LNG_1BYTE_EN;
		break;
	case OL_FR:	// Frencrh
		*pMode = YND_LNG_1BYTE_FR | YND_MOD_1BYTE | YND_LNG_1BYTE_EN;
		break;
	case OL_IT:	// Italian
		*pMode = YND_LNG_1BYTE_IT | YND_MOD_1BYTE | YND_LNG_1BYTE_EN;
		break;
	case OL_DE:	// German
		*pMode = YND_LNG_1BYTE_DE | YND_MOD_1BYTE | YND_LNG_1BYTE_EN;
		break;
	case OL_ES:	// Spanish
		*pMode = YND_LNG_1BYTE_ES | YND_MOD_1BYTE | YND_LNG_1BYTE_EN;
		break;
	case OL_PT:	// Portuguese
		*pMode = YND_LNG_1BYTE_PT | YND_MOD_1BYTE | YND_LNG_1BYTE_EN;
		break;
	case OL_NL:	// Dutch
		*pMode = YND_LNG_1BYTE_NL | YND_MOD_1BYTE | YND_LNG_1BYTE_EN;
		break;
	case OL_RU:	// Russian
		*pMode = YND_LNG_1BYTE_RU | YND_MOD_1BYTE | YND_LNG_1BYTE_EN;
		break;
	case OL_UK:	// Ukrainian
		*pMode = YND_LNG_1BYTE_UK | YND_MOD_1BYTE | YND_LNG_1BYTE_EN;
		break;*/
	default:
		return false;
	}

	return true;
}

bool COcrEngineYnd::loadEngine(void)
{

	ESString libPath;

#ifdef __i386__ 
     libPath = "/usr/lib/i386-linux-gnu/epsonscan2-ocr/libocr-ocrsys.so";
#elif defined(__mips__)
     libPath = "/usr/lib/mips64el-linux-gnuabi64/epsonscan2-ocr/libocr-ocrsys.so";
#elif defined(__aarch64__)
    libPath = "/usr/lib/aarch64-linux-gnu/epsonscan2-ocr/libocr-ocrsys.so";
#elif defined(__loongarch__)
    libPath = "/usr/lib/loongarch64-linux-gnu/epsonscan2-ocr/libocr-ocrsys.so";
#else
    libPath = "/usr/lib/x86_64-linux-gnu/epsonscan2-ocr/libocr-ocrsys.so";
#endif   

	// Load library
	m_hYndLib = ::LoadLibrary(libPath.c_str());
	if (m_hYndLib == NULL) {
		return false;
	}

	// Get proc address
	m_pYndInit				= (YNDINIT)dlsym(m_hYndLib, "YndInit");
	m_pYndEnd				= (YNDEND)dlsym(m_hYndLib, "YndEnd");
	m_pYndSetImage			= (YNDSETIMAGE)dlsym(m_hYndLib, "YndSetImage");
	m_pYndRecog				= (YNDRECOG2)dlsym(m_hYndLib, "YndRecog2");
	m_pYndGetResultCount	= (YNDGETRESULTCOUNT)dlsym(m_hYndLib, "YndGetResultCount");
	m_pYndGetResult			= (YNDGETRESULTEX)dlsym(m_hYndLib, "YndGetResultEx");
	if ((m_pYndInit == NULL) || (m_pYndEnd == NULL)
		|| (m_pYndSetImage == NULL) 
		|| (m_pYndRecog == NULL) || (m_pYndGetResultCount == NULL) || (m_pYndGetResult == NULL)) {
		return false;
	}

	return true;
}

bool COcrEngineYnd::setImage(HANDLE hDIB, size_t size, PRMINFO *prm)
{

	// skip BITMAPFILEHEADER of bitmap file
   unsigned char * dibptr = (unsigned char *)hDIB + sizeof(BITMAPFILEHEADER);
   DWORD dibsize = size - sizeof(BITMAPFILEHEADER);

	SDI_TRACE_LOG("image dibsize (%d) hadersize = %d\n", dibsize, sizeof(BITMAPFILEHEADER));
	
	BITMAPINFOHEADER* infoHeader = (BITMAPINFOHEADER*)dibptr;

	//infoHeader->biXPelsPerMeter = (int)(((double)300 / (0.0254)) + 0.5);;
	//infoHeader->biYPelsPerMeter = (int)(((double)300 / (0.0254)) + 0.5);;

	// Clip so that the specified rectangle area does not exceed the image size.
	prm->rcTarget.right  = MIN(prm->rcTarget.right, infoHeader->biWidth);
    prm->rcTarget.bottom = MIN(prm->rcTarget.bottom, infoHeader->biHeight);

	SDI_TRACE_LOG("image sizeImage %d width = %d  height = %d  bitcount = %d resolutionX = %d  resolutionY = %d \n", infoHeader->biSizeImage, infoHeader->biWidth, infoHeader->biHeight, infoHeader->biBitCount, infoHeader->biXPelsPerMeter, infoHeader->biYPelsPerMeter);
	SDI_TRACE_LOG("prm->rcTarget.left = %d prm->rcTarget.top = %d prm->rcTarget.right = %d prm->rcTarget.bottom = %d \n", prm->rcTarget.left, prm->rcTarget.top, prm->rcTarget.right, prm->rcTarget.bottom);



	if (prm->rcTarget.right >= infoHeader->biWidth) {
		SDI_TRACE_LOG("prm->rcTarget.right same.");
		prm->rcTarget.right = infoHeader->biWidth - 1;
	}
	if (prm->rcTarget.bottom >= infoHeader->biHeight) {
		SDI_TRACE_LOG("prm->rcTarget.bottom same.");
		prm->rcTarget.bottom = infoHeader->biHeight - 1;
	}

	// Set image data to OCR module
	BOOL ret = m_pYndSetImage(dibptr, dibsize, NULL, &m_ErrorCode);
	if (ret) {
		return true;
	} else {
		SDI_TRACE_LOG("[ERROR]m_pYndSetImage failed(%d)\n", m_ErrorCode);
	}

	return false;
}
