#ifndef _INCLUDED_AWTEXLD_HPP_
#define _INCLUDED_AWTEXLD_HPP_

#include "awTexLd.h"
#include "media.hpp"
#include "db.h"
#ifndef DB_COMMA
	#define DB_COMMA ,
#endif

// Nasty hack to touch the classes so MSVC++ doesn't discards them.
#ifdef _MSC_VER
	extern class AwTlRegisterLoaderClass_AwBmpLoader_187 rlcAwBmpLoader_187;
	
	extern class AwTlRegisterLoaderClass_AwIffLoader_428 rlcAwIffLoader_428;
	extern class AwTlRegisterLoaderClass_AwIffLoader_429 rlcAwIffLoader_429;
	extern class AwTlRegisterLoaderClass_AwIffLoader_430 rlcAwIffLoader_430;

	extern class AwTlRegisterLoaderClass_AwPpmLoader_229 rlcAwPpmLoader_229;
	extern class AwTlRegisterLoaderClass_AwPgmLoader_230 rlcAwPgmLoader_230;
	extern class AwTlRegisterLoaderClass_AwPbmLoader_231 rlcAwPbmLoader_231;

	extern class RegisterChunkClassIlbmBmhdChunk_4 rccIlbmBmhdChunk_4;
	extern class RegisterChunkClassIlbmCmapChunk_5 rccIlbmCmapChunk_5;
	extern class RegisterChunkClassIlbmBodyChunk_6 rccIlbmBodyChunk_6;
	extern class RegisterChunkClassIlbmGrabChunk_7 rccIlbmGrabChunk_7;

#endif

namespace AwTl {

	#define CANT_HAPPEN db_msgf1(("AwCreateTexture(): (Line %u) CAN'T HAPPEN!",__LINE__));
	
	/*********************************/
	/* Pixel format global structure */
	/*********************************/
	
	struct PixelFormat
	{
		PixelFormat() : validB(false){}
		
		bool palettizedB : 1;
		bool alphaB : 1;
		bool validB : 1;
		
		unsigned bitsPerPixel;
		unsigned redLeftShift;
		unsigned redRightShift;
		unsigned greenLeftShift;
		unsigned greenRightShift;
		unsigned blueLeftShift;
		unsigned blueRightShift;
		
		DDPIXELFORMAT ddpf;
	};

	// DO SOMTHING ABOUT THIS
	extern PixelFormat pixelFormat;

	class CreateTextureParms;
	
	/********************/
	/* Colour structure */
	/********************/

	struct Colour
	{
		BYTE r,g,b;
		
		class ConvNonTransp
		{
			public:
				static inline unsigned DoConv (Colour const * _colP, Colour const * = NULL db_code1(DB_COMMA unsigned = 0))
				{
					return
						 static_cast<unsigned>(_colP->r)>>pixelFormat.redRightShift<<pixelFormat.redLeftShift
						|static_cast<unsigned>(_colP->g)>>pixelFormat.greenRightShift<<pixelFormat.greenLeftShift
						|static_cast<unsigned>(_colP->b)>>pixelFormat.blueRightShift<<pixelFormat.blueLeftShift
						|pixelFormat.ddpf.dwRGBAlphaBitMask
					;
				}
				static inline unsigned DoConv(BYTE const * _colP, Colour const * _paletteP db_code1(DB_COMMA unsigned _paletteSize))
				{
					db_assert1(_paletteP);
					db_onlyassert1(*_colP < _paletteSize);
					return DoConv(&_paletteP[*_colP]);
				}
		};

		class ConvTransp
		{
			private:
				static inline unsigned MakeNonTranspCol(Colour const * _colP)
				{
					unsigned rv = ConvNonTransp::DoConv(_colP);
					if (rv) return rv;
					// make one of r,g or b in the output == 1, choose the one which is closest to the input
					unsigned rdiff = (1<<pixelFormat.redRightShift) - _colP->r;
					unsigned bdiff = (1<<pixelFormat.blueRightShift) - _colP->b;
					unsigned gdiff = (1<<pixelFormat.greenRightShift) - _colP->g;
					if (bdiff<=rdiff && bdiff<=gdiff)
						return 1<<pixelFormat.blueLeftShift;
					else if (rdiff<=gdiff)
						return 1<<pixelFormat.redLeftShift;
					else
						return 1<<pixelFormat.greenLeftShift;
				}
			public:
				static inline unsigned DoConv (Colour const * _colP, Colour const * = NULL db_code1(DB_COMMA unsigned = 0))
				{
					if (!_colP->b && !_colP->r && !_colP->g)
						//return pixelFormat.alphaB ? pixelFormat.ddsd.ddpfPixelFormat.dwRBitMask|pixelFormat.ddsd.ddpfPixelFormat.dwGBitMask|pixelFormat.ddsd.ddpfPixelFormat.dwBBitMask : 0;
						return 0;
					else
						return MakeNonTranspCol(_colP);
				}
				static inline unsigned DoConv(BYTE const * _colP, Colour const * _paletteP db_code1(DB_COMMA unsigned _paletteSize))
				{
					db_assert1(_paletteP);
					db_onlyassert1(*_colP < _paletteSize);
					if (!*_colP)
						//return pixelFormat.alphaB ? pixelFormat.ddsd.ddpfPixelFormat.dwRBitMask|pixelFormat.ddsd.ddpfPixelFormat.dwGBitMask|pixelFormat.ddsd.ddpfPixelFormat.dwBBitMask : 0;
						return 0;
					else
						return MakeNonTranspCol(&_paletteP[*_colP]);
				}
		};
		
		class ConvNull
		{
			public:
				static inline unsigned DoConv (BYTE const * _colP, Colour const * db_code1(DB_COMMA unsigned = 0))
				{
					db_assert1(pixelFormat.palettizedB);
					return *_colP;
				}
		};
	};

	/*****************/
	/* Pointer union */
	/*****************/
	
	union SurfUnion
	{
		D3DTexture * textureP;
		DDSurface * surfaceP;
		void * voidP;
		SurfUnion(){}
		SurfUnion(D3DTexture * p) : textureP(p){}
		SurfUnion(DDSurface * p) : surfaceP(p){}
	};

	union PtrUnion
	{
		void * voidP;
		char * charP;
		signed char * scharP;
		unsigned char * ucharP;
		BYTE * byteP;
		short * shortP;
		unsigned short * ushortP;
		WORD * wordP;
		signed * intP;
		unsigned * uintP;
		DWORD * dwordP;
		long * longP;
		unsigned long * ulongP;
		Colour * colourP;
		
		inline PtrUnion(){}
		inline PtrUnion(void * _voidP):voidP(_voidP){}
		inline operator void * () const { return voidP; }
	};

	union PtrUnionConst
	{
		void const * voidP;
		char const * charP;
		signed char const * scharP;
		unsigned char const * ucharP;
		BYTE const * byteP;
		short const * shortP;
		unsigned short const * ushortP;
		WORD const * wordP;
		signed const * intP;
		unsigned const * uintP;
		DWORD const * dwordP;
		long const * longP;
		unsigned long const * ulongP;
		Colour const * colourP;
		
		inline PtrUnionConst(){}
		inline PtrUnionConst(void const * _voidP):voidP(_voidP){}
		inline PtrUnionConst(PtrUnion _uP):voidP(_uP.voidP){}
		inline operator void const * () const { return voidP; }
	};
	
	/***************************************/
	/* Generic copying to surface function */
	/***************************************/
	
	template<class CONVERT, class SRCTYPE>
	class GenericConvertRow
	{
		public:
			static void Do (PtrUnion _dstRowP, unsigned _dstWidth, SRCTYPE const * _srcRowP, unsigned _srcWidth, Colour const * _paletteP = NULL db_code1(DB_COMMA unsigned _paletteSize = 0));
	};

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4701)
#endif

	template<class CONVERT, class SRCTYPE>
	void GenericConvertRow<CONVERT, SRCTYPE>::Do (PtrUnion _dstRowP, unsigned _dstWidth, SRCTYPE const * _srcRowP, unsigned _srcWidth, Colour const * _paletteP db_code1(DB_COMMA unsigned _paletteSize))
	{
		switch (pixelFormat.bitsPerPixel)
		{
			default:
				CANT_HAPPEN
			case 16:
			{
				db_assert1(!pixelFormat.palettizedB);
				for (unsigned colcount = _srcWidth; colcount; --colcount)
				{
					*_dstRowP.wordP++ = static_cast<WORD>(CONVERT::DoConv(_srcRowP++,_paletteP db_code1(DB_COMMA _paletteSize)));
				}
				if (_srcWidth<_dstWidth)
					*_dstRowP.wordP = static_cast<WORD>(CONVERT::DoConv(_srcRowP-1,_paletteP db_code1(DB_COMMA _paletteSize)));
				break;
			}
			case 24:
			{
				db_assert1(!pixelFormat.palettizedB);
				union { DWORD dw; BYTE b[3]; } u;
				for (unsigned colcount = _srcWidth; colcount; --colcount)
				{
					u.dw = static_cast<DWORD>(CONVERT::DoConv(_srcRowP++,_paletteP db_code1(DB_COMMA _paletteSize)));
					*_dstRowP.byteP++ = u.b[0];
					*_dstRowP.byteP++ = u.b[1];
					*_dstRowP.byteP++ = u.b[2];
				}
				if (_srcWidth<_dstWidth)
				{
					*_dstRowP.byteP++ = u.b[0];
					*_dstRowP.byteP++ = u.b[1];
					*_dstRowP.byteP = u.b[2];
				}
			}
			case 32:
			{
				db_assert1(!pixelFormat.palettizedB);
				for (unsigned colcount = _srcWidth; colcount; --colcount)
				{
					*_dstRowP.dwordP++ = static_cast<DWORD>(CONVERT::DoConv(_srcRowP++,_paletteP db_code1(DB_COMMA _paletteSize)));
				}
				if (_srcWidth<_dstWidth)
					*_dstRowP.dwordP = static_cast<DWORD>(CONVERT::DoConv(_srcRowP-1,_paletteP db_code1(DB_COMMA _paletteSize)));
				break;
			}
			case 8:
			{
				for (unsigned colcount = _srcWidth; colcount; --colcount)
				{
					*_dstRowP.byteP++ = static_cast<BYTE>(CONVERT::DoConv(_srcRowP++,_paletteP db_code1(DB_COMMA _paletteSize)));
				}
				if (_srcWidth<_dstWidth)
					*_dstRowP.byteP = static_cast<BYTE>(CONVERT::DoConv(_srcRowP-1,_paletteP db_code1(DB_COMMA _paletteSize)));
				break;
			}
			case 1:
			case 2:
				db_assert1(pixelFormat.palettizedB);
			case 4:
			{
				unsigned shift=0;
				unsigned val;
				--_dstRowP.byteP; // decrement here because we increment before the first write
				for (unsigned colcount = _srcWidth; colcount; --colcount)
				{
					val = CONVERT::DoConv(_srcRowP++,_paletteP db_code1(DB_COMMA _paletteSize));
					if (!shift)
						*++_dstRowP.byteP = static_cast<BYTE>(val);
					else
						*_dstRowP.byteP |= static_cast<BYTE>(val<<shift);
					shift += pixelFormat.bitsPerPixel;
					shift &= 7;
				}
				if (_srcWidth<_dstWidth)
				{
					if (!shift)
						*++_dstRowP.byteP = static_cast<BYTE>(val);
					else
						*_dstRowP.byteP |= static_cast<BYTE>(val<<shift);
				}
				break;
			}
		}
	}
	
#ifdef _MSC_VER
#pragma warning(pop)
#endif

	// reference counting support
	class RefCntObj
	{
		public:
			unsigned AddRef() { return ++m_nRefCnt; }
			unsigned Release() { if (0==(--m_nRefCnt)) { delete this; return 0;} else return m_nRefCnt; }
		protected:
			virtual ~RefCntObj(){
				#ifndef NDEBUG
					DbForget(this);
				#endif
			}
			RefCntObj() : m_nRefCnt(1){
				#ifndef NDEBUG
					DbRemember(this);
				#endif
			}
			RefCntObj(RefCntObj const &) : m_nRefCnt(1){
				#ifndef NDEBUG
					DbRemember(this);
				#endif
			}
			RefCntObj & operator = (RefCntObj const &){ return *this;}
		private:		
			unsigned m_nRefCnt;
			
		#ifndef NDEBUG
			friend void DbRemember(RefCntObj * pObj);
			friend void DbForget(RefCntObj * pObj);
			friend class AllocList;
		#endif
	};
	
	SurfUnion LoadFromParams(CreateTextureParms *);

} // namespace AwTl

struct AwBackupTexture : public AwTl::RefCntObj
{
	public:
		AwTl::SurfUnion Restore(AwTl::CreateTextureParms const & rParams);
	protected:
		AwTl::SurfUnion CreateTexture(AwTl::CreateTextureParms const & rParams);
	
		void ChoosePixelFormat(AwTl::CreateTextureParms const & rParams);
		
		virtual ~AwBackupTexture(){}
		
		// return the number of unique colours in the image or zero if this cannot be determined
		virtual unsigned GetNumColours() = 0;
		
		// return the smallest palette size that is available for the image
		virtual unsigned GetMinPaletteSize() = 0;
		
		// return true if the image has a single transparent colour
		virtual bool HasTransparentMask(bool bDefault);
		
		// called when a backup texture is about to be used for restoring, but after the above two functions have been called
		virtual void OnBeginRestoring(unsigned nMaxPaletteSize);
		
		virtual AwTl::Colour * GetPalette() = 0;
		
		virtual bool AreRowsReversed();
		
		virtual AwTl::PtrUnion GetRowPtr(unsigned nRow) = 0;
		
		virtual void LoadNextRow(AwTl::PtrUnion pRow) = 0;
		
		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 OnFinishRestoring(bool bSuccess);
		
		// metrics
		unsigned m_nWidth;
		unsigned m_nHeight;
		unsigned m_nPaletteSize; // 0 inicates no palette

		unsigned m_fFlags;
		
	private:
		bool m_bTranspMask;
		
	friend AwTl::SurfUnion AwTl::LoadFromParams(AwTl::CreateTextureParms *);
};

namespace AwTl {

	class TypicalBackupTexture : public ::AwBackupTexture
	{
		public:
			TypicalBackupTexture(AwBackupTexture const & rBase, PtrUnion * ppPixMap, Colour * pPalette)
				: AwBackupTexture(rBase)
				, m_ppPixMap(ppPixMap)
				, m_pPalette(pPalette)
			{}
			
			virtual ~TypicalBackupTexture()
			{
				if (m_pPalette)
				{
					delete[] m_pPalette;
					if (m_ppPixMap)
					{
						delete[] m_ppPixMap->byteP;
						delete[] m_ppPixMap;
					}
				}
				else
				{
					if (m_ppPixMap)
					{
						delete[] m_ppPixMap->colourP;
						delete[] m_ppPixMap;
					}
				}
			}
			
			virtual Colour * GetPalette();
			
			virtual PtrUnion GetRowPtr(unsigned nRow);
			
			virtual void LoadNextRow(PtrUnion pRow);
			
			// note: the palette size member must be set in
			// LoadHeaderInfo() for these functions to work correctly
			virtual unsigned GetNumColours();
			
			virtual unsigned GetMinPaletteSize();
			
		private:
			PtrUnion * m_ppPixMap;
			Colour * m_pPalette;
	};

	class TexFileLoader : public AwBackupTexture
	{
		public:
			SurfUnion Load(MediaMedium * pMedium, CreateTextureParms const & rParams);

		protected:
			// standard constructor
			// & destructor
			
			// Interface Functions. Each overridden version should set awTlLastErr
			// when an error occurs
		
			// Called to set the width and height members; the palette size member can also be safely set at this point.
			// Neither the width height or palette size members need actually be set until AllocateBuffers returns
			virtual void LoadHeaderInfo(MediaMedium * pMedium) = 0;
			
			// should ensure that the palette size is set
			virtual void AllocateBuffers(bool bWantBackup, unsigned nMaxPaletteSize) = 0;
			
			virtual void OnFinishLoading(bool bSuccess);
			
			virtual AwBackupTexture * CreateBackupTexture() = 0;
	};

	class TypicalTexFileLoader : public TexFileLoader
	{
		protected:
			TypicalTexFileLoader() : m_pRowBuf(NULL), m_ppPixMap(NULL), m_pPalette(NULL) {}
		
			virtual ~TypicalTexFileLoader();
		
			virtual unsigned GetNumColours();
			
			virtual unsigned GetMinPaletteSize();
		
			virtual void AllocateBuffers(bool bWantBackup, unsigned nMaxPaletteSize);
			
			virtual PtrUnion GetRowPtr(unsigned nRow);
			
			virtual AwBackupTexture * CreateBackupTexture();
		
			Colour * m_pPalette;
		private:
			PtrUnion * m_ppPixMap;
			
			PtrUnion m_pRowBuf;
	};
	
	extern void RegisterLoader(char const * pszMagic, AwTl::TexFileLoader * (* pfnCreate) () );
	
} // namespace AwTl

#define AWTEXLD_IMPLEMENT_DYNCREATE(pszMagic, tokenClassName) _AWTEXLD_IMPLEMENT_DYNCREATE_LINE_EX(pszMagic,tokenClassName,__LINE__)
#define _AWTEXLD_IMPLEMENT_DYNCREATE_LINE_EX(pszMagic, tokenClassName, nLine) _AWTEXLD_IMPLEMENT_DYNCREATE_LINE(pszMagic,tokenClassName,nLine)

#define _AWTEXLD_IMPLEMENT_DYNCREATE_LINE(pszMagic,tokenClassName,nLine) \
	AwTl::TexFileLoader * AwTlCreateClassObject ##_## tokenClassName ##_## nLine () { \
		return new tokenClassName; \
	} \
	class AwTlRegisterLoaderClass ##_## tokenClassName ##_## nLine { \
		public: AwTlRegisterLoaderClass ##_## tokenClassName ##_## nLine () { \
			AwTl::RegisterLoader(pszMagic, AwTlCreateClassObject ##_## tokenClassName ##_## nLine); \
		} \
	} rlc ## tokenClassName ##_## nLine;

#endif // ! _INCLUDED_AWTEXLD_HPP_
