#ifndef _included__d3_image_hpp_
#define _included__d3_image_hpp_

#error "This file is obsolete"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "list_tem.hpp"

#include "ddraw.h"
#include "d3d.h"
#include "vramtime.h"

#include "system.h"
#include "mem3dc.h"

#include "dxlog.h"
// define = 1 if you want an output log file of all texture files loaded and for which DD surface format
#if debug
#define OUTPUT_LOG 1
#else
#define OUTPUT_LOG 0
#endif

// define = 1 to use fast files for loading (will still try normal files if fastfile doesn't contain correct data)
#define USE_FASTFILE 1

#if USE_FASTFILE

#include "ffstdio.h"
#define D3I_FILE FFILE
#define d3i_fpos_t ffpos_t
#define d3i_fclearerr ffclearerr
#define d3i_fclose ffclose
#define d3i_fcloseall ffcloseall
#define d3i_feof ffeof
#define d3i_ferror fferror
#define d3i_fgetc ffgetc
#define d3i_fgetpos ffgetpos
#define d3i_fgets ffgets
#define d3i_flook fflook
#define d3i_flookb fflookb
#define d3i_fopen ffopen
#define d3i_fread ffread
#define d3i_freadb ffreadb
#define d3i_fseek ffseek
#define d3i_fsetpos ffsetpos
#define d3i_ftell fftell

#else

#define D3I_FILE FILE
#define d3i_fpos_t fpos_t
#define d3i_fclearerr fclearerr
#define d3i_fclose fclose
#define d3i_fcloseall fcloseall
#define d3i_feof feof
#define d3i_ferror ferror
#define d3i_fgetc fgetc
#define d3i_fgetpos fgetpos
#define d3i_fgets fgets
#define d3i_fopen fopen
#define d3i_fread fread
#define d3i_fseek fseek
#define d3i_fsetpos fsetpos
#define d3i_ftell ftell

#endif


// define = 1 if you do not want to handle the error return codes, and require a load failure to cause an exit
#define EXIT_ON_LOAD_FAIL 0

// define = 1 if you are locating images through rif files and expect this to always succeed
// works like EXIT_ON_LOAD_FAIL, and only if EXIT_ON_LOAD_FAIL is set, but most failures are handled
// by assuming the given name to be a complete path and filename
#define EXIT_ON_LOCATE_FAIL 0

// define = 1 to time file opens, file closes and file reads
#define TIME_LOADS 0

// I tested the times with a typically complex set of images from AVP,
// loaded over a nework.
// Opening took 16secs, Reading 38secs and Closing 2secs for approx
// for about 500 images (including mip maps)

#if TIME_LOADS
#include <time.h>
#define START_TIMER {clock_t __stime = clock();
#define END_TIMER(__var) __var += (unsigned int) ((clock() - __stime) * 1000 / CLOCKS_PER_SEC);}
#else
#define START_TIMER
#define END_TIMER(__var)
#endif

#if TIME_LOADS
extern unsigned int OpenTime;
extern unsigned int CloseTime;
extern unsigned int ReadTime;
#endif

#if OUTPUT_LOG
#include "debuglog.hpp"
extern LogFile CL_LogFile;
#endif

// externs for 3dc things
extern "C" {
	extern LPDIRECTDRAW lpDD;
	extern DDPIXELFORMAT DisplayPixelFormat;
};

enum CL_RGBFormat // for loading from files
{
	CLF_RGB, CLF_BGR, CLF_BGRX
};

struct CL_Pixel_24
{
	union
	{
		struct
		{
			unsigned char r,g,b;
			unsigned char xx;
		};
		unsigned long l;
	};

	CL_Pixel_24() : l(0) {}
	CL_Pixel_24(unsigned char const r,unsigned char const g,unsigned char const b) : r(r),g(g),b(b),xx(0) {}
	CL_Pixel_24(CL_Pixel_24 const & p24) : l(p24.l) {}
	CL_Pixel_24(unsigned long const i) : l(i) {}

	operator unsigned long (void) const { return l; }

	inline void Read (D3I_FILE * const f, CL_RGBFormat const t)
	{
		switch(t)
		{
			case CLF_RGB:
				d3i_fread(&r,1,1,f);
				d3i_fread(&g,1,1,f);
				d3i_fread(&b,1,1,f);
				break;
			case CLF_BGR:
				d3i_fread(&b,1,1,f);
				d3i_fread(&g,1,1,f);
				d3i_fread(&r,1,1,f);
				break;
			case CLF_BGRX:
				d3i_fread(&b,1,1,f);
				d3i_fread(&g,1,1,f);
				d3i_fread(&r,1,1,f);
				d3i_fseek(f,1,SEEK_CUR);
			default:
				break;
		}
	}

	inline void Read (D3I_FILE * const f, CL_RGBFormat const t, unsigned int const maxval)
	{
		Read(f,t);
		if (maxval != 255)
		{
			r = (unsigned char) ( ((unsigned int)r << 8) / (maxval+1) );
			g = (unsigned char) ( ((unsigned int)g << 8) / (maxval+1) );
			b = (unsigned char) ( ((unsigned int)b << 8) / (maxval+1) );
		}
	}

	unsigned int FVD_Distance(CL_Pixel_24 const & p2) const;
};


struct CL_DX_Format
{
	unsigned int
		red_mask,
		red_shift,
		red_bits,
		red_bits_gt8,
		red_bits_lt8,
		blue_mask,
		blue_shift,
		blue_bits,
		blue_bits_gt8,
		blue_bits_lt8,
		green_mask,
		green_shift,
		green_bits,
		green_bits_gt8,
		green_bits_lt8;

	CL_Pixel_24 dx_black;

	inline void Init(unsigned int rmask,unsigned int gmask,unsigned int bmask)
	{
		red_mask = rmask;
		blue_mask = bmask;
		green_mask = gmask;
		for (red_shift = 0; !(rmask & 1); red_shift++, rmask>>=1);
		for (green_shift = 0; !(gmask & 1); green_shift++, gmask>>=1);
		for (blue_shift = 0; !(bmask & 1); blue_shift++, bmask>>=1);
		for (red_bits = 0; rmask; red_bits++, rmask>>=1);
		for (green_bits = 0; gmask; green_bits++, gmask>>=1);
		for (blue_bits = 0; bmask; blue_bits++, bmask>>=1);
		if (blue_bits >= green_bits && blue_bits >= red_bits)
		{
			dx_black = CL_Pixel_24(0,0,1);
		}
		else if (red_bits >= green_bits)
		{
			dx_black = CL_Pixel_24(1,0,0);
		}
		else
		{
			dx_black = CL_Pixel_24(0,1,0);
		}
		if (red_bits >= 8)
		{
			red_bits_gt8 = red_bits-8;
			red_bits_lt8 = 0;
		}
		else
		{
			red_bits_lt8 = 8-red_bits;
			red_bits_gt8 = 0;
		}
		if (green_bits >= 8)
		{
			green_bits_gt8 = green_bits-8;
			green_bits_lt8 = 0;
		}
		else
		{
			green_bits_lt8 = 8-green_bits;
			green_bits_gt8 = 0;
		}
		if (blue_bits >= 8)
		{
			blue_bits_gt8 = blue_bits-8;
			blue_bits_lt8 = 0;
		}
		else
		{
			blue_bits_lt8 = 8-blue_bits;
			blue_bits_gt8 = 0;
		}
	}
};


// for 32 bit and 24 bit DirectX formats
template <class S>
struct CL_Pixel_T
{
	static CL_DX_Format f;
	static CL_DX_Format f_d3d;
	static CL_DX_Format f_ddraw;

	S p;

	inline S r(void) const { return (S)((p & f.red_mask)>>f.red_shift); }
	inline S g(void) const { return (S)((p & f.green_mask)>>f.green_shift); }
	inline S b(void) const { return (S)((p & f.blue_mask)>>f.blue_shift); }

	CL_Pixel_T() {}
	CL_Pixel_T(CL_Pixel_T<S> const & p32) : p(p32.p) {}
	CL_Pixel_T(CL_Pixel_24 const & p24)
	: p((S)(
		(S)p24.r>>f.red_bits_lt8<<f.red_shift+f.red_bits_gt8 |
		(S)p24.g>>f.green_bits_lt8<<f.green_shift+f.green_bits_gt8 |
		(S)p24.b>>f.blue_bits_lt8<<f.blue_shift+f.blue_bits_gt8 ))
	{
		// make the 32bit pixel non-black to avoid transparency problems
		if (!p && p24.l) // if the 24bit pixel is not black and the 32 bit pixel is black
		{
			// choose which of r,g or b to make non-zero - try and get as close to black as poss
			if ((1<<f.blue_bits_lt8)-p24.b <= (1<<f.red_bits_lt8)-p24.r && (1<<f.blue_bits_lt8)-p24.b <= (1<<f.green_bits_lt8)-p24.g)
				p = (S)(1<<f.blue_shift);
			else if ((1<<f.red_bits_lt8)-p24.r <= (1<<f.green_bits_lt8)-p24.g)
				p = (S)(1<<f.red_shift);
			else
				p = (S)(1<<f.green_shift);
		}
	}

	inline operator CL_Pixel_24 (void) const
	{
		S rr = (S)(r()>>f.red_bits_gt8<<f.red_bits_lt8);
		S gg = (S)(g()>>f.green_bits_gt8<<f.green_bits_lt8);
		S bb = (S)(b()>>f.blue_bits_gt8<<f.blue_bits_lt8);

		if (!(rr|gg|bb) && p)
		{
			if (b()<<f.red_bits>=r()<<f.blue_bits && b()<<f.green_bits>=g()<<f.blue_bits) // make the 16bit pixel non-black to avoid transparency problems
				bb = 1;
			else if (r()<<f.green_bits>=g()<<f.red_bits)
				rr = 1;
			else
				gg = 1;
		}
		
		return CL_Pixel_24((unsigned char)rr,(unsigned char)gg,(unsigned char)bb);
	};
	operator S (void) const { return p; }

	inline void Read (D3I_FILE * const f, CL_RGBFormat const t)
	{
		CL_Pixel_24 p24;
		p24.Read(f,t);
		*this = (CL_Pixel_T<S>)p24;
	}
	
	inline void Read (D3I_FILE * const f, CL_RGBFormat const t, unsigned int const maxval)
	{
		CL_Pixel_24 p24;
		p24.Read(f,t,maxval);
		*this = (CL_Pixel_T<S>)p24;
	}
};


typedef CL_Pixel_T<unsigned long> CL_Pixel_32;
typedef CL_Pixel_T<unsigned short> CL_Pixel_16;


enum CL_Error
{
	CLE_OK,
	CLE_LOADERROR, // file cannot be loaded - format is wrong
	CLE_OPENERROR, // file cannot be opened - does not exist?
	CLE_FINDERROR, // file cannot be found - not listed in .RIF file
	CLE_RIFFERROR, // rif file not loaded, or invalid
	CLE_INVALIDGAMEMODE, // specified game mode does not exist
	CLE_INVALIDDXMODE, // video mode is invalid
	CLE_DXERROR, // other direct X related error
	CLE_ALLOCFAIL // failed memory allocation
};


enum CL_ImageMode {

	CLM_GLOBALPALETTE, // image shares a global palette in a palettized mode
	CLM_TLTPALETTE, // images may also share an abstract palette which remaps via a tlt to a global display palette
	CLM_ATTACHEDPALETTE, // 256 colour image with attached palette
	CLM_16BIT, // 16 bit in specified RGB format
	CLM_32BIT, // 32 bit in specified RGB format
	CLM_24BIT // 24 bit truecolour image in 888 format
};
// Note:
// currently 24-bit and 16-bit image formats are not output.
// If the desired format is 24-bit or 16-bit then 256 colour BMPs are loaded
// and 'unquantized' to generate the required format.


enum CL_LoadMode {

	CLL_D3DTEXTURE,
	CLL_DDSURFACE
};

void CL_Select_Mode(CL_LoadMode const lmode);


void CL_Init_D3DMode(LPDDSURFACEDESC const format);

enum CL_VideoMode {

	CLV_8,
	CLV_15,
	CLV_24,
	CLV_8T,
	CLV_8TLT
};

void CL_Init_DirectDrawMode(CL_VideoMode const vmode);



// test!!!
#if HwTextureHack
static int craptest = 0;
#endif

#if EXIT_ON_LOAD_FAIL
	extern "C"
	{
		#include "3dc.h"
	}
	#if EXIT_ON_LOCATE_FAIL
		#define EXITONLOCATEFAIL(__errcode,__iname,__enum_id) \
			if (CLE_RIFFERROR != __errcode) { \
				if (__iname) textprint("Cannot figure path for:\n%s\n",__iname); \
				else textprint("Cannot figure path for:\nImage ID %d\n",__enum_id); \
				WaitForReturn(); \
				ExitSystem(); \
				exit(0x10cafa11); \
			}
	#else
		#define EXITONLOCATEFAIL(__errcode,__iname,__enum_id)
	#endif
	#define EXITONLOADFAIL(__iname) \
		{ \
			textprint("Cannot open:\n%s\n",__iname); \
			WaitForReturn(); \
			ExitSystem(); \
			exit(0x10adfa11); \
		}
	#define EXITONREADFAIL(__iname) \
		{ \
			textprint("Cannot read:\n%s\n",__iname); \
			WaitForReturn(); \
			ExitSystem(); \
			exit(0x10adfa11); \
		}
#else
	#define EXITONLOCATEFAIL(__errcode,__iname,__enum_id)
	#define EXITONLOADFAIL(__iname)
	#define EXITONREADFAIL(__iname)
#endif

struct CL_Flags
{
	unsigned int loaded : 1;
	unsigned int located : 1;
	unsigned int raw16bit : 1;
	unsigned int tltpalette : 1;

	CL_Flags()
		: loaded(0)
		, located(0)
		, tltpalette(0)
		, raw16bit(0)
			{}
};

#define CL_EID_INVALID (-1)


#if 0
template <class I>
class CL_MIP_Image
{
public:
	unsigned int num_mipmaps;
	I * * mipmaps; // array of CL_Image pointers of decreasing image size

	unsigned int width; // == mipmaps[0]->width
	unsigned int height; // == mipmaps[0]->height
	unsigned int size; // == mipmaps[0]->size

	char * fname; // full filename (including directory/path) of mipmap with index 0
	char * name; // name of image without directory/path or extension

	CL_MIP_Image() : mipmaps(0), num_mipmaps(0), fname(0), name(0) {}
	~CL_MIP_Image() { Delete(); }

	CL_MIP_Image(CL_MIP_Image<I> const & i2)
		: width (i2.width)
		, height (i2.height)
		, size (i2.size)
		, num_mipmaps (i2.num_mipmaps)
		{ Copy(i2); }

	CL_MIP_Image<I> & operator = (CL_MIP_Image<I> const & i2)
	{
		if (&i2 != this)
		{
			Delete();

			width = i2.width;
			height = i2.height;
			size = i2.size;
			num_mipmaps = i2.num_mipmaps;

			Copy(i2);
		}
		return *this;
	}

private:
	void Copy(CL_MIP_Image<I> const & i2)
	{
		if (i2.mipmaps)
		{
			mipmaps = new I * [num_mipmaps];
			for (int i=0; i<num_mipmaps; ++i)
			{
				mipmaps[i] = new I(*i2.mipmaps[i]);
			}
		}

		if (i2.fname)
		{
			fname = new char[strlen(i2.fname)+1];
			strcpy(fname,i2.fname);
		}

		if (i2.name)
		{
			name = new char[strlen(i2.name)+1];
			strcpy(name,i2.name);
		}
	}
	void Delete(void)
	{
		if (mipmaps)
		{
			for (int i=0; i<num_mipmaps; ++i) delete mipmaps[i];
			delete[] mipmaps;
			mipmaps = 0;
		}

		if (name)
		{
			delete[] name;
			name = 0;
		}
		
		if (fname)
		{
			delete[] fname;
			fname = 0;
		}
	}

	CL_Flags flags;

public:
	inline CL_Error Load(int const enum_id)
	{
		return Load(0,enum_id);
	}
	inline CL_Error Load(char const * const iname)
	{
		return Load(iname,CL_EID_INVALID);
	}
	inline CL_Error Load()
	{
		return Load(0,CL_EID_INVALID);
	}

	inline CL_Error PreLoad(int const enum_id)
	{
		return PreLoad(0,enum_id);
	}
	inline CL_Error PreLoad(char const * const iname)
	{
		return PreLoad(iname,CL_EID_INVALID);
	}

	CL_Error CopyToScanDrawTexture(unsigned char * * const ImagePtrA [], unsigned int maxnummips)
	{
		if (!flags.loaded) return CLE_LOADERROR;

		if (CLL_DDSURFACE != I::lmode) CL_Select_Mode(CLL_DDSURFACE);

		if (num_mipmaps < maxnummips) maxnummips = num_mipmaps;
		if (!maxnummips) return CLE_ALLOCFAIL;

		if (!*ImagePtrA[0]) *ImagePtrA[0] = (unsigned char *) AllocateMem((2*width+1)*(2*height+1)*I::bitsperpixel/24+(maxnummips-3)*I::bitsperpixel/8); // slightly more than 4/3 w*h*bytedepth
		
		for (int i=0; i<maxnummips; ++i)
		{
			if (i) *ImagePtrA[i] = *ImagePtrA[i-1] + mipmaps[i-1]->width*mipmaps[i-1]->height*(I::bitsperpixel>>3);
			CL_Error thismipmaperror = mipmaps[i]->CopyToScanDrawTexture(ImagePtrA[i]);
			if (CLE_OK != thismipmaperror) return thismipmaperror;
		}
		return CLE_OK;
	}

	CL_Error CopyToD3DTexture(LPDIRECTDRAWSURFACE * const DDPtrA [], LPVOID * const DDSurfaceA [], unsigned int maxnummips, int const MemoryType)
	{
		if (!flags.loaded) return CLE_LOADERROR;

		WaitForVRamReady(VWS_D3DTEXCREATE);

		if (CLL_D3DTEXTURE != I::lmode) CL_Select_Mode(CLL_D3DTEXTURE);

		LPDIRECTDRAWSURFACE lpDDS;
		DDSURFACEDESC ddsd;
		HRESULT ddrval;

		if (width & 3 || height & 3)
		{
			// return error code
			return CLE_DXERROR;
		}

		/* test !!! */
		{
		#if HwTextureHack
		craptest++;
		if (craptest > 10)
			return CLE_DXERROR;
		#endif
		}


		if (num_mipmaps < maxnummips) maxnummips = num_mipmaps;
		// Set up the mip-mapped surface description. starting
		// with the passed texture format and then
		// incorporating the information read from the
		// ppm.
		memcpy(&ddsd, I::format, sizeof(DDSURFACEDESC));
		ddsd.dwSize = sizeof(DDSURFACEDESC);
		ddsd.dwFlags = (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
			| DDSD_MIPMAPCOUNT);
		ddsd.dwMipMapCount = maxnummips; // engine standard, primary plus six mip-maps
		ddsd.ddsCaps.dwCaps = (DDSCAPS_TEXTURE | DDSCAPS_MIPMAP
			| DDSCAPS_COMPLEX | MemoryType);
		ddsd.dwHeight = height;
		ddsd.dwWidth = width;


		// Create the surface
		ddrval = lpDD->CreateSurface(&ddsd, &lpDDS, NULL);
		LOGDXERR(ddrval);

		if (ddrval != DD_OK)
		{
			#if debug
			ReleaseDirect3D();
			exit(ddrval);
			#else
			lpDDS->Release();
			return CLE_DXERROR;
			#endif
		}

		// We must now traverse the mip-map chain from highest to lowest
		// resolutions,  For each surface AFTER the first one, we must 
		// load a new file, using a name obtained from the mip map number

		int MipMapNum = 0;
		LPDIRECTDRAWSURFACE lpThisMipMap, lpNextMipMap;
		DDSCAPS ddsCaps;


		lpThisMipMap = lpDDS;
		// Component Object Model, increase reference count on 
		// mip-map surface by one.
		lpThisMipMap->AddRef(); 
		ddsCaps.dwCaps = (DDSCAPS_TEXTURE | DDSCAPS_MIPMAP);
		ddrval = DD_OK;

		while ((ddrval == DD_OK) && (MipMapNum < maxnummips)) // both tests in case...
		{
			// Call LoadPPMIntoDDSurface with lpThisMipMap, new file name, and
			// other values.

			*DDSurfaceA[MipMapNum] = mipmaps[MipMapNum]->CopyToDDSurface(lpThisMipMap);

			// Death trap
			if (!*DDSurfaceA[MipMapNum])
			{
				return CLE_DXERROR;
			}
			*DDPtrA[MipMapNum] = lpThisMipMap;

			// Proceed to the next level.
			// Collect bonus rings.
			ddrval = lpThisMipMap->GetAttachedSurface(&ddsCaps, &lpNextMipMap);
			// Necessary to match the manual increment of the reference count on the
			// COM texture.  I think.
			lpThisMipMap->Release();
			// ?? lpNextMipMap = lpThisMipMap;
			lpThisMipMap = lpNextMipMap;

			MipMapNum++;
		}

		return CLE_OK;

	} 

	CL_Error CopyToDirectDrawSurface(LPDIRECTDRAWSURFACE * const DDPtrA [], LPVOID * const DDSurfaceA [], unsigned int maxnummips, int const MemoryType)
	{
		if (!flags.loaded) return CLE_LOADERROR;

		WaitForVRamReady(VWS_DDCREATE);

		if (CLL_DDSURFACE != I::lmode) CL_Select_Mode(CLL_DDSURFACE);

		LPDIRECTDRAWSURFACE lpDDS;
		DDSURFACEDESC ddsd;
		HRESULT ddrval;

		if (width & 3 || height & 3)
		{
			// return error code
			return CLE_DXERROR;
		}

		if (num_mipmaps < maxnummips) maxnummips = num_mipmaps;
		// Set up the mip-mapped surface description. starting
		// with the passed texture format and then
		// incorporating the information read from the
		// ppm.
		memset(&ddsd, 0, sizeof ddsd);
		ddsd.dwSize = sizeof ddsd;
		ddsd.dwFlags = (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
			| DDSD_MIPMAPCOUNT);
		ddsd.dwMipMapCount = maxnummips; // engine standard, primary plus six mip-maps
		ddsd.ddsCaps.dwCaps = (DDSCAPS_OFFSCREENPLAIN | DDSCAPS_MIPMAP
			| DDSCAPS_COMPLEX | MemoryType);
		ddsd.dwHeight = height;
		ddsd.dwWidth = width;


		// Create the surface
		ddrval = lpDD->CreateSurface(&ddsd, &lpDDS, NULL);

		LOGDXERR(ddrval);
		if (ddrval != DD_OK)
		{
			#if debug
			ReleaseDirect3D();
			exit(ddrval);
			#else
			lpDDS->Release();
			return CLE_DXERROR;
			#endif
		}

		DDCOLORKEY set_zero = {0,0};
		ddrval = lpDDS->SetColorKey(DDCKEY_SRCBLT, &set_zero);

		LOGDXERR(ddrval);
		if(ddrval != DD_OK)
		{
			#if debug
			ReleaseDirect3D();
			exit(ddrval);
			#else
			lpDDS->Release();
			return CLE_DXERROR;
			#endif
		}

		// We must now traverse the mip-map chain from highest to lowest
		// resolutions,  For each surface AFTER the first one, we must 
		// load a new file, using a name obtained from the mip map number

		int MipMapNum = 0;
		LPDIRECTDRAWSURFACE lpThisMipMap, lpNextMipMap;
		DDSCAPS ddsCaps;


		lpThisMipMap = lpDDS;
		// Component Object Model, increase reference count on 
		// mip-map surface by one.
		lpThisMipMap->AddRef(); 
		ddsCaps.dwCaps = (DDSCAPS_TEXTURE | DDSCAPS_MIPMAP);
		ddrval = DD_OK;

		while ((ddrval == DD_OK) && (MipMapNum < maxnummips)) // both tests in case...
		{
			// Call LoadPPMIntoDDSurface with lpThisMipMap, new file name, and
			// other values.

			*DDSurfaceA[MipMapNum] = mipmaps[MipMapNum]->CopyToDDSurface(lpThisMipMap);

			// Death trap
			if (!*DDSurfaceA[MipMapNum])
			{
				return CLE_DXERROR;
			}
			*DDPtrA[MipMapNum] = lpThisMipMap;

			// Proceed to the next level.
			// Collect bonus rings.
			ddrval = lpThisMipMap->GetAttachedSurface(&ddsCaps, &lpNextMipMap);
			// Necessary to match the manual increment of the reference count on the
			// COM texture.  I think.
			lpThisMipMap->Release();
			// ?? lpNextMipMap = lpThisMipMap;
			lpThisMipMap = lpNextMipMap;

			MipMapNum++;
		}

		return CLE_OK;

	} 

private:
	// I was using Dan's list template for this,
	// but since it is not a standard part of 3DC,
	// I have had to write a specific simple list handler
	struct TempListMember
	{
		I * mip;
		TempListMember * next;
		TempListMember(I * data) : mip(data), next(0) {}
		//TempListMember() : next(0) {}
		~TempListMember() { if (next) delete next; }
	};
	struct TempList
	{
		unsigned int n_entries;
		TempListMember * first;
		TempListMember * last;
		TempList(I * data) : first(new TempListMember(data)), n_entries(1) { last = first; }
		TempList & operator += (I * data)
		{
			if (!first)
			{
				first = new TempListMember(data);
				last = first;
			}
			else
			{
				last->next = new TempListMember(data);
				last = last->next;
			}
			++ n_entries;
			return *this;
		}
		~TempList()
		{
			if (first) delete first;
		}
	};
	
	CL_Error Load(char const * const iname, int const enum_id)
	{
		if (iname || CL_EID_INVALID != enum_id) flags.located = 0;
		
		I * mip0;
		
		if (flags.located && mipmaps)
		{
			mip0 = mipmaps[0];
			// we have grabbed the previously allocated pointer
			// it will now be treated as if it were allocated here,
			// so we must remove it from the class, in case the
			// deconstructor tries to deallocate it
			delete[] mipmaps;
			mipmaps = 0;
		}
		else
		{
			mip0 = new I;

			CL_Error locate_err = mip0->Locate(iname,enum_id);

			if (locate_err != CLE_OK)
			{
				delete mip0;
				EXITONLOCATEFAIL(locate_err,iname,enum_id)
				return locate_err;
			}
		}

		#if OUTPUT_LOG
		char const * texformat;
		unsigned int redbits = 8;
		unsigned int greenbits = 8;
		unsigned int bluebits = 8;
		switch (I::imode)
		{
		case CLM_GLOBALPALETTE:
		case CLM_TLTPALETTE:
			texformat = "Palettized Display";
			break;
		case CLM_ATTACHEDPALETTE:
			texformat = "Palettized Textures";
			break;
		case CLM_32BIT:
			texformat = "32BIT DX";
			redbits += CL_Pixel_32::f.red_bits_gt8;
			redbits -= CL_Pixel_32::f.red_bits_lt8;
			greenbits += CL_Pixel_32::f.green_bits_gt8;
			greenbits -= CL_Pixel_32::f.green_bits_lt8;
			bluebits += CL_Pixel_32::f.blue_bits_gt8;
			bluebits -= CL_Pixel_32::f.blue_bits_lt8;
			break;
		case CLM_24BIT:
			texformat = "24-bit for runtime conversion";
			break;
		case CLM_16BIT:
			texformat = "16BIT DX";
			redbits += CL_Pixel_16::f.red_bits_gt8;
			redbits -= CL_Pixel_16::f.red_bits_lt8;
			greenbits += CL_Pixel_16::f.green_bits_gt8;
			greenbits -= CL_Pixel_16::f.green_bits_lt8;
			bluebits += CL_Pixel_16::f.blue_bits_gt8;
			bluebits -= CL_Pixel_16::f.blue_bits_lt8;
			break;
		}
		CL_LogFile.lprintf("--%s %s (%u-bit, %u-%u-%u)\n",mip0->fname,texformat,I::bitsperpixel,redbits,greenbits,bluebits);
		#endif

		START_TIMER
		D3I_FILE * fp = d3i_fopen(mip0->fname,"rb");
		END_TIMER(OpenTime)

		if (!fp)
		{
			#if OUTPUT_LOG
			CL_LogFile.lputs("** ERROR: unable to open\n");
			#endif
			EXITONLOADFAIL(mip0->fname)
			delete mip0;
			return CLE_OPENERROR;
		}

		CL_Error load_err = mip0->Load_Image(fp);
		START_TIMER
		d3i_fclose(fp);
		END_TIMER(CloseTime)

		if (load_err != CLE_OK)
		{
			if (!flags.located) delete mip0;
			#if OUTPUT_LOG
			CL_LogFile.lputs("** ERROR: unable to read\n");
			#endif
			EXITONREADFAIL(mip0->fname)
			return load_err;
		}

		TempList miplist(mip0);

		width = mip0->width;
		height = mip0->height;
		size = mip0->size;

		if (!flags.located)
		{
			if (fname) delete[] fname;
			if (name) delete[] name;
			fname = new char[strlen(mip0->fname)+1];
			name = new char[strlen(mip0->name)+1];
			strcpy(fname,mip0->fname);
			strcpy(name,mip0->name);
		}

		for (int mip_idx = 1; mip_idx < 7; ++mip_idx)
		{
			I * mip_n;
			
			mip_n = new I;
			mip_n->name = new char[strlen(name)+1];
			mip_n->fname = new char[strlen(fname)+1];
			strcpy(mip_n->name,name);
			strcpy(mip_n->fname,fname);

			char * dotpos = strrchr(mip_n->fname,'.');
			if (!dotpos)
			{
				delete mip_n;
				break;
			}
			sprintf(dotpos+3,"%1d",mip_idx);

			#if OUTPUT_LOG
			char const * texformat;
			unsigned int redbits = 8;
			unsigned int greenbits = 8;
			unsigned int bluebits = 8;
			switch (I::imode)
			{
			case CLM_GLOBALPALETTE:
			case CLM_TLTPALETTE:
				texformat = "Palettized Display";
				break;
			case CLM_ATTACHEDPALETTE:
				texformat = "Palettized Textures";
				break;
			case CLM_32BIT:
				texformat = "32BIT DX";
				redbits += CL_Pixel_32::f.red_bits_gt8;
				redbits -= CL_Pixel_32::f.red_bits_lt8;
				greenbits += CL_Pixel_32::f.green_bits_gt8;
				greenbits -= CL_Pixel_32::f.green_bits_lt8;
				bluebits += CL_Pixel_32::f.blue_bits_gt8;
				bluebits -= CL_Pixel_32::f.blue_bits_lt8;
				break;
			case CLM_24BIT:
				texformat = "24-bit for runtime conversion";
				break;
			case CLM_16BIT:
				texformat = "16BIT DX";
				redbits += CL_Pixel_16::f.red_bits_gt8;
				redbits -= CL_Pixel_16::f.red_bits_lt8;
				greenbits += CL_Pixel_16::f.green_bits_gt8;
				greenbits -= CL_Pixel_16::f.green_bits_lt8;
				bluebits += CL_Pixel_16::f.blue_bits_gt8;
				bluebits -= CL_Pixel_16::f.blue_bits_lt8;
				break;
			}
			CL_LogFile.lprintf("--%s %s (%u-bit, %u-%u-%u)\n",mip_n->fname,texformat,I::bitsperpixel,redbits,greenbits,bluebits);
			#endif

			START_TIMER
			fp = d3i_fopen(mip_n->fname,"rb");
			END_TIMER(OpenTime)

			if (!fp)
			{
				delete mip_n;
				#if OUTPUT_LOG
				CL_LogFile.lputs("** Warning: unable to open\n");
				#endif
				break;
			}

			load_err = mip_n->Load_Image(fp);
			START_TIMER
			d3i_fclose(fp);
			END_TIMER(CloseTime)

			if (load_err != CLE_OK)
			{
				delete mip_n;
				#if OUTPUT_LOG
				CL_LogFile.lputs("** Warning: unable to read\n");
				#endif
				break;
			}

			if (mip_n->width << mip_idx < width || mip_n->height << mip_idx < height)
			{
				delete mip_n;
				#if OUTPUT_LOG
				CL_LogFile.lputs("** Warning: less than half size\n");
				#endif
				break;
			}

			miplist += mip_n;
		}

		if (mipmaps)
		{
			for (int i = 0; i<num_mipmaps; ++i) delete mipmaps[i];
			delete[] mipmaps;
		}

		num_mipmaps = miplist.n_entries;
		mipmaps = new I * [num_mipmaps];

		TempListMember * listP = miplist.first;
		for (int i=0; i<num_mipmaps; ++i)
		{
			mipmaps[i] = listP->mip;
			listP = listP->next;
		}

		flags.loaded = 1;
		flags.located = 1;
		return CLE_OK;
	}
	
	CL_Error PreLoad(char const * const iname, int const enum_id)
	{
		I * mip0 = new I;

		CL_Error locate_err = mip0->Locate(iname,enum_id);

		if (locate_err != CLE_OK)
		{
			delete mip0;
			return locate_err;
		}

		if (fname) delete[] fname;
		if (name) delete[] name;
		fname = new char[strlen(mip0->fname)+1];
		name = new char[strlen(mip0->name)+1];
		strcpy(fname,mip0->fname);
		strcpy(name,mip0->name);

		if (mipmaps)
		{
			for (int i=0; i<num_mipmaps; ++i) delete mipmaps[i];
			delete[] mipmaps;
		}

		num_mipmaps = 1;
		mipmaps = new I * [1];
		*mipmaps = mip0;
		
		flags.loaded = 0;
		flags.located = 1;

		return CLE_OK;
	}
};
#endif

struct CL_Image
{
public:
	static CL_ImageMode imode; // video mode defines which format images should be loaded
	static CL_ImageMode imode_d3d; // video mode defines which format images should be loaded
	static CL_ImageMode imode_ddraw; // video mode defines which format images should be loaded
	static LPDDSURFACEDESC format;
	static unsigned int bitsperpixel;
	static unsigned int bitsperpixel_d3d;
	static unsigned int bitsperpixel_ddraw;
	static CL_LoadMode lmode;

	// any one of these 3 may be valid
	union
	{ // array of 24 bit or 32 bit pixels
		CL_Pixel_24 * * im24; // array of 24 bit pixels
		CL_Pixel_32 * * im32; // array of 32 bit pixels
		// the data storage sizes of CL_Pixel_24 and CL_Pixel_32 must match
	};
	union
	{
		CL_Pixel_16 * * im16; // array of 16 bit pixels
		unsigned short * * im16raw; // raw double-byte pixel data
	};
	unsigned char * * im8; // array of entrys into CLUT
	
	unsigned int palette_size;
	CL_Pixel_24 * palette; // CLUT (applicable only if 'im8' is valid)

	unsigned int width;
	unsigned int height;
	unsigned int size; // width * height

	char * fname; // full name of image including directory/path and extension
	char * name; // name of image without directory/path or extension

	List<CL_Image *> mipmaps;

	CL_Image() : im8(0),im16(0),im24(0),palette(0),palette_size(0),fname(0),name(0) {}
	virtual ~CL_Image();

	CL_Image(CL_Image const &);
	CL_Image & operator = (CL_Image const &);

	inline CL_Error Load(char const * const iname) // looks at rif file and video mode to determine which file to load
	{
		return Load(iname,CL_EID_INVALID);
	}
	inline CL_Error Load(int const enum_id) // looks at rif file and video mode to determine which file to load
	{
		return Load(0,enum_id);
	}
	inline CL_Error Load() // assumes preload has been called
	{
		return Load(0,CL_EID_INVALID);
	}
	
	inline CL_Error PreLoad(char const * const iname) // looks at rif file and video mode to determine which file to load
	{
		return PreLoad(iname,CL_EID_INVALID);
	}
	inline CL_Error PreLoad(int const enum_id) // looks at rif file and video mode to determine which file to load
	{
		return PreLoad(0,enum_id);
	}

	CL_Error LoadMipMaps(int const n_mips = 7); // assumes locating has been done (ie. PreLoad or Load has been called)

	CL_Error Load_BMP(D3I_FILE * f); // videomode != CLM_GLOBALPALETTE
	CL_Error Load_PPM(D3I_FILE * f); // videomode == CLM_16BIT || CLM_24BIT
	CL_Error Load_PGM(D3I_FILE * f); // videomode == CLM_GLOBALPALETTE
	CL_Error Load_PWM(D3I_FILE * f); // videomode == CLM_GLOBALPALETTE

	CL_Error Load_Image(D3I_FILE * f); // calls one of the above if correct format

	CL_Error GetBitsPerPixel(unsigned int* bpp); //returns bitsperpixel;

	// needs CL_Init_D3DMode((LPDDSURFACEDESC)format) to have been called
	inline CL_Error CopyToD3DTexture(LPDIRECTDRAWSURFACE * const DDPtrA, LPVOID * const DDSurfaceA, int const MemoryType)
	{
		return CopyToD3DTexture(&DDPtrA,&DDSurfaceA,MemoryType);
	}
	CL_Error CopyToD3DTexture(LPDIRECTDRAWSURFACE * const DDPtrA [], LPVOID * const DDSurfaceA [], int const MemoryType, unsigned int n_mips_max = 1);
	// needs CL_Init_ScanDrawMode((CL_VideoMode) videomode) to have been called
	inline CL_Error CopyToDirectDrawSurface(LPDIRECTDRAWSURFACE * const DDPtrA, LPVOID * const DDSurfaceA, int const MemoryType)
	{
		return CopyToDirectDrawSurface(&DDPtrA,&DDSurfaceA,MemoryType);
	}
	CL_Error CopyToDirectDrawSurface(LPDIRECTDRAWSURFACE * const DDPtrA [], LPVOID * const DDSurfaceA [], int const MemoryType, unsigned int n_mips_max = 1);
	// needs CL_Init_ScanDrawMode((CL_VideoMode) videomode) to have been called
	inline CL_Error CopyToScanDrawTexture(unsigned char * * const ImagePtrA)
	{
		return CopyToScanDrawTexture(&ImagePtrA);
	}
	CL_Error CopyToScanDrawTexture(unsigned char * * const ImagePtrA [], unsigned int n_mips_max = 1);
	// create an empty image for user manipulation
	CL_Error Make(int const _width,int const _height);
	// create an empty random image for user manipulation
	CL_Error MakeRandom(int const _width,int const _height,int const seed = -1);
	// ensure with and height are multiples of given units
	void PadTo(int const width_unit,int const height_unit);

	inline CL_Flags const & GetFlags() const
	{
		return flags;
	}
protected:
	CL_Flags flags;

	virtual CL_Error Locate(char const * iname, int const enum_id);
	CL_Error Load(char const * const iname, int const enum_id);
	CL_Error PreLoad(char const * const iname, int const enum_id);

	CL_Error ReducePalette(unsigned int const num_colours);

	// returns DDSURFACEDESC.lpSurface
	// does what LoadPPMIntoDDSurface did
	LPVOID CopyToDDSurface(LPDIRECTDRAWSURFACE lpDDS);

private:
	void Copy(CL_Image const &);
	void Delete(void);
	void DeleteNotMips(void);
};

#endif // !_included__d3_image_hpp_
