/*<<<***************************************************************************
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: xdcPicJpegPlug
#Project: xdcPicStd
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<15/09/2013: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/
#include "stdafx.h"
#include "xdcPicStd.h"
#include "xdcPicJpegPlug.h"

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

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

jpegPlug::jpegPlug()
{
}

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

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

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

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

const uchar jpg_signature[2] = {255, 216 /*M_SOI*/};

bool jpegPlug::CanWorkWithType(LPCSTR pszType, const uchar *pStmData, int lgData, int *pLgDataMin, xbRefPtr<IgdiDecompressor> *pprDecomp, IgdiImage *pImg) const
{
	if (pszType && !lgData && (stricmp(pszType,"jpg") == 0 || stricmp(pszType,"jpeg") == 0))
	{
		if (pprDecomp || pImg)
		{
			((jpegPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}
/*	if (lgData < 10)
	{
		*pLgDataMin = 10;
		return false;
	}
	if (memcmp(pStmData+6,"JFIF",4) == 0)
	{
		if (pprDecomp || pImg)
		{
			((jpegPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}
	if (memcmp(pStmData+6,"Exif",4) == 0)
	{
		if (pprDecomp || pImg)
		{
			((jpegPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}*/
	if (lgData < sizeof(jpg_signature))
	{
		*pLgDataMin = sizeof(jpg_signature);
		return false;
	}

	if (memcmp(pStmData,jpg_signature,sizeof(jpg_signature)) == 0)
	{
		if (pprDecomp || pImg)
		{
			((jpegPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}

	return false;
}

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

bool jpegPlug::Compress(ulong caps, xbRefPtr<IgdiCompressor> *pprComp,xbOpMode reqMode)
{
	xbAssert(caps == 0);
	jpegCompressor::CreateObject(m_pFact,pprComp);
	return true;
}

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

xbString jpegPlug::GetCompressVersion(bool bShort,LCID lcid) const
{
	return "xdcPicture JPEG-6b Plugin";
}

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

xbString jpegPlug::GetDefaultExtension() const
{
	return "jpg";
}

bool jpegPlug::GetCompressLevelInfo(gdiSettingInfo *pInfo) const
{
	pInfo->dfltVal = jpg_DEFAULT_COMPRESSION;
	pInfo->maxVal = 100;
	pInfo->minVal = 0;
	pInfo->label = "Compression level";
	pInfo->name = "CompressLevel";
	pInfo->help = "100 is the best compression and lowest quality";

	return true;
}

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

gdiCompressCaps jpegPlug::GetCaps() const
{
	return gdiCompressCaps(gdiCompressCaps_ReadWrite | gdiCompressCaps_CompressWithQualityLost);
}

/**************************************************************************************
* jpegCompressor
**************************************************************************************/
void jpegCompressor::CreateObject(xbHotPlugFactory *pFactory,xbRefPtr<IgdiCompressor> *ppObj)
{
	jpegCompressor *plg = xbDnew jpegCompressor();
	try
	{
		plg->setFactory(pFactory);
		plg->Create();
		
		(*ppObj) = plg;
		
		plg->Release();
	}
	catch(...)
	{
		plg->Release();
		throw;
	}
}

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

jpegCompressor::jpegCompressor()
{
	m_bJpegInit = false;
	m_compressLevel = jpg_DEFAULT_COMPRESSION;
}

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

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


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

// IgdiCompressor methods
void jpegCompressor::NewInput(IgdiImage *pImg)
{
	jpegOpen();
	m_prImg = pImg;
	pImg->GetBitmap(-1,&m_prBmp);
	
	jpegInit();
}

void jpegCompressor::NewInput(IgdiBitmap *pBmp)
{
	jpegOpen();
	m_prImg.Empty();
	m_prBmp = pBmp;
}

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

bool jpegCompressor::GetInput(xbRefPtr<IgdiBitmap> *pprBmp,xbOpMode reqMode)
{
	if (m_prBmp.IsEmpty())
	{
		if (reqMode == xbOp_Required)
			xbThrow gdiEcompress("No current bitmap affected to JPEG compressor","jpegCompressor::GetInput()");
		return false;
	}
	
	if (pprBmp)
		*pprBmp = m_prBmp;
	return true;
}

void jpegCompressor::SetCompressLevel(int compressLevel)
{
	if (m_bJpegInit)
		xbThrow gdiEcompress("JPEG compression: can't change compression level after compression is started","jpegCompressor::SetCompressLevel("+xbString(compressLevel)+")"); 
	switch (compressLevel)
	{
	case gdiCompressLow:
		compressLevel = 0;
		break;
	case gdiCompressMid:
		compressLevel = 50;
		break;
	case gdiCompressDefault:
		compressLevel = jpg_DEFAULT_COMPRESSION;
		break;
	case gdiCompressHigh:
		compressLevel = 100;
		break;
	}
	if (compressLevel < 0 || compressLevel > 100)
		xbThrow gdiEcompress("JPEG compression: Bad compression level (must be between 0 and 100)","jpegCompressor::SetCompressionLevel("+xbString(compressLevel)+")"); 
	m_compressLevel = compressLevel;
}

int jpegCompressor::GetCompressLevel() const
{
	return m_compressLevel;
}


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


bool jpegCompressor::SetCompressSetting(LPCSTR pszName,int intVal,LPCSTR strVal,xbOpMode reqMode)
{
	if (reqMode == xbOp_Required)
		xbThrow gdiEcompressUser(xbString(pszName)+" setting not supported or unknown","jpegCompressor::SetCompressSetting()");
	return false;
}

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

void jpegCompressor::ResetCompressSettings(int compressLevel)
{
	jpegClose();
	SetCompressLevel(compressLevel);
}


//IxbInputStream methods
bool	jpegCompressor::CanRead() const
{
	return m_bJpegInit && m_prImg.IsValid();
}

ulong	jpegCompressor::Read(void *pData,ulong DataLg,bool DataLg_Required)
{
	if (!m_bJpegInit || m_prImg.IsEmpty())
		xbThrow gdiEcompress(STG_E_READFAULT,"Read failed, JPEG compressor not started","jpegCompressor::Read()");
	uchar *ptr = (uchar*)pData;
	ulong rd = 0;
	while(DataLg > 0)
	{
		if (!jpegRead(DataLg))
		{
			if (DataLg_Required)
				xbThrow gdiEcompress(STG_E_READFAULT,"Read failed, JPEG End of image reached","jpegCompressor::Read()");
			break;
		}
		int cByte = m_resDta.Length;
		if (ulong(cByte) <= DataLg)
		{
			memcpy(ptr,m_resDta,cByte);
			m_resDta.Empty();
		}
		else
		{
			memcpy(ptr,m_resDta,DataLg);
			m_resDta.Delete(0,DataLg);
			cByte = int(DataLg);
		}
		ptr += cByte;
		rd += cByte;
		DataLg -= cByte;
	}
	return rd;
}


ulong	jpegCompressor::ReadString(xbString *pStr,xbString *pStopStr,IxbStreamMode *pMode)
{
	xbThrow gdiEcompress(E_NOTIMPL,"jpegCompressor::ReadString() : not implemented (JPEG plugin)");
	return -1;
}

ulong	jpegCompressor::ReadStringW(xbBSTR *pStr,xbBSTR *pStopStr,IxbStreamMode *pMode)
{
	xbThrow gdiEcompress(E_NOTIMPL,"jpegCompressor::ReadStringW() : not implemented (JPEG plugin)");
	return -1;
}

/* Expanded data destination object for stdio output */
struct my_destination_mgr
{
	struct jpeg_destination_mgr pub; /* public fields */
	
	xbData *pOutBuf;		/* target stream */
	JOCTET * buffer;		/* start of buffer */
};

typedef my_destination_mgr * my_dest_ptr;

#define OUTPUT_BUF_SIZE  4096	/* choose an efficiently fwrite'able size */
/*
* Initialize destination --- called by jpeg_start_compress
* before any data is actually written.
*/

METHODDEF(void)
init_destination (j_compress_ptr cinfo)
{
	my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
	
	/* Allocate the output buffer --- it will be released when done with image */
	dest->buffer = (JOCTET *)
		(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
		OUTPUT_BUF_SIZE * SIZEOF(JOCTET));
	
	dest->pub.next_output_byte = dest->buffer;
	dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
}


/*
* Empty the output buffer --- called whenever buffer fills up.
*
* In typical applications, this should write the entire output buffer
* (ignoring the current state of next_output_byte & free_in_buffer),
* reset the pointer & count to the start of the buffer, and return TRUE
* indicating that the buffer has been dumped.
*
* In applications that need to be able to suspend compression due to output
* overrun, a FALSE return indicates that the buffer cannot be emptied now.
* In this situation, the compressor will return to its caller (possibly with
* an indication that it has not accepted all the supplied scanlines).  The
* application should resume compression after it has made more room in the
* output buffer.  Note that there are substantial restrictions on the use of
* suspension --- see the documentation.
*
* When suspending, the compressor will back up to a convenient restart point
* (typically the start of the current MCU). next_output_byte & free_in_buffer
* indicate where the restart point will be if the current call returns FALSE.
* Data beyond this point will be regenerated after resumption, so do not
* write it out when emptying the buffer externally.
*/

METHODDEF(boolean)
empty_output_buffer (j_compress_ptr cinfo)
{
	my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
	
	dest->pOutBuf->Add(dest->buffer, OUTPUT_BUF_SIZE);
	
	dest->pub.next_output_byte = dest->buffer;
	dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
	
	return TRUE;
}


/*
* Terminate destination --- called by jpeg_finish_compress
* after all data has been written.  Usually needs to flush buffer.
*
* NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
* application must deal with any cleanup that should happen even
* for error exit.
*/

METHODDEF(void)
term_destination (j_compress_ptr cinfo)
{
	my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
	size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer;
	
	/* Write any data remaining in the buffer */
	if (datacount > 0) 
	{
		dest->pOutBuf->Add(dest->buffer, datacount);
	}
}


/*
* Prepare for output to a stdio stream.
* The caller must have already opened the stream, and is responsible
* for closing it after finishing compression.
*/

GLOBAL(void)
jpeg_xbData_dest (j_compress_ptr cinfo, xbData* pOutBuf)
{
	my_dest_ptr dest;
	
	/* The destination object is made permanent so that multiple JPEG images
	* can be written to the same file without re-executing jpeg_stdio_dest.
	* This makes it dangerous to use this manager and a different destination
	* manager serially with the same JPEG object, because their private object
	* sizes may be different.  Caveat programmer.
	*/
	if (cinfo->dest == NULL) 
	{	/* first time for this JPEG object? */
		cinfo->dest = (struct jpeg_destination_mgr *)
			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
			SIZEOF(my_destination_mgr));
	}
	
	dest = (my_dest_ptr) cinfo->dest;
	dest->pub.init_destination = init_destination;
	dest->pub.empty_output_buffer = empty_output_buffer;
	dest->pub.term_destination = term_destination;
	dest->pOutBuf = pOutBuf;
}

void jpegCompressor::jpegOpen()
{
	jpegClose();
	
	m_cinfo.err = jpeg_std_error(&m_jerr);
	jpeg_create_compress(&m_cinfo);
	
	m_bJpegInit = true;
}

void jpegCompressor::jpegInit()
{
	m_resDta.Empty();
	
	/* Step 2: specify data destination (eg, a file) */
	/* Note: steps 2 and 3 can be done in either order. */
	jpeg_xbData_dest(&m_cinfo, &m_resDta);
	
	/* Step 3: set parameters for compression */
	
	/* First we supply a description of the input image.
	* Four fields of the cinfo struct must be filled in:
	*/
	
	gdiSizeLow			sz;
	gdiBmpColorType	bmpType;
	int				bmpBitsPerPixel;
	m_prBmp->GetBitmapInfo(&sz,&bmpType,&bmpBitsPerPixel);
	m_prBmp->msAlphaUndo();
	m_cinfo.image_width = sz.cx; 	/* image width and height, in pixels */
	m_cinfo.image_height = sz.cy;
	
	bool bConvert = false;
	switch(bmpType)
	{
//	case gdiBmpColor_Gray:
//		m_cinfo.input_components = 1;		/* # of color components per pixel */
//		m_cinfo.in_color_space = JCS_GRAYSCALE; 	/* colorspace of input image */
//		break;
	case gdiBmpColor_RGB:
		m_cinfo.input_components = 3;		/* # of color components per pixel */
		m_cinfo.in_color_space = JCS_RGB; 	/* colorspace of input image */
		break;
//	case gdiBmpColor_CMYK:
//		m_cinfo.input_components = 4;		/* # of color components per pixel */
//		m_cinfo.in_color_space = JCS_CMYK; 	/* colorspace of input image */
//		break;
//	case gdiBmpColor_YUV:
//		m_cinfo.input_components = 3;		/* # of color components per pixel */
//		m_cinfo.in_color_space = JCS_YCbCr; 	/* colorspace of input image */
//		break;
//	case gdiBmpColor_YUVK:
//		m_cinfo.input_components = 4;		/* # of color components per pixel */
//		m_cinfo.in_color_space = JCS_YCCK; 	/* colorspace of input image */
//		break;
	default:
		bConvert = true;
		//Normally: gdiBmpColor_RGBO or gdiBmpColor_YUVO
		m_cinfo.input_components = 3;		/* # of color components per pixel */
//		if (bmpType == gdiBmpColor_YUVA)
//		{
//			m_cinfo.in_color_space = JCS_YCbCr; 	/* colorspace of input image */
//			bmpType = gdiBmpColor_YUV;
//		}
//		else
		{
			m_cinfo.in_color_space = JCS_RGB; 	/* colorspace of input image */
			bmpType = gdiBmpColor_RGB;
		}
		break;
	};
	
	if (bConvert || bmpBitsPerPixel != (m_cinfo.input_components * 8))
	{
		xbRefPtr<IgdiBitmap> prNewBmp;
		m_prBmp->Convert(NULL,bmpType,(m_cinfo.input_components * 8),&prNewBmp);
		m_prBmp = prNewBmp;
	}

	gdiSizeU	szU;
	if (m_prImg.IsValid())
		szU = m_prImg->GetMetricSize();
	
    if (szU.IsValid()
		&& szU.unitX != gdiUnit_void && szU.unitX != gdiUnit_percent
		&& szU.unitX == szU.unitY
		) 
	{
		/* Set JFIF density parameters from the BMP data */
		switch(szU.unitX)
		{
		case gdiUnit_inch:
			m_cinfo.X_density = (UINT16) (double(sz.cx) / szU.cx);
			m_cinfo.Y_density = (UINT16) (double(sz.cy) / szU.cy);
			m_cinfo.density_unit = 1;	/* dots/inch */
			break;
		case gdiUnit_mm:
			m_cinfo.X_density = (UINT16) (double(sz.cx) / szU.cx * 10.0); /* 10 mm per cm */
			m_cinfo.Y_density = (UINT16) (double(sz.cy) / szU.cy * 10.0);
			m_cinfo.density_unit = 2;	/* dots/cm */
			break;
		case gdiUnit_point:
			m_cinfo.X_density = (UINT16) (double(sz.cx) / szU.cx * gdiPOINT_PER_INCH);
			m_cinfo.Y_density = (UINT16) (double(sz.cy) / szU.cy * gdiPOINT_PER_INCH);
			m_cinfo.density_unit = 1;	/* dots/inch */
			break;
		case gdiUnit_pixel:
			m_cinfo.X_density = (UINT16) (szU.cx);
			m_cinfo.Y_density = (UINT16) (szU.cy);
			m_cinfo.density_unit = 0;	/* unknown */
			break;
		default:
			gdiEsys::Throw("Invalid x unit for picture size");
		}
    }
	else
	{
		m_cinfo.X_density = (UINT16) (sz.cx);
		m_cinfo.Y_density = (UINT16) (sz.cy);
		m_cinfo.density_unit = 0;	/* unknown */
	}
	
	/* Now use the library's routine to set default compression parameters.
	* (You must set at least m_cinfo.in_color_space before calling this,
	* since the defaults depend on the source color space.)
	*/
	jpeg_set_defaults(&m_cinfo);
	/* Now you can set any non-default parameters you wish to.
	* Here we just illustrate the use of quality (quantization table) scaling:
	*/
	jpeg_set_quality(&m_cinfo, 100-m_compressLevel, TRUE /* limit to baseline-JPEG values */);
	
	/* Step 4: Start compressor */
	
	/* TRUE ensures that we will write a complete interchange-JPEG file.
	* Pass TRUE unless you are very sure of what you're doing.
	*/
	jpeg_start_compress(&m_cinfo, TRUE);
}

void jpegCompressor::jpegClose()
{
	if (!m_bJpegInit)
		return;
	
	m_bJpegInit = false;
	jpeg_destroy_compress(&m_cinfo);
	
	m_resDta.Empty();
}

#define jpegREAD_ROWBY	16
bool jpegCompressor::jpegRead(int dataLg)
{
	if (dataLg <= m_resDta.Length)
		return true;

	if (m_cinfo.next_scanline >= m_cinfo.image_height) 
		return m_resDta.IsValid();
	
	/* Step 5: while (scan lines remain to be written) */
	/*           jpeg_write_scanlines(...); */
	
	/* Here we use the library's state variable cinfo.next_scanline as the
	* loop counter, so that we don't have to keep track ourselves.
	* To keep things simple, we pass one scanline per call; you can pass
	* more if you wish, though.
	*/
	JSAMPROW row_pointer[jpegREAD_ROWBY];
	int rowCnt,rowLength = m_cinfo.image_width * m_cinfo.input_components;
	xbLock	lck;
	while (m_cinfo.next_scanline < m_cinfo.image_height) 
	{
	/* jpeg_write_scanlines expects an array of pointers to scanlines.
	* Here the array is only one element long, but you could pass
	* more than one scanline at a time if that's more convenient.
		*/
		rowCnt = m_prBmp->GetBitmapRow(&lck,m_cinfo.next_scanline, (uchar**) row_pointer, 1 /*jpegREAD_ROWBY*/, rowLength);
		
		jpeg_write_scanlines(&m_cinfo, row_pointer, rowCnt);
		
		if (dataLg <= m_resDta.Length)
			return true;
	}
	
	/* Step 6: Finish compression */
	
	jpeg_finish_compress(&m_cinfo);
	
	return m_resDta.IsValid();
}

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

jpegDecompressor::jpegDecompressor()
{
	m_bJpegInit = false;
	m_jpegStep = jpegStep_Unknown;
}

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

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

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

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

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

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


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

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

void jpegDecompressor::ResetDecompressSettings()
{
}


//IxbOutputStream methods
bool	jpegDecompressor::CanWrite() const
{
	return m_bJpegInit;
}

ulong	jpegDecompressor::Write(const void *pData,ulong DataLg,bool DataLg_Required)
{
	if (!m_bJpegInit)
		xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, JPEG decompressor not started","jpegDecompressor::Write()");
	if (m_jpegStep == jpegStep_Unknown || m_jpegStep == jpegStep_EndOfFormat)
	{
		if (DataLg_Required && DataLg>0)
			xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, decompression end reached with staying data","jpegDecompressor::Write() : DataLg="+xbString(DataLg)+", Staying="+xbString(DataLg+m_waitingDta.Length));
		return DataLg;
	}

	m_waitingDta.Add(pData,DataLg);

	while(jpegProcessWaitingData());
	if (m_jpegStep == jpegStep_Unknown || m_jpegStep == jpegStep_EndOfFormat)
	{
		int cDta= jpegEndStayingData(DataLg);
		if (DataLg_Required && cDta!=int(DataLg))
			xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, decompression end reached with staying data","jpegDecompressor::Write() : DataLg="+xbString(DataLg)+", Staying="+xbString(cDta));
		return cDta;
	}
	return DataLg;
}

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

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

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

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

#define jpegWRITE_ROWBY 2
bool jpegDecompressor::jpegProcessWaitingData()
{
	switch(m_jpegStep)
	{
	case jpegStep_ReadHeader:
	  /* Step 3: read file parameters with jpeg_read_header() */
		if (jpeg_read_header(&m_cinfo, TRUE) == JPEG_SUSPENDED)
			return false;
		if (m_prImg.IsEmpty())
		{
			gdiCreateImage(&m_prImg,"simple","JPEG image");
		}
		m_prImg->SetBitmapCount(0);
		{
			m_prBmp.Empty();

			m_cinfo.output_width = m_cinfo.image_width;
			m_cinfo.output_height = m_cinfo.image_height;

			gdiBmpInfo	info;
			info.bmpSz.cx = m_cinfo.output_width;
			info.bmpSz.cy = m_cinfo.output_height;
			
			switch(m_cinfo.out_color_space)
			{
//			case JCS_GRAYSCALE:
//				info.bmpType = gdiBmpColor_Gray;
//				break;
			case JCS_RGB:
				info.bmpType = gdiBmpColor_RGB;
				break;
/*			case JCS_CMYK:
				info.bmpType = gdiBmpColor_CMYK;
				break;
			case JCS_YCbCr:
				info.bmpType = gdiBmpColor_YUV;
				break;
			case JCS_YCCK:
				info.bmpType = gdiBmpColor_YUVK;
				break;*/
			default:
				info.bmpType = gdiBmpColor_RGB;
				m_cinfo.out_color_space = JCS_RGB;
				m_cinfo.output_components = 3;
				//xbThrow gdiEcompress(E_NOTIMPL,"Unknown jpeg format","jpegCompressor::jpegProcessWaitingData(type: "+xbString(m_cinfo.out_color_space)+", num components: "+xbString(m_cinfo.output_components)+")");
			};

			info.bmpBitsPerPixel = m_cinfo.output_components * 8;

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

		m_jpegStep = jpegStep_StartDecompress;
		return true;
	case jpegStep_StartDecompress:
		/* Step 4: set parameters for decompression */

		/* In
		this example, we don't need to change any of the defaults set by
		* jpeg_read_header(), so we do nothing here.
		*/

		/* Step 5: Start decompressor */

		if (!jpeg_start_decompress(&m_cinfo))
			return false;
		m_jpegStep = jpegStep_ReadRows;
		return true;
	case jpegStep_ReadRows:
		{
			/* JSAMPLEs per row in output buffer */
			int rowLength = m_cinfo.output_width * m_cinfo.output_components;

			JSAMPROW row_pointer[jpegWRITE_ROWBY];
			int rowCnt;
			xbLock lck;

			/* Step 6: while (scan lines remain to be read) */
			/*           jpeg_read_scanlines(...); */

			/* Here we use the library's state variable cinfo.output_scanline as the
			* loop counter, so that we don't have to keep track ourselves.
			*/
			while (m_cinfo.output_scanline < m_cinfo.output_height) 
			{
				/* jpeg_read_scanlines expects an array of pointers to scanlines.
				 * Here the array is only one element long, but you could ask for
				 * more than one scanline at a time if that's more convenient.
				 */
				rowCnt = m_prBmp->GetBitmapRow(&lck,m_cinfo.output_scanline, (uchar**) row_pointer, jpegWRITE_ROWBY, rowLength);

				int ret = jpeg_read_scanlines(&m_cinfo, row_pointer, rowCnt);
				if (ret <= 0)
				{
					if (m_cinfo.output_scanline < m_cinfo.output_height) 
						return false;
					break;
				}
			}
		}
		m_jpegStep = jpegStep_EndOfImage;
		return true;
	case jpegStep_EndOfImage:
		/* Step 7: Finish decompression */

		if (!jpeg_finish_decompress(&m_cinfo))
			return false;

		m_jpegStep = jpegStep_EndOfFormat;
		return false;
	case jpegStep_EndOfFormat:
		return false;
	}

	xbThrow gdiEcompress(E_INVALIDARG,"jpegCompressor::jpegProcess() : unknown step("+xbString(int(m_jpegStep))+") (JPEG plugin)");
}

void jpegDecompressor::jpegClose()
{
	if (!m_bJpegInit)
		return;
	
	m_bJpegInit = false;
	jpeg_destroy_decompress(&m_cinfo);

	m_prBmp.Empty();
	m_jpegStep = jpegStep_Unknown;
}

/* Expanded data source object for stdio input */

struct my_source_mgr
{
	struct jpeg_source_mgr pub;	/* public fields */
	
	xbData		*pInpBuf,*pCurBuf;		/* input stream */
	int			cByteToSkip;
	bool		bEOI;
};

typedef my_source_mgr * my_src_ptr;

#define INPUT_BUF_SIZE  4096	/* choose an efficiently fread'able size */


/*
* Initialize source --- called by jpeg_read_header
* before any data is actually read.
*/

METHODDEF(void)
init_source (j_decompress_ptr cinfo)
{
	my_src_ptr src = (my_src_ptr) cinfo->src;
	
	/* We reset the empty-input-file flag for each image,
	* but we don't clear the input buffer.
	* This is correct behavior for reading a series of images from one source.
	*/
	src->bEOI = false;
	src->cByteToSkip = 0;
//	src->pCurBuf = src->pInpBuf = NULL;
}


/*
* Fill the input buffer --- called whenever buffer is emptied.
*
* In typical applications, this should read fresh data into the buffer
* (ignoring the current state of next_input_byte & bytes_in_buffer),
* reset the pointer & count to the start of the buffer, and return TRUE
* indicating that the buffer has been reloaded.  It is not necessary to
* fill the buffer entirely, only to obtain at least one more byte.
*
* There is no such thing as an EOF return.  If the end of the file has been
* reached, the routine has a choice of ERREXIT() or inserting fake data into
* the buffer.  In most cases, generating a warning message and inserting a
* fake EOI marker is the best course of action --- this will allow the
* decompressor to output however much of the image is there.  However,
* the resulting error message is misleading if the real problem is an empty
* input file, so we handle that case specially.
*
* In applications that need to be able to suspend compression due to input
* not being available yet, a FALSE return indicates that no more data can be
* obtained right now, but more may be forthcoming later.  In this situation,
* the decompressor will return to its caller (with an indication of the
* number of scanlines it has read, if any).  The application should resume
* decompression after it has loaded more data into the input buffer.  Note
* that there are substantial restrictions on the use of suspension --- see
* the documentation.
*
* When suspending, the decompressor will back up to a convenient restart point
* (typically the start of the current MCU). next_input_byte & bytes_in_buffer
* indicate where the restart point will be if the current call returns FALSE.
* Data beyond this point must be rescanned after resumption, so move it to
* the front of the buffer rather than discarding it.
*/

METHODDEF(boolean)
fill_input_buffer (j_decompress_ptr cinfo)
{
	my_src_ptr src = (my_src_ptr) cinfo->src;
	
	if (src->pInpBuf->IsEmpty())
		return FALSE;
	
	if (src->cByteToSkip > 0)
	{
		if (src->pInpBuf->Length <= src->cByteToSkip)
		{
			src->cByteToSkip -= src->pInpBuf->Length;
			src->pInpBuf->Empty();
			return FALSE;
		}

		src->pInpBuf->Delete(0,src->cByteToSkip);
		src->cByteToSkip = 0;
	}

	src->pCurBuf->Attach(src->pInpBuf);

	src->pub.next_input_byte = (uchar*)(LPCVOID)(*src->pCurBuf);
	src->pub.bytes_in_buffer = src->pCurBuf->Length;
	
	return TRUE;
}


/*
* Skip data --- used to skip over a potentially large amount of
* uninteresting data (such as an APPn marker).
*
* Writers of suspendable-input applications must note that skip_input_data
* is not granted the right to give a suspension return.  If the skip extends
* beyond the data currently in the buffer, the buffer can be marked empty so
* that the next read will cause a fill_input_buffer call that can suspend.
* Arranging for additional bytes to be discarded before reloading the input
* buffer is the application writer's problem.
*/

METHODDEF(void)
skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
	if (num_bytes <= 0)
		return;

	my_src_ptr src = (my_src_ptr) cinfo->src;
	
	if (ulong(num_bytes) <= src->pub.bytes_in_buffer)
	{
		src->pub.next_input_byte += (size_t) num_bytes;
		src->pub.bytes_in_buffer -= (size_t) num_bytes;
		return;
	}

	src->cByteToSkip += (num_bytes - src->pub.bytes_in_buffer);
	src->pub.next_input_byte = NULL;
	src->pub.bytes_in_buffer = 0;
}


/*
* An additional method that can be provided by data source modules is the
* resync_to_restart method for error recovery in the presence of RST markers.
* For the moment, this source module just uses the default resync method
* provided by the JPEG library.  That method assumes that no backtracking
* is possible.
*/


/*
* Terminate source --- called by jpeg_finish_decompress
* after all data has been read.  Often a no-op.
*
* NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
* application must deal with any cleanup that should happen even
* for error exit.
*/

METHODDEF(void)
term_source (j_decompress_ptr cinfo)
{
	my_src_ptr src = (my_src_ptr) cinfo->src;
	src->bEOI = true;
}


/*
* Prepare for input from a stdio stream.
* The caller must have already opened the stream, and is responsible
* for closing it after finishing decompression.
*/

GLOBAL(void)
jpeg_xbData_src (j_decompress_ptr cinfo, xbData * pInpBuf, xbData * pCurBuf)
{
	my_src_ptr src;
	
	/* The source object and input buffer are made permanent so that a series
	* of JPEG images can be read from the same file by calling jpeg_stdio_src
	* only before the first one.  (If we discarded the buffer at the end of
	* one image, we'd likely lose the start of the next one.)
	* This makes it unsafe to use this manager and a different source
	* manager serially with the same JPEG object.  Caveat programmer.
	*/
	if (cinfo->src == NULL) 
	{	/* first time for this JPEG object? */
		cinfo->src = (struct jpeg_source_mgr *)
			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
			SIZEOF(my_source_mgr));
		src = (my_src_ptr) cinfo->src;
	}
	
	src = (my_src_ptr) cinfo->src;
	src->pub.init_source = init_source;
	src->pub.fill_input_buffer = fill_input_buffer;
	src->pub.skip_input_data = skip_input_data;
	src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
	src->pub.term_source = term_source;
	src->pInpBuf = pInpBuf;
	src->pCurBuf = pCurBuf;
	src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
	src->pub.next_input_byte = NULL; /* until buffer loaded */
}

void jpegDecompressor::jpegOpen()
{
	jpegClose();
	
	m_cinfo.err = jpeg_std_error(&m_jerr);
	jpeg_create_decompress(&m_cinfo);
	m_bJpegInit = true;
	
	/* Step 2: specify data source (eg, a file) */
	m_waitingDta.Empty();
	m_tmpBuf.Empty();
	jpeg_xbData_src(&m_cinfo, &m_waitingDta, &m_tmpBuf);
	m_jpegStep = jpegStep_ReadHeader;
}

int jpegDecompressor::jpegEndStayingData(int dataLg)
{
	my_src_ptr src = (my_src_ptr) m_cinfo.src;
	if (src->cByteToSkip != 0)
		xbThrow gdiEcompress("Bytes to skip staying at decompression end, can't get processed data length (JPEG plugin, bytes: "+xbString(src->cByteToSkip)+")");

	int dataStaying = src->pub.bytes_in_buffer;
	if (dataStaying > dataLg)
		xbThrow gdiEcompress("Too many data staying at decompression end, can't get processed data length (JPEG plugin, bytes staying: "+xbString(dataStaying)+", current bytes written: "+xbString(dataLg)+")");

	return dataLg - dataStaying;
}
