#include "advwin32.h"
#include "iff_ILBM.hpp"

IFF_IMPLEMENT_DYNCREATE("ILBM","BMHD",IlbmBmhdChunk)
IFF_IMPLEMENT_DYNCREATE("ILBM","CMAP",IlbmCmapChunk)
IFF_IMPLEMENT_DYNCREATE("ILBM","BODY",IlbmBodyChunk)
IFF_IMPLEMENT_DYNCREATE("ILBM","GRAB",IlbmGrabChunk)

namespace IFF
{
	void IlbmBmhdChunk::Serialize(Archive * pArchv)
	{
		pArchv->Transfer(width);
		pArchv->Transfer(height);
		pArchv->Transfer(xTopLeft);
		pArchv->Transfer(yTopLeft);
		pArchv->Transfer(nBitPlanes);
		pArchv->Transfer(eMasking);
		pArchv->Transfer(eCompression);
		pArchv->Transfer(flags);
		pArchv->Transfer(iTranspCol);
		pArchv->Transfer(xAspectRatio);
		pArchv->Transfer(yAspectRatio);
		pArchv->Transfer(xMax);
		pArchv->Transfer(yMax);
	}
	
	void IlbmCmapChunk::Serialize(Archive * pArchv)
	{
		if (pArchv->m_bIsLoading)
		{
			nEntries = pArchv->GetSize()/3;
			if (pTable) delete[] pTable;
			pTable = new RGBTriple [nEntries];
		}
		
		for (unsigned i=0; i<nEntries; ++i)
			pArchv->Transfer(pTable[i]);
	}
	
	IlbmCmapChunk::~IlbmCmapChunk()
	{
		if (pTable) delete[] pTable;
	}
	
	void IlbmBodyChunk::Serialize(Archive * pArchv)
	{
		if (pArchv->m_bIsLoading)
		{
			nSize = pArchv->GetSize();
			if (pData) delete[] pData;
			pData = new UBYTE [nSize];
		}
		
		pArchv->TransferBlock(pData,nSize);
	}

	bool IlbmBodyChunk::GetHeaderInfo() const
	{
		Chunk * pHdr = GetProperty("BMHD");
		if (!pHdr)
		{
			return false;
		}
		nWidth = static_cast<IlbmBmhdChunk *>(pHdr)->width;
		eCompression = static_cast<IlbmBmhdChunk *>(pHdr)->eCompression;
		nBitPlanes = static_cast<IlbmBmhdChunk *>(pHdr)->nBitPlanes;
		return true;
	}
	
	#ifndef IFF_READ_ONLY
		bool IlbmBodyChunk::BeginEncode()
		{
			if (!GetHeaderInfo())
			{
				return false;
			}
			
			pEncodeDst = new DataBlock;
			pEncodeSrc = new UBYTE [(nWidth+7)/8];
			
			nSizeNonCprss = 0;
			nSizeCprss = 0;
			
			return true;
		}

// The uninitialised part of byte is shifted out.
#ifdef _MSC_VER
#pragma warning(disable: 4701)
#endif
		bool IlbmBodyChunk::EncodeNextRow(unsigned const * pRow)
		{
			if (!pEncodeDst) return false;
			
			for (unsigned b=0; b<nBitPlanes; ++b)
			{
				UBYTE * pBuf = pEncodeSrc;
				
				unsigned byte;
				for (unsigned x=0; x<nWidth; ++x)
				{
					byte <<= 1;
					byte |= pRow[x]>>b & 1;
					
					if (7==(x&7)) *pBuf++ = static_cast<UBYTE>(byte);
				}
				if (nWidth & 7)
				{
					byte <<= 8-(nWidth & 7);
					*pBuf = static_cast<UBYTE>(byte);
				}
				
				if (eCompression)
				{
					nSizeNonCprss += (nWidth+7)/8;
					
					UBYTE const * pBuf = pEncodeSrc;
					unsigned i=(nWidth+7)/8;
					
					while (i)
					{
						if (1==i)
						{
							pEncodeDst->Append(0);
							pEncodeDst->Append(*pBuf);
							nSizeCprss += 2;
							i=0;
						}
						else if (i>1)
						{
							if (pBuf[0]==pBuf[1])
							{
								unsigned j=2;
								while (j<i && j<0x80 && pBuf[j-1]==pBuf[j])
									++j;
								pEncodeDst->Append(static_cast<UBYTE>(0x101-j));
								pEncodeDst->Append(*pBuf);
								pBuf += j;
								i -= j;
								nSizeCprss += 2;
							}
							else
							{
								unsigned j=2;
								while (j<i && j<0x80 && (pBuf[j]!=pBuf[j-1] || pBuf[j-1]!=pBuf[j-2]))
									++j;
								if (j<i && pBuf[j]==pBuf[j-1] && pBuf[j-1]==pBuf[j-2])
									j-=2;
								pEncodeDst->Append(static_cast<UBYTE>(j-1));
								pEncodeDst->Append(pBuf,j);
								pBuf += j;
								i -= j;
								nSizeCprss += j+1;
							}
						}
					}
					
				}
				else
				{
					pEncodeDst->Append(pEncodeSrc,(nWidth+7)/8);
				}
			}
			
			return true;
		}
#ifdef _MSC_VER
#pragma warning(default: 4701)
#endif
		
		bool IlbmBodyChunk::EndEncode()
		{
			if (!pEncodeDst) return false;
			
			if (pData) delete[] pData;
			nSize = pEncodeDst->GetDataSize();
			pData = new UBYTE[nSize];
			
			memcpy(pData,pEncodeDst->GetDataPtr(),nSize);
			
			delete pEncodeDst;
			delete[] pEncodeSrc;
			
			pEncodeDst = NULL;
			pEncodeSrc = NULL;
			
			return true;
		}
	#endif
	
	bool IlbmBodyChunk::BeginDecode() const
	{
		if (pDecodeDst) delete[] pDecodeDst;
		if (!pData || !GetHeaderInfo())
		{
			pDecodeSrc = NULL;
			pDecodeDst = NULL;
			return false;
		}
		
		pDecodeSrc = pData;
		nRemaining = nSize;
		
		pDecodeDst = new unsigned [nWidth];
		
		return pData != NULL;
	}

// The uninitialised part of pDecodeDst is shifted out.
#ifdef _MSC_VER
#pragma warning(disable: 4701)
#endif
	unsigned const * IlbmBodyChunk::DecodeNextRow() const
	{
		if (!pDecodeSrc || !pDecodeDst) return NULL;
		
		for (unsigned x=0; x<nWidth; ++x)
			pDecodeDst[x]=0;
			
		if (eCompression)
		{
			unsigned repcnt = 0;
			unsigned rawcnt = 0;
			
			for (unsigned b=0; b<nBitPlanes; ++b)
			{
				unsigned byte;
				for (unsigned x=0; x<nWidth; ++x)
				{
					if (!(x&7))
					{
						if (!nRemaining) return NULL;
						
					REPEAT_SKIP:
						byte = *pDecodeSrc;
						
						if (rawcnt)
						{
							--rawcnt;
							++pDecodeSrc;
							--nRemaining;
						}
						else if (repcnt)
						{
							--repcnt;
							if (!repcnt)
							{
								++pDecodeSrc;
								--nRemaining;
							}
						}
						else // byte is control byte
						{
							++pDecodeSrc;
							--nRemaining;
							
							if (!nRemaining) return NULL;
							
							if (byte<0x80)
							{
								rawcnt = byte;
								byte = *pDecodeSrc++;
								--nRemaining;
							}
							else if (byte>0x80)
							{
								repcnt = 0x100 - byte;
								byte = *pDecodeSrc;
							}
							else goto REPEAT_SKIP;
						}
					}
					
					pDecodeDst[x] |= (byte>>7 & 1)<<b;
					
					byte <<= 1;
				}
				
			}
		}
		else
		{
			for (unsigned b=0; b<nBitPlanes; ++b)
			{
				unsigned byte;
				for (unsigned x=0; x<nWidth; ++x)
				{
					if (!(x&7))
					{
						if (!nRemaining) return NULL;
						
						byte = *pDecodeSrc++;
						--nRemaining;
					}
					
					pDecodeDst[x] |= (byte>>7 & 1)<<b;
					
					byte <<= 1;
				}
				
			}
		}
			
		
		return pDecodeDst;
	}
#ifdef _MSC_VER
#pragma warning(default: 4701)
#endif
	
	IlbmBodyChunk::~IlbmBodyChunk()
	{
		if (pData) delete[] pData;
		if (pDecodeDst) delete[] pDecodeDst;
		#ifndef IFF_READ_ONLY
			if (pEncodeDst) delete pEncodeDst;
			if (pEncodeSrc) delete[] pEncodeSrc;
		#endif
	}
	
	void IlbmGrabChunk::Serialize(Archive * pArchv)
	{
		pArchv->Transfer(xHotSpot);
		pArchv->Transfer(yHotSpot);
	}
}
