#include "advwin32.h"
#ifndef DB_LEVEL
#define DB_LEVEL 4
#endif
#include "db.h"
#ifndef DB_COMMA
	#define DB_COMMA ,
#endif

#pragma warning(disable: 4701)
#include "awTexLd.hpp"
#pragma warning(default: 4701)

#include "iff.hpp"
#include "iff_ILBM.hpp"

#include "list_tem.hpp"

#include <limits.h>

// conversion functors for IFF loader
class AwIffConvNull
{
	public:
		static inline unsigned DoConv (unsigned const * _colP, AwTl::Colour const * db_code1(DB_COMMA unsigned))
		{
			db_assert1(AwTl::pixelFormat.palettizedB);
			return *_colP;
		}
};

class AwIffConvNonTransp : public AwTl::Colour::ConvNonTransp
{
	public:
		static inline unsigned DoConv(unsigned const * pCol, AwTl::Colour const * pPalette db_code1(DB_COMMA unsigned nPaletteSize))
		{
			db_assert1(pPalette);
			db_onlyassert1(*pCol < nPaletteSize);
			return AwTl::Colour::ConvNonTransp::DoConv(&pPalette[*pCol]);
		}
};

class AwIffConvTransp
{
	public:
		static unsigned iTranspCol; // the index of the transparent colour
		static unsigned rawTranspCol; // the value of a transparent pixel on the surface

		static inline unsigned DoConv(unsigned const * pCol, AwTl::Colour const * pPalette db_code1(DB_COMMA unsigned nPaletteSize))
		{
			using namespace AwTl;

			if (*pCol == iTranspCol) return rawTranspCol;
			unsigned rv = AwIffConvNonTransp::DoConv(pCol,pPalette db_code1(DB_COMMA nPaletteSize));
			if (rv != rawTranspCol) return rv;
			
			// make the colour non-transparent (nb: only an occasional case)
			
			// OK, Here's the plan:
			
			// First, suppose that I were to decrease either the Red, Green or Blue
			// component in order to make the colour non-transparent.
			// Taking Red as an example, I'll work out what the resultant red value
			// will be if I decrease red to achieve a non-transparent colour
			// I'll compare this to the actual red value of the colour in question
			// The lower the difference, the better it'll be (ie. closer to the
			// original colour).
			// Obviously, I'll do the same for Green and Blue
			// Then I'll repeat the process but this time considering increasing
			// the components.
			// I'll then have six values which are scores (the lower the better)
			// for what colour component to change and how to change it.
			// If any of the components cannot be decreased (ie. their resulting
			// value is already zero) or increased (ie. their resulting value
			// is at maximum), then I'll set the corresponding score to
			// UINT_MAX so that that colour-changing operation won't be selected
			// (because that'll be the one that buggers everything up).
			
			unsigned nRedDiffDown   =
				(pPalette[*pCol].r < 1<<pixelFormat.redRightShift  ) ? UINT_MAX
					: (pPalette[*pCol].r & (1<<pixelFormat.redRightShift  )-1) + ((1<<pixelFormat.redRightShift  )+1)/2;
					
			unsigned nGreenDiffDown =
				(pPalette[*pCol].g < 1<<pixelFormat.greenRightShift) ? UINT_MAX
					: (pPalette[*pCol].g & (1<<pixelFormat.greenRightShift)-1) + ((1<<pixelFormat.greenRightShift)+1)/2;
					
			unsigned nBlueDiffDown  =
				(pPalette[*pCol].b < 1<<pixelFormat.blueRightShift ) ? UINT_MAX
					: (pPalette[*pCol].b & (1<<pixelFormat.blueRightShift )-1) + ((1<<pixelFormat.blueRightShift )+1)/2;
			
			unsigned nRedDiffUp   =
				(pPalette[*pCol].r >= (255 & ~((1<<pixelFormat.redRightShift  )-1) ) ? UINT_MAX
					: (1<<pixelFormat.redRightShift  )*3/2 - (pPalette[*pCol].r & (1<<pixelFormat.redRightShift  )-1));
					
			unsigned nGreenDiffUp =
				(pPalette[*pCol].g >= (255 & ~((1<<pixelFormat.greenRightShift)-1) ) ? UINT_MAX
					: (1<<pixelFormat.greenRightShift)*3/2 - (pPalette[*pCol].g & (1<<pixelFormat.greenRightShift)-1));
					
			unsigned nBlueDiffUp  =
				(pPalette[*pCol].b >= (255 & ~((1<<pixelFormat.blueRightShift )-1) ) ? UINT_MAX
					: (1<<pixelFormat.blueRightShift )*3/2 - (pPalette[*pCol].b & (1<<pixelFormat.blueRightShift )-1));
			
			// Pick lowest value and do the business
			
			Colour colAdj = pPalette[*pCol];
			
			#if defined(_MSC_VER) && _MSC_VER >= 1100
				// VC5.0 gives inane warnings when += type operators
				// are used on types smaller than int (even with
				// explicit casting!)
				#pragma warning(disable:4244)
			#endif
			if
			(
				   nBlueDiffUp <= nBlueDiffDown
				&& nBlueDiffUp <= nRedDiffUp
				&& nBlueDiffUp <= nRedDiffDown
				&& nBlueDiffUp <= nGreenDiffUp
				&& nBlueDiffUp <= nGreenDiffDown
			)
			{
				colAdj.b += static_cast<unsigned char>(1<<pixelFormat.blueRightShift);
			}
			else if
			(
				   nBlueDiffDown <= nRedDiffUp
				&& nBlueDiffDown <= nRedDiffDown
				&& nBlueDiffDown <= nGreenDiffUp
				&& nBlueDiffDown <= nGreenDiffDown
			)
			{
				colAdj.b -= static_cast<unsigned char>(1<<pixelFormat.blueRightShift);
			}
			else if
			(
				   nRedDiffUp <= nRedDiffDown
				&& nRedDiffUp <= nGreenDiffUp
				&& nRedDiffUp <= nGreenDiffDown
			)
			{
				colAdj.r += static_cast<unsigned char>(1<<pixelFormat.redRightShift);
			}
			else if
			(
				   nRedDiffDown <= nGreenDiffUp
				&& nRedDiffDown <= nGreenDiffDown
			)
			{
				colAdj.r -= static_cast<unsigned char>(1<<pixelFormat.redRightShift);
			}
			else if (nGreenDiffUp <= nGreenDiffDown)
			{
				colAdj.g += static_cast<unsigned char>(1<<pixelFormat.greenRightShift);
			}
			else
			{
				colAdj.g -= static_cast<unsigned char>(1<<pixelFormat.greenRightShift);
			}
			#if defined(_MSC_VER) && _MSC_VER == 1100
				// VC5.0 gives inane warnings when += type operators
				// are used on types smaller than int (even with
				// explicit casting!)
				#pragma warning(default:4244)
			#endif
			
			return Colour::ConvNonTransp::DoConv(&colAdj);
		}
};

unsigned AwIffConvTransp::iTranspCol;
unsigned AwIffConvTransp::rawTranspCol;

// IFF Loader

class AwIffLoader : public AwTl::TexFileLoader
{
	public:
		AwIffLoader() : m_pPalette(NULL), m_bDecoding(false) {}
	protected:
		virtual ~AwIffLoader();
	
		virtual void LoadHeaderInfo(MediaMedium * pMedium);

		virtual unsigned GetNumColours();
		
		virtual unsigned GetMinPaletteSize();

		virtual bool HasTransparentMask(bool bDefault);
		
		virtual void AllocateBuffers(bool bWantBackup, unsigned nMaxPaletteSize);
		
		virtual void OnBeginRestoring(unsigned nMaxPaletteSize);
		
		virtual AwTl::Colour * GetPalette();
		
		
		virtual AwTl::PtrUnion GetRowPtr(unsigned nRow);
		
		virtual void LoadNextRow(AwTl::PtrUnion pRow);
		
		virtual void ConvertRow(AwTl::PtrUnion pDest, unsigned nDestWidth, AwTl::PtrUnionConst pSrc, unsigned nSrcOffset, unsigned nSrcWidth, AwTl::Colour * pPalette db_code1(DB_COMMA unsigned nPaletteSize));
		
		virtual DWORD GetTransparentColour();
		
		virtual void OnFinishLoading(bool bSuccess);
		
		virtual void OnFinishRestoring(bool bSuccess);
		
		virtual AwBackupTexture * CreateBackupTexture();
		
	private:
		static bool Enumerator(IFF::Chunk * pChunk, void * pData);
		// list of chunks found by enumerator
		List<IFF::IlbmBodyChunk *> m_listBodyChunks;
		
		// smallest and largest palette sizes of versions of this image
		unsigned m_nMaxPaletteSize;
		unsigned m_nMinPaletteSize;
		
		// buffer for palette -
		// since the IFF cmap table is in a different format to what the Aw loaders require
		// (maybe should think about standardizing the data types?)
		AwTl::Colour * m_pPalette;
		
		// iff data
		IFF::File m_ifData;
		IFF::IlbmBmhdChunk * m_pHdr;
		IFF::IlbmCmapChunk * m_pCmap;
		IFF::IlbmBodyChunk * m_pBody;
		
		bool m_bDecoding;
};

AwIffLoader::~AwIffLoader()
{
	if (m_pPalette) delete[] m_pPalette;
}

void AwIffLoader::LoadHeaderInfo(MediaMedium * pMedium)
{
	db_log4("\tLoading an IFF file");
	
	while (m_listBodyChunks.size())
		m_listBodyChunks.delete_first_entry();
	
	if (!m_ifData.Load(pMedium) || !m_ifData.GetContents())
	{
		if (NO_ERROR == (awTlLastWinErr = GetLastError()))
			awTlLastErr = AW_TLE_BADFILEDATA;
		else
			awTlLastErr = AW_TLE_CANTREADFILE;
			
		db_log3("AwCreateTexture(): ERROR: IFF file load failed");
	}
	else
	{
		m_nMinPaletteSize = UINT_MAX;
		m_nMaxPaletteSize = 0;
		m_ifData.GetContents()->EnumChildren("ILBM","BODY",Enumerator,this);
	}
}

unsigned AwIffLoader::GetNumColours()
{
	return m_nMaxPaletteSize;
}

unsigned AwIffLoader::GetMinPaletteSize()
{
	return m_nMinPaletteSize;
}

void AwIffLoader::AllocateBuffers(bool /*bWantBackup*/, unsigned nMaxPaletteSize)
{
	// we will need to allocate buffers when restoring as well as first-time loading
	// so allocate buffers in OnBeginRestoring() which we'll call here

	OnBeginRestoring(nMaxPaletteSize);
}

bool AwIffLoader::Enumerator(IFF::Chunk * pChunk, void * pData)
{
	db_assert1(pChunk);
	db_assert1(pData);
	AwIffLoader * pThis = static_cast<AwIffLoader *>(pData);
	
	IFF::Chunk * pCmap = pChunk->GetProperty("CMAP");
	IFF::Chunk * pHdr = pChunk->GetProperty("BMHD");
	if (pCmap && pHdr) // must have these two properties
	{
		unsigned nThisPaletteSize = static_cast<IFF::IlbmCmapChunk *>(pCmap)->nEntries;
		db_logf4(("\tfound a %u colour %scompressed IFF body chunk",nThisPaletteSize,static_cast<IFF::IlbmBmhdChunk *>(pHdr)->eCompression ? "" : "un"));
		
		pThis->m_listBodyChunks.add_entry(static_cast<IFF::IlbmBodyChunk *>(pChunk));
		
		if (nThisPaletteSize < pThis->m_nMinPaletteSize)
			pThis->m_nMinPaletteSize = nThisPaletteSize;
		
		if (nThisPaletteSize > pThis->m_nMaxPaletteSize)
			pThis->m_nMaxPaletteSize = nThisPaletteSize;
	}
	else db_log3("AwCreateTexture(): WARNING: IFF body chunk found with insufficient associated property chunks");
	
	return true; // continue enumeration
}

void AwIffLoader::OnBeginRestoring(unsigned nMaxPaletteSize)
{
	using namespace AwTl;
	
	if (m_listBodyChunks.size())
	{
		// if decodeing, m_pBody will be valid
		if (m_bDecoding)
		{
			m_pBody->EndDecode();
			m_bDecoding = false;
		}
		
		m_pBody = NULL;
		unsigned nBestPaletteSize = 0;
		
		for (LIF<IFF::IlbmBodyChunk *> itChunks(&m_listBodyChunks); !itChunks.done(); itChunks.next())
		{
			IFF::IlbmCmapChunk * pCmap = static_cast<IFF::IlbmCmapChunk *>(itChunks()->GetProperty("CMAP"));
			db_assert1(pCmap);
			
			if ((!nMaxPaletteSize || pCmap->nEntries <= nMaxPaletteSize) && pCmap->nEntries > nBestPaletteSize)
			{
				m_pBody = itChunks();
				m_pCmap = pCmap;
				nBestPaletteSize = pCmap->nEntries;
			}
		}
		
		if (m_pBody)
		{
			m_pHdr = static_cast<IFF::IlbmBmhdChunk *>(m_pBody->GetProperty("BMHD"));
			// delete old buffers
			if (m_pPalette) delete[] m_pPalette;
			// allocate buffer for palette, make it extra big to cope with corrupt files
			unsigned nAllocPaletteSize = m_pCmap->nEntries;
			unsigned nAllocPaletteSizeShift = 0;
			while (0!=(nAllocPaletteSize >>= 1))
			{
				++nAllocPaletteSizeShift;
			}
			m_pPalette = new Colour [2<<nAllocPaletteSizeShift]; // prevent corrupt data causing a crash
			//m_pPalette = new Colour [m_pCmap->nEntries];
			// copy the palette
			for (unsigned i=0; i<m_pCmap->nEntries; ++i)
			{
				// hacked testa
				m_pPalette[i].r = m_pCmap->pTable[i].r;
				m_pPalette[i].g = m_pCmap->pTable[i].g;
				m_pPalette[i].b = m_pCmap->pTable[i].b;
			}
			// set the width height and palette size in the base class
			m_nPaletteSize = m_pCmap->nEntries;
			m_nWidth = m_pHdr->width;
			m_nHeight = m_pHdr->height;
			// prepare to decode the data			
			m_pBody->BeginDecode();
			m_bDecoding = true;
			// set the transparent mask colours
			switch (m_pHdr->eMasking)
			{
				case IFF::IlbmBmhdChunk::MASK_NONE:
					break;
				case IFF::IlbmBmhdChunk::MASK_TRANSPARENTCOL:
					AwIffConvTransp::iTranspCol = m_pHdr->iTranspCol;
					if (pixelFormat.palettizedB)
						AwIffConvTransp::rawTranspCol = AwIffConvTransp::iTranspCol;
					else
						AwIffConvTransp::rawTranspCol = 
							 static_cast<unsigned>(m_pPalette[AwIffConvTransp::iTranspCol].r)>>pixelFormat.redRightShift<<pixelFormat.redLeftShift
							|static_cast<unsigned>(m_pPalette[AwIffConvTransp::iTranspCol].g)>>pixelFormat.greenRightShift<<pixelFormat.greenLeftShift
							|static_cast<unsigned>(m_pPalette[AwIffConvTransp::iTranspCol].b)>>pixelFormat.blueRightShift<<pixelFormat.blueLeftShift;
					break;
				default:
					db_log3("AwCreateTexture(): ERROR: IFF mask field wrong");
					awTlLastErr = AW_TLE_BADFILEDATA;
			}
		}
		else
		{
	 		awTlLastErr = AW_TLE_CANTPALETTIZE; // no suitable chunk found
		 	db_log3("AwCreateTexture(): ERROR: No suitable IFF body chunk found");
	 	}
	}
	else
	{
	 	awTlLastErr = AW_TLE_BADFILEDATA;
	 	db_log3("AwCreateTexture(): ERROR: IFF file not loaded or contains no image data");
	}
}

AwTl::Colour * AwIffLoader::GetPalette()
{
	return m_pPalette;
}

bool AwIffLoader::HasTransparentMask(bool bDefault)
{
	if (m_listBodyChunks.size())
	{
		IFF::IlbmBmhdChunk * pHdr = static_cast<IFF::IlbmBmhdChunk *>(m_listBodyChunks.first_entry()->GetProperty("BMHD"));
		db_assert1(pHdr);
		return (IFF::IlbmBmhdChunk::MASK_TRANSPARENTCOL == pHdr->eMasking);
	}
	else
		return bDefault;
}

DWORD AwIffLoader::GetTransparentColour()
{
	return AwIffConvTransp::rawTranspCol;
}

AwTl::PtrUnion AwIffLoader::GetRowPtr(unsigned /*nRow*/)
{
	// the iff object has an internal buffer to which a pointer
	// is returned when we decode each row
	// unfortunately we have to cast constness away, but never mind
	return const_cast<unsigned *>(m_pBody->DecodeNextRow());
}

void AwIffLoader::LoadNextRow(AwTl::PtrUnion /*pRow*/)
{
	// GetRowPtr() has called DecodeNextRow()
	// which has filled in the data already
	// so do nothing here
}

void AwIffLoader::ConvertRow(AwTl::PtrUnion pDest, unsigned nDestWidth, AwTl::PtrUnionConst pSrc, unsigned nSrcOffset, unsigned nSrcWidth, AwTl::Colour * pPalette db_code1(DB_COMMA unsigned nPaletteSize))
{
	using namespace AwTl;
	
	// we have overridden this function for two reasons:
	// 1. The data type for each texel in the row is unsigned int
	//    to allow for the fact that all images are palettized
	//    with no limit on the palette size. The default
	//    implementation would assume BYTE (unsigned char)
	// 2. The transparency flag and colour is stored in the
	//    file and the transparency flag passed to AwCreateTexture()
	//    is ignored. The transparent colour does not have to be 0,0,0
	//    either
	
	db_assert1(pPalette);
	db_assert1(pPalette == m_pPalette);
	// we can still use GenericConvertRow though, using the conversion functors above
	
	if (pixelFormat.palettizedB)
	{
		GenericConvertRow<AwIffConvNull,unsigned>::Do(pDest, nDestWidth, pSrc.uintP+nSrcOffset, nSrcWidth);
	}
	else
	{
		switch (m_pHdr->eMasking)
		{
			case IFF::IlbmBmhdChunk::MASK_NONE:
				GenericConvertRow<AwIffConvNonTransp,unsigned>::Do(pDest, nDestWidth, pSrc.uintP+nSrcOffset, nSrcWidth, pPalette db_code1(DB_COMMA nPaletteSize));
				break;
			case IFF::IlbmBmhdChunk::MASK_TRANSPARENTCOL:
				GenericConvertRow<AwIffConvTransp,unsigned>::Do(pDest, nDestWidth, pSrc.uintP+nSrcOffset, nSrcWidth, pPalette db_code1(DB_COMMA nPaletteSize));
				break;
			default:
				db_log3("AwCreateTexture(): ERROR: IFF mask field wrong");
				awTlLastErr = AW_TLE_BADFILEDATA;
		}
	}
}

void AwIffLoader::OnFinishLoading(bool /*bSuccess*/)
{
	if (m_bDecoding)
	{
		m_pBody->EndDecode();
		m_bDecoding = false;
	}
}

void AwIffLoader::OnFinishRestoring(bool /*bSuccess*/)
{
	if (m_bDecoding)
	{
		m_pBody->EndDecode();
		m_bDecoding = false;
	}
}
		
AwBackupTexture * AwIffLoader::CreateBackupTexture()
{
	// use the same object for restoring
	AddRef();
	return this;
}

// Valid file ID fields: 'FORM' 'LIST' 'CAT ' - we can load them all
#ifdef _MSC_VER
	// VC5.0 tries to compile out code that is in a library
	// and it thinks isn't being used
	#line 427
#endif
AWTEXLD_IMPLEMENT_DYNCREATE("FORM",AwIffLoader)
AWTEXLD_IMPLEMENT_DYNCREATE("LIST",AwIffLoader)
AWTEXLD_IMPLEMENT_DYNCREATE("CAT ",AwIffLoader)
