#include <utils/ESFile.h>
#include "TransferMgr.hpp"
#include "JpegDecode.hpp"
#include "DTR.hpp"
#include "GrayToMono.hpp"
#include "ColorToGray.hpp"
#include "Lut.hpp"
#include "ProcOrientation.hpp"
#include "MonoToGray.hpp"
#include "Saving.hpp"
#include "Resize.hpp"
#include "GetOrientation.hpp"
#include "Error.hpp"
#include "Padding.hpp"
#include "DDEFilter.hpp"
#include "Brightness.hpp"
#include "Contrast.hpp"
#include "Gamma.hpp"
#include "SkipBlankPageOnOff.hpp"
#include "ESIMP2.hpp"
#include "Gamma10To18.hpp"
#include "EdgeFill.hpp"

namespace epsonscan 
{
	int Image::threadCount_ = 0;
	CritSec Image::m_ThreadCriticSec;

	bool Image::IsHardwareImageProcessSupported(std::shared_ptr<KeyMgr> keyMgr)
	{
		Brightness* brightness = (Brightness*)(keyMgr->GetKeyInstance(kSDIBrightnessKey).get());
		Contrast* contrast = (Contrast*)(keyMgr->GetKeyInstance(kSDIContrastKey).get());
		Gamma* gamma = (Gamma*)(keyMgr->GetKeyInstance(kSDIGammaKey).get());
		if(brightness || contrast || gamma)
		{
			if(brightness->IsSupportBrightnessCommand() || contrast->IsSupportContrastCommand() || gamma->IsSupportGammaCommand())
			{
				return true;
			}
		}

		return false;
	}



	 static inline double inchToPixel(double inch, double dpi)
    {
        return dpi * inch;
    }

    static void setBit (uint8_t* data, uint32_t bitIndex)
    {
        uint32_t octet = bitIndex / 8;
        uint8_t bit    = 7 - (bitIndex % 8);

        data[octet] |=  (1 << bit);
    }

    static void maskBit (uint8_t* data, uint32_t bitIndex)
    {
        uint32_t octet = bitIndex / 8;
        uint8_t bit    = 7 - (bitIndex % 8);

        data[octet] &= ~(1 << bit);
    }


    static void setBits (uint8_t* data, uint32_t startBit, uint32_t endBit, SDIEdgeFill mode)
    {
        if (mode == kSDIEdgeFillBlack) {
            for (uint32_t i = startBit; i < endBit; i++) {
                maskBit(data, i);
            }
        }else{
            for (uint32_t i = startBit; i < endBit; i++) {
                setBit(data, i);
            }
        }
    }

			void ProcessEdgeFill(ES_CMN_FUNCS::BUFFER::CESHeapBuffer& buffer,
                                         ESImageInfo& imageInfo,
                                         SDIEdgeFill edgeFillMode,
                                         ESNumber edgeFillVolumeTop, // inch x10
                                         ESNumber edgeFillVolumeLeft, // inch x10
                                         ESNumber edgeFillVolumeRight, // inch x10
                                         ESNumber edgeFillVolumeBottom // inch x10
                         )
			{
				ESNumber  height = ES_IMAGE_INFO::GetESImageHeight(imageInfo);
				ESNumber  width = ES_IMAGE_INFO::GetESImageWidth(imageInfo);
				ESNumber  bitsPerSample = ES_IMAGE_INFO::GetESImageBitsPerSample(imageInfo);
				ESNumber imageRowBytes      =  ES_IMAGE_INFO::GetESImageBytesPerRow(imageInfo);
				ESNumber  samplesPerPixel = ES_IMAGE_INFO::GetESImageSamplesPerPixel(imageInfo);
				ESNumber  imageResolutionX = ES_IMAGE_INFO::GetESImageResolutionX(imageInfo);
				ESNumber  imageResolutionY = ES_IMAGE_INFO::GetESImageResolutionY(imageInfo);
				ESNumber imageBitsPerPixel  =  ES_IMAGE_INFO::GetESImageSamplesPerPixel(imageInfo) * ES_IMAGE_INFO::GetESImageBitsPerSample(imageInfo);
				ESNumber imageBitsPerRow    =  imageBitsPerPixel * width;

				uint8_t* pInBytes = buffer.GetBufferPtr();

				ESNumber fillAreaTopPixel    = inchToPixel(edgeFillVolumeTop/1000.0 ,    imageResolutionY);
				ESNumber fillAreaBottomPixel = inchToPixel(edgeFillVolumeBottom/1000.0, imageResolutionY);
				ESNumber fillAreaLeftPixel   = inchToPixel(edgeFillVolumeLeft/1000.0,   imageResolutionX);
				ESNumber fillAreaRightPixel  = inchToPixel(edgeFillVolumeRight/1000.0,  imageResolutionX);

				ESNumber fillBitsLeft        = fillAreaLeftPixel * imageBitsPerPixel;
				ESNumber fillBitsRight       = fillAreaRightPixel * imageBitsPerPixel;

				{//check fill area
					if(imageResolutionX == 0 || imageResolutionY == 0) {
							return;
					}

					if (fillBitsRight + fillBitsLeft > imageBitsPerRow) {
						fillBitsLeft  = 0;
						fillBitsRight = imageBitsPerRow;
					}

					if (fillAreaTopPixel + fillAreaBottomPixel > ES_IMAGE_INFO::GetESImageHeight(imageInfo)) {
					fillAreaBottomPixel = 0;
					fillAreaTopPixel  =  ES_IMAGE_INFO::GetESImageHeight(imageInfo);
					}
				}
				{//do edge fill

					for (uint32_t i = 0; i < height; i++) {
						// fill top
						if (i < fillAreaTopPixel) {
							setBits(&pInBytes[imageRowBytes * i], 0, imageBitsPerRow, edgeFillMode);
						}

						// fill bottom
						if (ES_IMAGE_INFO::GetESImageHeight(imageInfo) - fillAreaBottomPixel <  i) {
							setBits(&pInBytes[imageRowBytes * i], 0, imageBitsPerRow, edgeFillMode);
						}

						// fill left
						setBits(&pInBytes[imageRowBytes * i], 0, fillBitsLeft, edgeFillMode);

						// fill right
						setBits(&pInBytes[imageRowBytes * i],  imageBitsPerRow - fillBitsRight, imageBitsPerRow, edgeFillMode);
					}
				}
			}

    static double MyMMtoInch(double mm)
	{
		return mm / 25.4;
	}
	SDIError Image::applyImageProc(std::shared_ptr<KeyMgr> keyMgr, std::shared_ptr<ModelInfo> modelInfo)
	{
			imageProcThread_.reset(new std::thread([this, keyMgr, modelInfo]
			{
				try	
				{
				WaitUntilTheradLimit(keyMgr->GetCommandValueAsNumber(kESXResolution));
				AddThreadCount();

				SDI_TRACE_LOG("applyImageProc start %d", ES_IMAGE_INFO::GetESImagePageCount(imageInfo_));

				imageInfo_[ES_IMAGE_INFO::kESImageInfoResolutionXKey]	 = (ESNumber)keyMgr->GetCommandValueAsNumber(kESXResolution);
				imageInfo_[ES_IMAGE_INFO::kESImageInfoResolutionYKey]	 = (ESNumber)keyMgr->GetCommandValueAsNumber(kESYResolution);
				imageInfo_[ES_IMAGE_INFO::kESImageInfoOutputResolutionKey] = (ESNumber)keyMgr->GetCommandValueAsNumber(kESXResolution);

				JpegDecode jpegDecode(keyMgr, modelInfo);
				ColorToGray colorToGray(keyMgr, modelInfo);
				GrayToMono	grayToMono(keyMgr, modelInfo);
				DTR dtr(keyMgr, modelInfo);
				Lut lut(keyMgr, modelInfo);
				MonoToGray monoToGray(keyMgr, modelInfo);
				Resize resize(keyMgr, modelInfo);
				ProcOrientation procOrientation(keyMgr, modelInfo);
				Saving saving(keyMgr, modelInfo);
				GetOrientation getOrientation(keyMgr, modelInfo);
				Padding padding(keyMgr, modelInfo);
				ESIMP2 esimp2(keyMgr, modelInfo);
				Gamma10To18 gamma10To18(keyMgr, modelInfo);
				DDEFilter ddeFilter(keyMgr, modelInfo);

			
				SDIAutoColorPixelType autoColorPixelType = kSDIAutoColorPixelTypeMono;

				bool autoJudgedColor = true;
				
				ESNumber whitePageSkipType = kPrvHRD_WhitePageSkipColorType_Gray;
				modelInfo->GetValue(kPrvHRD_WhitePageSkipColorType, whitePageSkipType);
	
				{
					jpegDecode.Process(imageInfo_, dataBuf_);
				}
				{
					padding.Process(imageInfo_, dataBuf_);
				}		
				{
					esimp2.Detect(imageInfo_, dataBuf_);
				}
				{
					gamma10To18.Process(imageInfo_, dataBuf_);
				}
				{	
					if (imageInfo_[ES_IMAGE_INFO::kESImageInfoUpsideDownKey])
					{
						procOrientation.SetOrientation(kSDIOrientation180);
						procOrientation.Process(imageInfo_, dataBuf_);
					}
				}
				{
					dtr.SetActionType(kActionTypeGetDocumentLocation);
					dtr.Process(imageInfo_, dataBuf_);
				}
				{
					resize.Process(imageInfo_, dataBuf_);
				}
				{
					esimp2.Crop(imageInfo_, dataBuf_);
				}
				{
					esimp2.DoDeficiencyCorrection(imageInfo_, dataBuf_);
				}
				{
					ColorType* colorTypeAuto = (ColorType*)(keyMgr->GetKeyInstance(kSDIColorTypeKey).get()); 
					if(colorTypeAuto && colorTypeAuto->IsSupportHardwareColorTypeAuto())
					{
						if(ES_IMAGE_INFO::GetESImageMonoPage(imageInfo_) && keyMgr->GetValueInt(kSDIColorTypeKey) == kSDIColorTypeAuto)
						{
							autoJudgedColor = false;
							autoColorPixelType = (SDIAutoColorPixelType)keyMgr->GetValueInt(kSDIAutoColorPixelTypeKey);
						}
					} else {
						dtr.SetActionType(kActionTypeJudgeColorType);
						dtr.Process(imageInfo_, dataBuf_);
						autoJudgedColor = dtr.IsColorPage();
						autoColorPixelType = dtr.GetAutoColorPixelType();
					}
				}
				{				
					dtr.SetActionType(kActionTypeRemovePunchHole);
					dtr.Process(imageInfo_, dataBuf_);
				}
				{				
					if (whitePageSkipType == kPrvHRD_WhitePageSkipColorType_Color)
					{
						SkipBlankPageOnOff* skipBlankPageOnOff = (SkipBlankPageOnOff*)(keyMgr->GetKeyInstance(kSDIBlankPageSkipKey).get());
						if(skipBlankPageOnOff && skipBlankPageOnOff->IsSupportHardwareBlankPageOnOff())
						{
							if(ES_IMAGE_INFO::GetESImageBlankPageSkip(imageInfo_))
							{
								isBlankPage_ = true;
								dataBuf_.FreeBuffer();
								SDI_TRACE_LOG("machine blank page found  %d", ES_IMAGE_INFO::GetESImagePageCount(imageInfo_));
								SubThreadCount();
								return;
							} else {
								isBlankPage_ = false;
							}
						} else {
							dtr.SetActionType(kActionTypeJudgeBlankPage);
							dtr.Process(imageInfo_, dataBuf_);
							isBlankPage_ = dtr.IsBlankPage();
							if (isBlankPage_)
							{
								dataBuf_.FreeBuffer();
								SDI_TRACE_LOG("blank page found  %d", ES_IMAGE_INFO::GetESImagePageCount(imageInfo_));
								SubThreadCount();
								return;
							}
						}
					}
				}
				{	
					if(IsHardwareImageProcessSupported(keyMgr) == false)
					{
						lut.SetAutoJudgedColor(autoJudgedColor || autoColorPixelType == kSDIAutoColorPixelTypeGray);
						lut.Process(imageInfo_, dataBuf_);		
					}								
				}
				{
					if (whitePageSkipType == kPrvHRD_WhitePageSkipColorType_Gray)
					{
						SkipBlankPageOnOff* skipBlankPageOnOff = (SkipBlankPageOnOff*)(keyMgr->GetKeyInstance(kSDIBlankPageSkipKey).get());
						if(skipBlankPageOnOff && skipBlankPageOnOff->IsSupportHardwareBlankPageOnOff())
						{
							if(ES_IMAGE_INFO::GetESImageBlankPageSkip(imageInfo_))
							{
								isBlankPage_ = true;
								dataBuf_.FreeBuffer();
								SDI_TRACE_LOG("machine blank page found  %d", ES_IMAGE_INFO::GetESImagePageCount(imageInfo_));
								SubThreadCount();
								return;
							} else {
								isBlankPage_ = false;
							}
						} else {
							dtr.SetActionType(kActionTypeJudgeBlankPage);
							dtr.Process(imageInfo_, dataBuf_);
							isBlankPage_ = dtr.IsBlankPage();
							if (isBlankPage_)
							{
								dataBuf_.FreeBuffer();
								SDI_TRACE_LOG("blank page found  %d", ES_IMAGE_INFO::GetESImagePageCount(imageInfo_));
								SubThreadCount();
								return;
							}
						}
					}
				}
				{
					colorToGray.SetAutoJudgedColor(autoJudgedColor);
					colorToGray.Process(imageInfo_, dataBuf_);
					if (keyMgr->GetValueInt(kSDITextEnhanceKey) != kSDITextEnhanceLevelNone)
					{
						if(keyMgr->GetValueInt(kSDIColorTypeKey) == kSDIColorTypeMono1
							|| (keyMgr->GetValueInt(kSDIColorTypeKey) == kSDIColorTypeAuto && autoJudgedColor == false && autoColorPixelType == kSDIAutoColorPixelTypeMono))
						{
							ddeFilter.SetActionType(kActionTypeBin);
							ddeFilter.Process(imageInfo_, dataBuf_);
						}
						else{
							ddeFilter.SetActionType(kActionTypeADE);
							ddeFilter.Process(imageInfo_, dataBuf_);
						}
					}else
					{
						grayToMono.SetAutoJudgedColor(autoJudgedColor || autoColorPixelType == kSDIAutoColorPixelTypeGray);
						grayToMono.SetIsConvertToGamma22(IsHardwareImageProcessSupported(keyMgr));
						grayToMono.Process(imageInfo_, dataBuf_);				
					}
				}
				{
					if (keyMgr->GetValueInt(kSDIOrientationKey) == kSDIOrientationAuto)
					{
						getOrientation.Process(imageInfo_, dataBuf_);
						if (getOrientation.GetJudgeResult() != kSDIOrientationNone)
						{
							procOrientation.SetOrientation(getOrientation.GetJudgeResult());
							procOrientation.Process(imageInfo_, dataBuf_);
						}
					}
					else
					{	
							procOrientation.SetOrientation((SDIOrientation)keyMgr->GetValueInt(kSDIOrientationKey));
							procOrientation.Process(imageInfo_, dataBuf_);
					}
				}
				{
					monoToGray.Process(imageInfo_, dataBuf_);
				}
				{				

					if (keyMgr->GetValueInt(kSDIEdgeFillKey) != kSDIEdgeFillNone)
					{
						ESNumber edgeFillLeftMM = keyMgr->GetValueInt(kSDIEdgeFillLeftKey);
						ESNumber edgeFillRightMM = keyMgr->GetValueInt(kSDIEdgeFillRightKey);
						ESNumber edgeFillBottomMM = keyMgr->GetValueInt(kSDIEdgeFillBottomKey);
						ESNumber edgeFillTopMM = keyMgr->GetValueInt(kSDIEdgeFillTopKey);
						
						ESNumber resolution = ES_IMAGE_INFO::GetESImageResolutionX(imageInfo_);

						double edgeFillPixelLeft = MyMMtoInch(edgeFillLeftMM / 10.0) * 1000;
						double edgeFillPixelRight = MyMMtoInch(edgeFillRightMM / 10.0) * 1000;
						double edgeFillPixelTop = MyMMtoInch(edgeFillTopMM / 10.0) * 1000;
						double edgeFillPixelBottom = MyMMtoInch(edgeFillBottomMM / 10.0) * 1000;


						ProcessEdgeFill(dataBuf_, imageInfo_, (SDIEdgeFill)keyMgr->GetValueInt(kSDIEdgeFillKey), edgeFillPixelTop, edgeFillPixelLeft, edgeFillPixelRight, edgeFillPixelBottom);
					}

				}
				{
					saving.Process(imageInfo_, dataBuf_);
				}

				filePath_ = saving.GetOutputFileName();				
				dataBuf_.FreeBuffer();

				SDI_TRACE_LOG("applyImageProc end %d", ES_IMAGE_INFO::GetESImagePageCount(imageInfo_));

				}catch(std::bad_alloc& memerror)
				{
					SDI_TRACE_LOG("memory  error");
					lastError_ = kSDIErrorNoMemory;	
				}catch(RuntimeError& runtimeError)
				{
					SDI_TRACE_LOG("runtime error = %s", runtimeError.what());
					lastError_ = runtimeError.GetCode();
				}
				catch(...)
				{
					SDI_TRACE_LOG("unknown  error");
					lastError_ = kSDIErrorUnknownError;
				}

				SubThreadCount();
			}));


			return kSDIErrorNone;
	}
}//namespace epsonscan
