/*<<<***************************************************************************
The MIT License (MIT)

Copyright(C) 2013 - 2015 Marc Mirti Mancinelli, Christophe Lardau and Philippe Pamart (xdcube.org)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

!! IMPORTANT !!
This plugin/software uses and integrates the following sources which are 
obviously not covered/excluded from this license: 
- Independent JPEG Group's software library 6b used for jpeg support 
	(folder jpeg-6b, see README.txt for license)
- Glenn Randers- Pehrson's libpng-1.2.8
	(folder libpng-128, see LICENSE.txt)
- Jean-loup Gailly/Mark Adler's zlib-1.2.3 for png support and gzip compression
	(folder zlib-123, see README.txt for license)

For this source, please refer to their own license.
////////////////////////////////////////////////////////////////////////////////
#Title: xdcPicBmpPlug
#Project: xdcPicStd
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<15/09/2013: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/
#include "stdafx.h"
#include "xdcPicStd.h"
#include "xdcPicBmpPlug.h"

#include "jpeg-6b/jinclude.h"
#include "jpeg-6b/jerror.h"

/**************************************************************************************
* bmpPlug
**************************************************************************************/
void bmpPlug::throwEnd(const xbString &strEnd,const xbString &strCtx) const
{
	if (g_PicStdFact.isUnloadAsked())
		xbThrow xbEhotPlug(E_ABORT,strEnd+", plugin in destruction (JPEG plugin)",xbEdebug("bmpPlug::"+strCtx+""));
	xbThrow gdiEcompressUser(E_ABORT,strEnd+", operation cancelled by application (JPEG plugin)",xbEdebug("bmpPlug::"+strCtx+""));
}

bmpPlug::bmpPlug()
{
}

void bmpPlug::askClose(LPCSTR pReason)
{
	{
		xbSectLock lck(&m_sectPtr);
		if (pReason && m_closeReason.IsEmpty())
			m_closeReason = pReason;
	}
	Destroy();
}

xbString bmpPlug::getCloseReason() const
{
	xbSectLock lck(&m_sectPtr);
	xbString ret(m_closeReason);
	ret.OwnData();
	return ret;
}

void bmpPlug::onDestruction(bool /*bInDestroy*/)
{
}

// IgdiCompressPlug methods
bool bmpPlug::CanWorkWithType(LPCSTR pszType, IxbInputStream *pStm, xbRefPtr<IgdiDecompressor> *pprDecomp, IgdiImage *pImg) const
{
	if (pszType && stricmp(pszType,"bmp") == 0)
	{
		if (pprDecomp || pImg)
		{
			((bmpPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}
	//TODO
	return false;
}

bool bmpPlug::CanWorkWithType(LPCSTR pszType, const uchar *pStmData, int lgData, int *pLgDataMin, xbRefPtr<IgdiDecompressor> *pprDecomp, IgdiImage *pImg) const
{
	if (pszType && !lgData && stricmp(pszType,"bmp") == 0)
	{
		if (pprDecomp || pImg)
		{
			((bmpPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}
	if (lgData < 2)
	{
		*pLgDataMin = 2;
		return false;
	}
	if (memcmp(pStmData,"BM",2) == 0)
	{
		if (pprDecomp || pImg)
		{
			((bmpPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}
	return false;
}

bool bmpPlug::Decompress(ulong caps, xbRefPtr<IgdiDecompressor> *pprUncomp,xbOpMode reqMode)
{
	xbAssert(caps == 0);
	bmpDecompressor::CreateObject(m_pFact,pprUncomp);
	return true;
}

bool bmpPlug::Compress(ulong caps, xbRefPtr<IgdiCompressor> *pprComp,xbOpMode reqMode)
{
	xbAssert(caps == 0);
	xbThrow gdiEcompress(E_NOTIMPL,"bmpPlug::Compress() : not implemented (BMP plugin)");
//	jpegBmpCompressor::CreateObject(m_pFact,pprComp);
	return true;
}

bool bmpPlug::EnumDecompressSetting(int &no, gdiSettingInfo *pInfo) const
{
	//TODO
	return false;
}

xbString bmpPlug::GetCompressVersion(bool bShort,LCID lcid) const
{
	return "xdcPicStd Bmp Plugin";
}

xbString bmpPlug::GetCompressLabel(bool bShort,LCID lcid) const
{
	return "Bmp images";
}

xbString bmpPlug::GetDefaultExtension() const
{
	return "bmp";
}

bool bmpPlug::GetCompressLevelInfo(gdiSettingInfo *pInfo) const
{
	return false;
}

bool bmpPlug::EnumCompressSetting(int &no, gdiSettingInfo *pInfo) const
{
	//TODO
	return false;
}

gdiCompressCaps bmpPlug::GetCaps() const
{
	return gdiCompressCaps(gdiCompressCaps_Read);
}


void bmpDecompressor::CreateObject(xbHotPlugFactory *pFactory,xbRefPtr<IgdiDecompressor> *ppObj)
{
	bmpDecompressor *plg = xbDnew bmpDecompressor();
	try
	{
		plg->setFactory(pFactory);
		plg->Create();
		
		(*ppObj) = plg;
		
		plg->Release();
	}
	catch(...)
	{
		plg->Release();
		throw;
	}
}
void bmpDecompressor::throwEnd(const xbString &strEnd,const xbString &strCtx) const
{
	if (g_PicStdFact.isUnloadAsked())
		xbThrow xbEhotPlug(E_ABORT,strEnd+", plugin in destruction (BMP plugin)",xbEdebug("bmpDecompressor::"+strCtx+""));
	xbThrow gdiEcompressUser(E_ABORT,strEnd+", operation cancelled by application (BMP plugin)",xbEdebug("bmpDecompressor::"+strCtx+""));
}

bmpDecompressor::bmpDecompressor()
{
	m_bBmpInit = false;
	m_bmpStep = bmpStep_Unknown;
}

void bmpDecompressor::askClose(LPCSTR pReason)
{
	{
		xbSectLock lck(&m_sectPtr);
		if (pReason && m_closeReason.IsEmpty())
			m_closeReason = pReason;
	}
	Destroy();
}

xbString bmpDecompressor::getCloseReason() const
{
	xbSectLock lck(&m_sectPtr);
	xbString ret(m_closeReason);
	ret.OwnData();
	return ret;
}

void bmpDecompressor::onDestruction(bool /*bInDestroy*/)
{
	eProtect(bmpClose());
	eProtect(m_prImg.Empty());
	eProtect(m_prBmp.Empty());
}

void bmpDecompressor::NewOutput(IgdiImage *pImg,IgdiDecompressorCallBack *pCb)
{
	bmpOpen();
	if (pImg)
		m_prImg = pImg;
	else
		m_prImg.Empty();
	m_prBmp.Empty();
}

bool bmpDecompressor::GetOutput(xbRefPtr<IgdiImage> *pprImg,xbOpMode reqMode)
{
	if (m_prImg.IsEmpty())
	{
		if (reqMode == xbOp_Required)
			xbThrow gdiEcompress("No current image affected to GIF decompressor","bmpDecompressor::GetOutput()");
		return false;
	}
	
	if (pprImg)
		*pprImg = m_prImg;
	return true;
}

bool bmpDecompressor::EnumDecompressSetting(int &no, gdiSettingInfo *pInfo) const
{
	return false;
}


bool bmpDecompressor::SetDecompressSetting(LPCSTR pszName,int intVal,LPCSTR strVal,xbOpMode reqMode)
{
	return false;
}

bool bmpDecompressor::GetDecompressSetting(LPCSTR pszName,int *pIntVal,xbString *pStrVal) const
{
	return false;
}

void bmpDecompressor::ResetDecompressSettings()
{
}


//IxbOutputStream methods
bool	bmpDecompressor::CanWrite() const
{
	return m_bBmpInit;
}

ulong	bmpDecompressor::Write(const void *pData,ulong DataLg,bool DataLg_Required)
{
	if (!m_bBmpInit)
		xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, GIF decompressor not started","bmpDecompressor::Write()");
	
	if (m_bmpStep == bmpStep_Unknown || m_bmpStep == bmpStep_BmpEnd)
	{
		if (DataLg_Required && DataLg > 0)
			xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, decompression end reached with staying data","bmpDecompressor::Write() : DataLg="+xbString(DataLg)+", Staying="+xbString(DataLg + waitingLength()));

		return DataLg;
	}

	m_waitingDta.Add(pData,DataLg);

	waitingStart();

	try
	{
		while(bmpProcessWaitingData());
	}
	catch(...)
	{
		waitingEnd();
		m_bmpStep = bmpStep_Unknown;
		throw;
	}

	waitingEnd();

	if (m_bmpStep == bmpStep_Unknown || m_bmpStep == bmpStep_BmpEnd)
	{
		int cDta = bmpEndStayingData(DataLg);
		if (DataLg_Required && cDta != int(DataLg))
			xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, decompression end reached with staying data","bmpDecompressor::Write() : DataLg="+xbString(DataLg)+", Staying="+xbString(cDta));
		return cDta;
	}
	return DataLg;
}

ulong	bmpDecompressor::WriteStream(IxbInputStream *pstm)
{
	xbThrow gdiEcompress(E_NOTIMPL,"bmpDecompressor::WriteStream() : not implemented (BMP plugin)");
	return -1;
}

ulong	bmpDecompressor::WriteStream(IxbInputStream *pstm,ulong DataLg,bool DataLg_Required)
{
	xbThrow gdiEcompress(E_NOTIMPL,"bmpDecompressor::WriteStream() : not implemented (BMP plugin)");
	return -1;
}

ulong	bmpDecompressor::WriteString(const char *pStr,ulong lgStr,bool bAddLf,IxbStreamMode *pMode)
{
	xbThrow gdiEcompress(E_NOTIMPL,"bmpDecompressor::WriteString() : not implemented (BMP plugin)");
	return -1;
}

ulong	bmpDecompressor::WriteStringW(const wchar *pStr,ulong lgStr,bool bAddLf,IxbStreamMode *pMode)
{
	xbThrow gdiEcompress(E_NOTIMPL,"bmpDecompressor::WriteStringW() : not implemented (BMP plugin)");
	return -1;
}


void bmpDecompressor::bmpClose()
{
	if (!m_bBmpInit)
		return;
	
	m_bBmpInit = false;
	m_palette.Empty();
	m_rowPixel.Empty();
	memset(&m_hdr,0,sizeof(m_hdr));
	memset(&m_img,0,sizeof(m_img));


	m_prBmp.Empty();
	m_bmpStep = bmpStep_Unknown;
}


void bmpDecompressor::bmpOpen()
{
	bmpClose();
	
	m_bBmpInit = true;
	
	memset(&m_hdr,0,sizeof(m_hdr));
	memset(&m_img,0,sizeof(m_img));
	m_palette.Empty();
	m_rowPixel.Empty();
	m_waitingDta.Empty();
	m_bmpStep = bmpStep_ReadFileHeader;
	m_paddingAfterImage = 0;
	m_paddingBeforeImage = 0;
	m_rowNo = 0;
}

int bmpDecompressor::bmpEndStayingData(int dataLg)
{
	int dataStaying = waitingLength();
	if (dataStaying > dataLg)
		xbThrow gdiEcompress("Too many data staying at decompression end, can't get processed data length (BMP plugin, bytes staying: "+xbString(dataStaying)+", current bytes written: "+xbString(dataLg)+")");

	return dataLg - dataStaying;
}

bool bmpDecompressor::bmpProcessWaitingData()
{
	switch(m_bmpStep)
	{
	case bmpStep_ReadFileHeader:
		if (waitingLength() < sizeof(BITMAPFILEHEADER))
			return false;

		waitingRead(&m_hdr, sizeof(BITMAPFILEHEADER));
		if (memcmp(&m_hdr,"BM",2)!=0)
			xbThrow gdiEcompress("Invalid BMP file (invalid header, BMP plugin)");
		m_bmpStep = bmpStep_ReadInfoHeader;
		return true;
	case bmpStep_ReadInfoHeader:
		if (waitingLength() < 40)
			return false;

		if (sizeof(m_img) != 40)
			xbThrow gdiEcompress("Invalid struct BITMAPINFOHEADER (invalid header, BMP plugin)");
		waitingRead(&m_img, 40);

		if (m_img.biSize != 40)
			xbThrow gdiEcompress("Invalid BMP file (invalid info header size="+xbString(m_img.biSize)+", BMP plugin)");
		if (m_img.biPlanes != 1)
			xbThrow gdiEcompress("Unsupported BMP file (bits planes="+xbString(m_img.biPlanes)+", BMP plugin)");
		if (m_img.biCompression != 0)
			xbThrow gdiEcompress("Unsupported BMP file (compression="+xbString(m_img.biCompression)+", BMP plugin)");

		if (m_prImg.IsEmpty())
		{
			gdiCreateImage(&m_prImg,"simple","BMP image");
		}
		m_prImg->SetBitmapCount(0);
		{
			gdiBmpInfo	info;
			info.bmpSz.cx = m_img.biWidth;
			info.bmpSz.cy = m_img.biHeight;
			info.bmpType = gdiBmpColor_RGB;
			info.bmpBitsPerPixel = 24;
			switch(m_img.biBitCount)
			{
			case 8:
				m_palette.Length = 256;
				m_rowPixel.Length = (m_img.biWidth+3)/4*4;
				break;
			case 24:
				m_rowPixel.Length = ((m_img.biWidth*3)+3)/4*4;
				break;
			default:
				xbThrow gdiEcompress(E_NOTIMPL,"Unknown BMP format","bmpCompressor, bitcount: "+xbString(m_img.biBitCount));
			};
			m_paddingBeforeImage = m_hdr.bfOffBits - sizeof(m_hdr) - m_img.biSize - (m_palette.Length * 4);
			if (m_paddingBeforeImage < 0)
				xbThrow gdiEcompress("Invalid BMP file (offset data="+xbString(m_hdr.bfOffBits)+", padding="+xbString(m_paddingBeforeImage)+", BMP plugin)");

			m_paddingAfterImage = m_hdr.bfSize - m_hdr.bfOffBits - (m_img.biHeight * m_rowPixel.Length);
			if (m_paddingAfterImage < 0)
				xbThrow gdiEcompress("Invalid BMP file (file size="+xbString(m_hdr.bfSize)+", offset data="+xbString(m_hdr.bfOffBits)+", padding="+xbString(m_paddingAfterImage)+", BMP plugin)");

			m_rowNo = m_img.biHeight;

			m_prImg->CreateBitmap(-1,&info,&m_prBmp);
		}

		if (m_palette.Length>0)
		{
			m_bmpStep = bmpStep_ReadPalette;
			return true;
		}
		if (m_paddingBeforeImage > 0)
		{
			m_bmpStep = bmpStep_ReadPaddingImage;
			return true;
		}
		m_bmpStep = bmpStep_ReadImage;
		return true;
	case bmpStep_ReadPalette:
		if (waitingLength() < 4*m_palette.Length)
			return false;
		waitingRead((void*)m_palette.Begin(), 4*m_palette.Length);
		if (m_paddingBeforeImage > 0)
		{
			m_bmpStep = bmpStep_ReadPaddingImage;
			return true;
		}
		m_bmpStep = bmpStep_ReadImage;
		return true;
	case bmpStep_ReadPaddingImage:
		if (waitingLength() < m_paddingBeforeImage)
			return false;

		waitingForward(m_paddingBeforeImage);
		m_bmpStep = bmpStep_ReadImage;
		return true;

	case bmpStep_ReadPadding:
		if (waitingLength() < m_paddingAfterImage)
			return false;

		waitingForward(m_paddingAfterImage);
		m_bmpStep = bmpStep_BmpEnd;
		return true;
	case bmpStep_ReadImage:
		if (waitingLength() < m_rowPixel.Length)
			return false;
		waitingRead((void*)m_rowPixel.Begin(), m_rowPixel.Length);

		m_rowNo--;

		for(;;)
		{
			xbLock lck;
			uchar *row_pointer=NULL;
			m_prBmp->GetBitmapRow(&lck,m_rowNo, (uchar**) &row_pointer, 1, m_img.biWidth*3);

			if (m_img.biBitCount == 24)
			{
				memcpy(row_pointer,m_rowPixel.Begin(),m_img.biWidth*3);

				break;
			}
			if (m_img.biBitCount == 8)
			{
				uchar *row_pointerEnd = row_pointer + m_img.biWidth*3;
				const ulong *pal = m_palette.Begin();
				ulong cpal;
				const uchar *ptr = m_rowPixel.Begin();
				while(row_pointer < row_pointerEnd)
				{
					cpal = pal[*(ptr++)];
					*(row_pointer++) = uchar(cpal&0xFF);
					*(row_pointer++) = uchar((cpal>>8)&0xFF);
					*(row_pointer++) = uchar((cpal>>16)&0xFF);
				}
				break;
			}

			xbThrow gdiEcompress(E_NOTIMPL,"Internal reading BMP format","bmpCompressor, bitcount: "+xbString(m_img.biBitCount));
		}
		if (m_rowNo > 0)
			return true;

		if (m_paddingAfterImage > 0)
		{
			m_bmpStep = bmpStep_ReadPadding;
			return true;
		}
		m_bmpStep = bmpStep_BmpEnd;
		return true;
	case bmpStep_BmpEnd:
		return false;
	}

	xbThrow gdiEcompress(E_INVALIDARG,"gifCompressor::gifProcess() : unknown step("+xbString(int(m_bmpStep))+") (BMP plugin)");
}

int bmpDecompressor::waitingLength()
{
	if (m_pWaitingDta)
		return xbNptr(m_pWaitingDtaEnd) - xbNptr(m_pWaitingDta);
	return m_waitingDta.Length;
}

void bmpDecompressor::waitingRead(void *pBuf, int cByte)
{
	if (cByte + m_pWaitingDta > m_pWaitingDtaEnd)
		xbThrow gdiEcompress("bmpDecompressor::waitingRead() : not enough waiting data");
	memcpy(pBuf,m_pWaitingDta,cByte);
	m_pWaitingDta+=cByte;
}

uchar bmpDecompressor::waitingReadUchar()
{
	if (m_pWaitingDta >= m_pWaitingDtaEnd)
		xbThrow gdiEcompress("bmpDecompressor::waitingReadUchar() : not enough waiting data");
	return *(m_pWaitingDta++);
}

ushort bmpDecompressor::waitingReadUshort()
{
	if (m_pWaitingDta + 2 > m_pWaitingDtaEnd)
		xbThrow gdiEcompress("bmpDecompressor::waitingReadUshort() : not enough waiting data");
   ushort val = ((m_pWaitingDta[1] << 8) | m_pWaitingDta[0]);
   m_pWaitingDta+=2;
   return val;
}

void bmpDecompressor::waitingForward(int cByte)
{
	if (cByte < 0 || cByte + m_pWaitingDta > m_pWaitingDtaEnd)
		xbThrow gdiEcompress("bmpDecompressor::waitingForward() : not enough waiting data");
	m_pWaitingDta+=cByte;
}

void bmpDecompressor::waitingBackward(int cByte)
{
	if (cByte < 0 || m_pWaitingDta-cByte < m_pWaitingDtaStart)
		xbThrow gdiEcompress("bmpDecompressor::waitingBackward() : not enough waiting data");
	m_pWaitingDta-=cByte;
}

void bmpDecompressor::waitingStart()
{
	m_pWaitingDtaStart = (const uchar *)(LPCVOID)m_waitingDta;
	m_pWaitingDtaEnd = m_pWaitingDtaStart + m_waitingDta.Length;
	m_pWaitingDta = m_pWaitingDtaStart;
}
void bmpDecompressor::waitingEnd()
{
	int cByte = xbNptr(m_pWaitingDta) - xbNptr(m_pWaitingDtaStart);
	m_waitingDta.Delete(0,cByte);
	m_pWaitingDta=NULL;
}

