//  Copyright (C) 2018-2022  SEIKO EPSON CORPORATION
//
//  License: LGPL-2.1+
//  Author : SEIKO EPSON CORPORATION
//
//  This file is part of the 'Epson Scan 2' package.
//	 This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.

//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//  
//  ESIMP2.cpp -- template and derived ESC/I ESC/I-2 protocol commands

#include "ESIMP2.hpp"
#include "ESIMP2Wrapper.h"
#include <utils/PathUtils.h>
#include "PaperDeskew.hpp"
#include "DeficiencyCorrection.hpp"
#include <math.h>
namespace epsonscan
{
    class MyESIMP2Wrapper
    {
        public:
            MyESIMP2Wrapper(std::string imp2Path) : fnCreatePtr_(nullptr), fnFreePtr_(nullptr), wrapper_(nullptr)
            {
                SDI_TRACE_LOG("Run constructor");
                esimp2_ = dlopen(imp2Path.c_str(), RTLD_LAZY);
                if(esimp2_ == nullptr)
                {
                    std::string error = dlerror();
                    throw RuntimeError("dtr2Lib is null");
                }
                fnCreatePtr_ = (IESIMP2WrapperCreatePtr)dlsym(esimp2_, "IESIMP2WrapperCreate");
                if (fnCreatePtr_ == nullptr)
                {
                    throw RuntimeError("fnCreatePtr is null");
                }
                fnFreePtr_ = (IESIMP2WrapperFreePtr)dlsym(esimp2_, "IESIMP2WrapperFree");
                if (fnFreePtr_ == nullptr)
                {
                    throw RuntimeError("fnFreePtr is null");
                }
                if (fnCreatePtr_(&wrapper_) != kESIMP2WrapperErrorNone)
                {
                    throw RuntimeError("IESIMP2Wrapper create fails");
                }
                if (wrapper_ == nullptr)
                {
                    throw RuntimeError("wrapper is nullptr");
                }
            }
            IESIMP2Wrapper* GetWrapper()
            {
                return wrapper_;
            }
            virtual ~MyESIMP2Wrapper()
            {
                SDI_TRACE_LOG("Run destrractor");
                if (wrapper_)
                {
                    fnFreePtr_(wrapper_);
                    wrapper_ = nullptr;
                }
                 if (esimp2_)
                 {
                   dlclose(esimp2_);
                   esimp2_ = nullptr;
                 }
            }
        private:
           void *esimp2_;
           IESIMP2WrapperCreatePtr fnCreatePtr_;
           IESIMP2WrapperFreePtr fnFreePtr_;
           IESIMP2Wrapper *wrapper_;
    };
    ESIMP2::ESIMP2(std::shared_ptr<KeyMgr> keyMgr,
                     std::shared_ptr<ModelInfo> modelInfo) : Filter(keyMgr, modelInfo), locationData_(nullptr)
    {
    }
    ESIMP2::~ESIMP2()
    {
        if (locationData_)
        {
             free(locationData_);
             locationData_ = nullptr;
        }
    }
    void ESIMP2::Detect(ESImageInfo& imageInfo, ES_CMN_FUNCS::BUFFER::CESHeapBuffer& inDataBuf)
    {
        AutoSize* autoSize =(AutoSize*)(keyMgr_->GetKeyInstance(kSDIAutoSizeKey).get());
        bool isCarrerSheetCrop = IsCarrerSheetCrop(imageInfo);
        if (autoSize->IsAdvancedSotwareAutoCropEnabled() || isCarrerSheetCrop)
        {
            if (IsESIMP2Available())
            {
                MyESIMP2Wrapper wrapper(GetESIMP2Path());
                ESIMP2WrapperCropParam param = {0};
                ESIMP2WrapperImageData inImage = {0};
                locationData_ = (ESIMP2WrapperLocationData*)malloc(sizeof(ESIMP2WrapperLocationData));
                inImage.width = ES_IMAGE_INFO::GetESImageWidth(imageInfo);
                inImage.height = ES_IMAGE_INFO::GetESImageHeight(imageInfo);
                inImage.samplesPerPixel= ES_IMAGE_INFO::GetESImageSamplesPerPixel(imageInfo);
                inImage.bitsPerSample = ES_IMAGE_INFO::GetESImageBitsPerSample(imageInfo);
                inImage.dataBuffer = inDataBuf.GetBufferPtr();
                inImage.resolutionX = ES_IMAGE_INFO::GetESImageResolutionX(imageInfo);
                inImage.resolutionY = ES_IMAGE_INFO::GetESImageResolutionY(imageInfo);
                param.modelID = modelInfo_->GetModelID();
                param.isCarrierSheet = isCarrerSheetCrop;
                wrapper.GetWrapper()->Detect(&param, &inImage, (ESIMP2WrapperLocationData*)locationData_);
                SDI_TRACE_LOG("Detect completed");
                if (keyMgr_->GetValueInt(kSDIFunctionalUnitKey) == kSDIFunctionalUnitDocumentFeeder)
                {
                       ESIMP2WrapperLocationData* locData = (ESIMP2WrapperLocationData*)locationData_;
                    if (keyMgr_->GetValueInt(kSDIAutoSizeKey) == kSDIAutoSizeNone)
                    {
                        double widthAsInch = (double)keyMgr_->GetValueInt(kSDIScanAreaWidthKey) / (double)keyMgr_->GetValueInt(kSDIResolutionKey);
                        double heightAsInch = (double)keyMgr_->GetValueInt(kSDIScanAreaHeightKey) / (double)keyMgr_->GetValueInt(kSDIResolutionKey);
                        double fixedSizeX = ES_IMAGE_INFO::GetESImageResolutionX(imageInfo) * widthAsInch;
                        double fixedSizeY = ES_IMAGE_INFO::GetESImageResolutionY(imageInfo) * heightAsInch;
                       double xDiff = fixedSizeX - locData->actImgW;
                       double yDiff = fixedSizeY - locData->actImgH;
                       locData->actImgH = locData->actImgH + yDiff;
                       locData->actImgW = locData->actImgW + xDiff;
                       locData->leftPoint = locData->leftPoint - xDiff / 2;
                        double radian = fabs(locData->theta * 0.0174533);
                        if (locData->xTangent > 0)
                        {
                            locData->locateW = locData->actImgW * cos(radian) + locData->actImgH * sin(radian);
                        }else{
                            locData->locateW = locData->actImgW * cos(radian) + locData->actImgH * sin(radian) + locData->xTangent;
                        }
                        if (locData->yTangent > 0)
                        {
                            locData->locateH = locData->actImgH * cos(radian) + locData->actImgW * sin(radian);
                        }else{
                            locData->locateH = locData->actImgH * cos(radian) + locData->actImgW * sin(radian) + locData->yTangent;
                        }
                    }
                     PaperDeskew* paperDeskew = (PaperDeskew*)keyMgr_->GetKeyInstance(kSDIPaperDeskewKey).get();
                     if (paperDeskew)
                     {
                        if (paperDeskew->IsAutoDeskewForWhiteBGEnabled() == false)
                        {
                            locData->theta = 0;
                            locData->xTangent = 0;
                            locData->yTangent = 0;
                            locData->actImgH = locData->locateH;
                            locData->actImgW = locData->locateW;
                        }
                     }
                }
            }
        }
    }
    bool ESIMP2::IsCarrerSheetCrop(ESImageInfo& imageInfo)
    {
        bool isHardWareCropEnabled = (keyMgr_->GetCommandValueAsBool(kESAutoCroppingInscribed) || keyMgr_->GetCommandValueAsBool(kESAutoCropping) );
        return isHardWareCropEnabled &&
                ES_IMAGE_INFO::GetESImageAutoCropped(imageInfo) == FALSE &&
                keyMgr_->GetValueInt(kSDIFunctionalUnitKey) == kSDIFunctionalUnitDocumentFeeder &&
                keyMgr_->GetValueInt(kSDIBackgroundColorKey) == kSDIBackgroundColorWhite;
    }
    void ESIMP2::Crop(ESImageInfo& imageInfo, ES_CMN_FUNCS::BUFFER::CESHeapBuffer& inDataBuf)
    {
        AutoSize* autoSize =(AutoSize*)(keyMgr_->GetKeyInstance(kSDIAutoSizeKey).get());
        bool isCarrerSheetCrop = IsCarrerSheetCrop(imageInfo);
        if (autoSize->IsAdvancedSotwareAutoCropEnabled() || isCarrerSheetCrop)
        {
            if (IsESIMP2Available() && locationData_)
            {
                MyESIMP2Wrapper wrapper(GetESIMP2Path());
                ESIMP2WrapperCropParam param = {0};
                ESIMP2WrapperImageData inImage = {0};
                ESIMP2WrapperImageData outImage = {0};
                ESString dtiPath;
                modelInfo_->GetDtiPath(dtiPath);
                SDI_TRACE_LOG("modelID = %d, dtiPath = %s", modelInfo_->GetModelID(), dtiPath.c_str());
                ESString esimp2Path = GetESIMP2Path();
                ESString pluginRoot = Filter::GetPluginRootDir();
                ESString dtrPath;
                ES_CMN_FUNCS::PATH::ES_CombinePath(dtrPath, pluginRoot, ES_STRING("libesdtr.so"));
                param.modelID = modelInfo_->GetModelID();
                strncpy(param.dtiParam, dtiPath.c_str(), ESIMP_MAX_PATH - 1);
                strncpy(param.esimp2Path, esimp2Path.c_str(), ESIMP_MAX_PATH - 1);
                strncpy(param.dtrPath, dtrPath.c_str(), ESIMP_MAX_PATH - 1);
                ESIMP2WrapperLocationData* locData = (ESIMP2WrapperLocationData*)locationData_;
                SDI_TRACE_LOG("locData->actImgW = %d", locData->actImgW);
                SDI_TRACE_LOG("locData->actImgH = %d", locData->actImgH);
                SDI_TRACE_LOG("locData->leftPoint = %d", locData->leftPoint);
                SDI_TRACE_LOG("locData->topPoint = %d", locData->topPoint);
                SDI_TRACE_LOG("locData->locateH = %d", locData->locateH);
                SDI_TRACE_LOG("locData->locateW = %d", locData->locateW);
                SDI_TRACE_LOG("locData->theta = %lf", locData->theta);
                inImage.width = ES_IMAGE_INFO::GetESImageWidth(imageInfo);
                inImage.height = ES_IMAGE_INFO::GetESImageHeight(imageInfo);
                inImage.samplesPerPixel= ES_IMAGE_INFO::GetESImageSamplesPerPixel(imageInfo);
                inImage.bitsPerSample = ES_IMAGE_INFO::GetESImageBitsPerSample(imageInfo);
                inImage.dataBuffer = inDataBuf.GetBufferPtr();
                inImage.resolutionX = ES_IMAGE_INFO::GetESImageResolutionX(imageInfo);
                inImage.resolutionY = ES_IMAGE_INFO::GetESImageResolutionY(imageInfo);
                double scaleX = (double)inImage.resolutionX / (double)locData->analysisResolutionX;
                double scaleY = (double)inImage.resolutionY / (double)locData->analysisResolutionY;
                locData->topPoint = locData->topPoint * scaleY;
                locData->leftPoint = locData->leftPoint * scaleX;
                locData->locateW = locData->locateW * scaleX;
                locData->locateH = locData->locateH * scaleY;
                locData->actImgW = locData->actImgW * scaleX;
                locData->actImgH = locData->actImgH * scaleY;
                locData->xTangent = locData->xTangent * scaleX;
                locData->yTangent = locData->yTangent * scaleY;
                outImage.width = locData->actImgW;
                outImage.height = locData->actImgH;
                outImage.samplesPerPixel= ES_IMAGE_INFO::GetESImageSamplesPerPixel(imageInfo);
                outImage.bitsPerSample = ES_IMAGE_INFO::GetESImageBitsPerSample(imageInfo);
                outImage.resolutionX = ES_IMAGE_INFO::GetESImageResolutionX(imageInfo);
                outImage.resolutionY = ES_IMAGE_INFO::GetESImageResolutionY(imageInfo);
                ES_CMN_FUNCS::BUFFER::CESHeapBuffer outBuffer;
                if(outBuffer.AllocBuffer(outImage.width * outImage.height * outImage.samplesPerPixel) == false) {
                    throw std::bad_alloc();
                }
                outImage.dataBuffer = outBuffer.GetBufferPtr();
                wrapper.GetWrapper()->Crop(&param, locData, &inImage, &outImage);
                free(locationData_);
                locationData_ = nullptr;
                imageInfo[ES_IMAGE_INFO::kESImageInfoWidthKey] = (ESNumber)outImage.width;
                imageInfo[ES_IMAGE_INFO::kESImageInfoHeightKey] = (ESNumber)outImage.height;
                inDataBuf.Attach(outBuffer);
                SDI_TRACE_LOG("Crop completed");
            }
        }
    }
    void ESIMP2::DoDeficiencyCorrection(ESImageInfo& imageInfo, ES_CMN_FUNCS::BUFFER::CESHeapBuffer& inDataBuf)
    {
         DeficiencyCorrection* deficiencyCorrection = (DeficiencyCorrection*)(keyMgr_->GetKeyInstance(kSDIDeficiencyCorrectionKey).get());
        if(deficiencyCorrection->IsProcessDeficiencyCorrection() && IsESIMP2Available())
        {
            MyESIMP2Wrapper wrapper(GetESIMP2Path());
            ESIMP2WrapperDeficiencyDetectRepairParam param = {0};
            ESDictionary bgLevel;
            if (ES_IMAGE_INFO::GetESImageSurfaceType(imageInfo) == ES_IMAGE_INFO::kESImageInfoSurfaceTypeFrontSide)
            {
                bgLevel = modelInfo_->GetBackGroundColorFront();
            }else{
                bgLevel = modelInfo_->GetBackGroundColorBack();
            }
            ESString imp2Path;
            if(modelInfo_->GetImp2Path(imp2Path))
            {
                param.pszParamDataPath = imp2Path.c_str();
            }
            param.bgCenterR = SAFE_ANY_VALUE_FROM_DICT(bgLevel, kESBGLevelKeyRed ,ESNumber,0);
            param.bgCenterG = SAFE_ANY_VALUE_FROM_DICT(bgLevel, kESBGLevelKeyGreen ,ESNumber,0);
            param.bgCenterB = SAFE_ANY_VALUE_FROM_DICT(bgLevel, kESBGLevelKeyBlue ,ESNumber,0);
            param.bgRangeR = SAFE_ANY_VALUE_FROM_DICT(bgLevel, kESBGLevelKeyRedWidth ,ESNumber,0);
            param.bgRangeG = SAFE_ANY_VALUE_FROM_DICT(bgLevel, kESBGLevelKeyGreenWidth ,ESNumber,0);
            param.bgRangeB = SAFE_ANY_VALUE_FROM_DICT(bgLevel, kESBGLevelKeyBlueWidth ,ESNumber,0);
            param.grayAdjustR = 1.01;
            param.grayAdjustG = 0.99;
            param.grayAdjustB = 0.99;
            param.isBackSide = (ES_IMAGE_INFO::GetESImageSurfaceType(imageInfo) == ES_IMAGE_INFO::kESImageInfoSurfaceTypeBackSide);
            if (keyMgr_->GetValueInt(kSDIRemovePunchholeKey) != 0 &&
                keyMgr_->GetValueInt(kSDIDeficiencyCorrectionKey) != 0)
            {
                param.mode = kESIMP2Wrappe_DeficiencyDetectRepairModeBoth;
            }else if (keyMgr_->GetValueInt(kSDIRemovePunchholeKey) == 0 &&
                      keyMgr_->GetValueInt(kSDIDeficiencyCorrectionKey) != 0)
            {
                param.mode = kESIMP2Wrappe_DeficiencyDetectRepairModeEdgeCorrect;
            }else if (keyMgr_->GetValueInt(kSDIRemovePunchholeKey) != 0 &&
                      keyMgr_->GetValueInt(kSDIDeficiencyCorrectionKey) == 0)
            {
                param.mode = kESIMP2Wrappe_DeficiencyDetectRepairModePunchHole;
            }
            ESIMP2WrapperImageData inImage = {0};
            inImage.width = ES_IMAGE_INFO::GetESImageWidth(imageInfo);
            inImage.height = ES_IMAGE_INFO::GetESImageHeight(imageInfo);
            inImage.samplesPerPixel= ES_IMAGE_INFO::GetESImageSamplesPerPixel(imageInfo);
            inImage.bitsPerSample = ES_IMAGE_INFO::GetESImageBitsPerSample(imageInfo);
            inImage.dataBuffer = inDataBuf.GetBufferPtr();
            inImage.resolutionX = ES_IMAGE_INFO::GetESImageResolutionX(imageInfo);
            inImage.resolutionY = ES_IMAGE_INFO::GetESImageResolutionY(imageInfo);
            wrapper.GetWrapper()->CorrectDeficienry(&inImage, &param);
        }
    }
    std::string ESIMP2::GetESIMP2Path()
    {
        std::string pluginRoot = Filter::GetPluginRootDir();
        ESString esimp2Path;
        ES_CMN_FUNCS::PATH::ES_CombinePath(esimp2Path, pluginRoot, ES_STRING("libesimp2.so"));
        return esimp2Path;
    }
    bool ESIMP2::IsESIMP2Available()
    {
        std::string pluginRoot = Filter::GetPluginRootDir();
        ESString esimp2Path = GetESIMP2Path();
        SDI_TRACE_LOG("libesimp2 = %s", esimp2Path.c_str());
        if (ES_CMN_FUNCS::PATH::ES_IsExistFile(esimp2Path.c_str())) {
                return true;
        }
        return false;
    }
}
