#define XMD_H

#include <windows.h>
#include "ImageLib.h"
#include "png\png.h"
#include <math.h>
#include <tchar.h>
#include "..\PakLib\PakInterface.h"

extern "C"
{
#include "jpeg\jpeglib.h"
#include "jpeg\jerror.h"
}

//#include "jpeg2000/jasper.h"

using namespace ImageLib;

Image::Image()
{
	mWidth = 0;
	mHeight = 0;
	mBits = NULL;
}

Image::~Image()
{
	delete mBits;
}

int	Image::GetWidth()
{
	return mWidth;
}

int	Image::GetHeight()
{
	return mHeight;
}

unsigned long* Image::GetBits()
{
	return mBits;
}

//////////////////////////////////////////////////////////////////////////
// PNG Pak Support

static void png_pak_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
	png_size_t check;

	/* fread() returns 0 on error, so it is OK to store this in a png_size_t
	* instead of an int, which is what fread() actually returns.
	*/
	check = (png_size_t)p_fread(data, (png_size_t)1, length,
		(PFILE*)png_ptr->io_ptr);

	if (check != length)
	{
		png_error(png_ptr, "Read Error");
	}
}

Image* GetPNGImage(const std::string& theFileName)
{
	png_structp png_ptr;
	png_infop info_ptr;
	unsigned int sig_read = 0;
	png_uint_32 width, height;
	int bit_depth, color_type, interlace_type;
	PFILE *fp;

	if ((fp = p_fopen(theFileName.c_str(), "rb")) == NULL)
		return NULL;

	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
	  NULL, NULL, NULL);
	png_set_read_fn(png_ptr, (png_voidp)fp, png_pak_read_data);

	if (png_ptr == NULL)
	{
		p_fclose(fp);
		return NULL;
	}

	/* Allocate/initialize the memory for image information.  REQUIRED. */
	info_ptr = png_create_info_struct(png_ptr);
	if (info_ptr == NULL)
	{
		p_fclose(fp);
		png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
		return NULL;
	}

   /* Set error handling if you are using the setjmp/longjmp method (this is
    * the normal method of doing things with libpng).  REQUIRED unless you
    * set up your own error handlers in the png_create_read_struct() earlier.
    */
	if (setjmp(png_ptr->jmpbuf))
	{
		/* Free all of the memory associated with the png_ptr and info_ptr */
		png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
		p_fclose(fp);
		/* If we get here, we had a problem reading the file */
		return NULL;
	}

	//png_init_io(png_ptr, fp);

	//png_ptr->io_ptr = (png_voidp)fp;

	png_read_info(png_ptr, info_ptr);
	png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
       &interlace_type, NULL, NULL);

	/* Add filler (or alpha) byte (before/after each RGB triplet) */
	png_set_expand(png_ptr);
	png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
	//png_set_gray_1_2_4_to_8(png_ptr);
	png_set_palette_to_rgb(png_ptr);
	png_set_gray_to_rgb(png_ptr);
	png_set_bgr(png_ptr);

//	int aNumBytes = png_get_rowbytes(png_ptr, info_ptr) * height / 4;
	unsigned long* aBits = new unsigned long[width*height];
	unsigned long* anAddr = aBits;
	for (int i = 0; i < height; i++)
	{
		png_read_rows(png_ptr, (png_bytepp) &anAddr, NULL, 1);
		anAddr += width;
	}

	/* read rest of file, and get additional chunks in info_ptr - REQUIRED */
	png_read_end(png_ptr, info_ptr);

	/* clean up after the read, and free any memory allocated - REQUIRED */
	png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);

	/* close the file */
	p_fclose(fp);

	Image* anImage = new Image();
	anImage->mWidth = width;
	anImage->mHeight = height;
	anImage->mBits = aBits;

	return anImage;
}

Image* GetTGAImage(const std::string& theFileName)
{
	PFILE* aTGAFile = p_fopen(theFileName.c_str(), "rb");
	if (aTGAFile == NULL)
		return NULL;

	BYTE aHeaderIDLen;
	p_fread(&aHeaderIDLen, sizeof(BYTE), 1, aTGAFile);

	BYTE aColorMapType;
	p_fread(&aColorMapType, sizeof(BYTE), 1, aTGAFile);
	
	BYTE anImageType;
	p_fread(&anImageType, sizeof(BYTE), 1, aTGAFile);

	WORD aFirstEntryIdx;
	p_fread(&aFirstEntryIdx, sizeof(WORD), 1, aTGAFile);

	WORD aColorMapLen;
	p_fread(&aColorMapLen, sizeof(WORD), 1, aTGAFile);

	BYTE aColorMapEntrySize;
	p_fread(&aColorMapEntrySize, sizeof(BYTE), 1, aTGAFile);	

	WORD anXOrigin;
	p_fread(&anXOrigin, sizeof(WORD), 1, aTGAFile);

	WORD aYOrigin;
	p_fread(&aYOrigin, sizeof(WORD), 1, aTGAFile);

	WORD anImageWidth;
	p_fread(&anImageWidth, sizeof(WORD), 1, aTGAFile);	

	WORD anImageHeight;
	p_fread(&anImageHeight, sizeof(WORD), 1, aTGAFile);	

	BYTE aBitCount = 32;
	p_fread(&aBitCount, sizeof(BYTE), 1, aTGAFile);	

	BYTE anImageDescriptor = 8 | (1<<5);
	p_fread(&anImageDescriptor, sizeof(BYTE), 1, aTGAFile);

	if ((aBitCount != 32) ||
		(anImageDescriptor != (8 | (1<<5))))
	{
		p_fclose(aTGAFile);
		return NULL;
	}

	Image* anImage = new Image();

	anImage->mWidth = anImageWidth;
	anImage->mHeight = anImageHeight;
	anImage->mBits = new unsigned long[anImageWidth*anImageHeight];

	p_fread(anImage->mBits, 4, anImage->mWidth*anImage->mHeight, aTGAFile);

	p_fclose(aTGAFile);

	return anImage;
}

int ReadBlobBlock(PFILE* fp, char* data)
{
	unsigned char aCount = 0;
	p_fread(&aCount, sizeof(char), 1, fp);
	p_fread(data, sizeof(char), aCount, fp);
	return aCount;
}

Image* GetGIFImage(const std::string& theFileName)
{
	#define BitSet(byte,bit)  (((byte) & (bit)) == (bit))
	#define LSBFirstOrder(x,y)  (((y) << 8) | (x))

	int
		opacity,
		status;

	register int i;

	register unsigned char *p;

	unsigned char
		background,
		c,
		flag,
		*global_colormap,
		header[1664],
		magick[12];

	unsigned int
		delay,
		dispose,
		global_colors,
		image_count,
		iterations;

	/*
	Open image file.
	*/

	PFILE *fp;

	if ((fp = p_fopen(theFileName.c_str(), "rb")) == NULL)
		return NULL;
	/*
	Determine if this is a GIF file.
	*/
	status=p_fread(magick, sizeof(char), 6, fp);

	if (((strncmp((char *) magick,"GIF87",5) != 0) &&
		(strncmp((char *) magick,"GIF89",5) != 0)))
		return NULL;

	global_colors=0;
	global_colormap=(unsigned char *) NULL;

	short pw;
	short ph;

	p_fread(&pw, sizeof(short), 1, fp);
	p_fread(&ph, sizeof(short), 1, fp);
	p_fread(&flag, sizeof(char), 1, fp);
	p_fread(&background, sizeof(char), 1, fp);
	p_fread(&c, sizeof(char), 1, fp);

	if (BitSet(flag,0x80))
	{
		/*
		opacity global colormap.
		*/
		global_colors=1 << ((flag & 0x07)+1);
		global_colormap=new unsigned char[3*global_colors];
		if (global_colormap == (unsigned char *) NULL)
		return NULL;

		p_fread(global_colormap, sizeof(char), 3*global_colors, fp);
	}

	delay=0;
	dispose=0;
	iterations=1;
	opacity=(-1);
	image_count=0;

	for ( ; ; )
	{
		if (p_fread(&c, sizeof(char), 1, fp) == 0)
		break;

		if (c == ';')
		break;  /* terminator */
		if (c == '!')
		{
			/*
			GIF Extension block.
			*/
			p_fread(&c, sizeof(char), 1, fp);

			switch (c)
			{
			case 0xf9:
				{
					/*
					Read Graphics Control extension.
					*/
					while (ReadBlobBlock(fp,(char *) header) > 0);

					dispose=header[0] >> 2;
					delay=(header[2] << 8) | header[1];
					if ((header[0] & 0x01) == 1)
					opacity=header[3];
					break;
				}
			case 0xfe:
				{
					char *comments;
					int length;

					/*
					Read Comment extension.
					*/
					comments=(char *) NULL;
					for ( ; ; )
					{
						length=ReadBlobBlock(fp,(char *) header);
						if (length <= 0)
						break;
						if (comments == NULL)
						{
							comments= new char[length+1];
							if (comments != (char *) NULL)
							*comments='\0';
						}

						header[length]='\0';
						strcat(comments,(char *) header);
					}
					if (comments == (char *) NULL)
						break;

					delete comments;
					break;
				}
			case 0xff:
				{
					int
					loop;

					/*
					Read Netscape Loop extension.
					*/
					loop=false;
					if (ReadBlobBlock(fp,(char *) header) > 0)
					loop=!strncmp((char *) header,"NETSCAPE2.0",11);
					while (ReadBlobBlock(fp,(char *) header) > 0)
					if (loop)
					iterations=(header[2] << 8) | header[1];
					break;
				}
			default:
				{
					while (ReadBlobBlock(fp,(char *) header) > 0);
					break;
				}
			}
		}

		if (c != ',')
			continue;

		if (image_count != 0)
		{
			/*
			Allocate next image structure.
			*/

			/*AllocateNextImage(image_info,image);
			if (image->next == (Image *) NULL)
			{
			DestroyImages(image);
			return((Image *) NULL);
			}
			image=image->next;
			MagickMonitor(LoadImagesText,TellBlob(image),image->filesize);*/
		}
		image_count++;

		short pagex;
		short pagey;
		short width;
		short height;
		int colors;
		bool interlaced;

		p_fread(&pagex, sizeof(short), 1, fp);
		p_fread(&pagey, sizeof(short), 1, fp);
		p_fread(&width, sizeof(short), 1, fp);
		p_fread(&height, sizeof(short), 1, fp);
		p_fread(&flag, sizeof(char), 1, fp);

		colors=!BitSet(flag,0x80) ? global_colors : 1 << ((flag & 0x07)+1);
		unsigned long* colortable = new unsigned long[colors];

		interlaced = BitSet(flag,0x40);

		delay=0;
		dispose=0;
		iterations=1;
		/*if (image_info->ping)
		{
		f (opacity >= 0)
		/image->matte=true;

		CloseBlob(image);
		return(image);
		}*/
		if ((width == 0) || (height == 0))
			return NULL;
		/*
		Inititialize colormap.
		*/
		/*if (!AllocateImageColormap(image,image->colors))
		ThrowReaderException(ResourceLimitWarning,"Memory allocation failed",
		image);*/
		if (!BitSet(flag,0x80))
		{
			/*
			Use global colormap.
			*/
			p=global_colormap;
			for (i=0; i < (int) colors; i++)
			{
				int r = *p++;
				int g = *p++;
				int b = *p++;

				colortable[i] = 0xFF000000 | (r << 16) | (g << 8) | (b);
			}

			//image->background_color=
			//image->colormap[Min(background,image->colors-1)];
		}
		else
		{
			unsigned char
			*colormap;

			/*
			Read local colormap.
			*/
			colormap=new unsigned char[3*colors];

			int pos = p_ftell(fp);
			p_fread(colormap, sizeof(char), 3*colors, fp);

			p=colormap;
			for (i=0; i < (int) colors; i++)
			{
				int r = *p++;
				int g = *p++;
				int b = *p++;

				colortable[i] = 0xFF000000 | (r << 16) | (g << 8) | (b);
			}
			delete colormap;
		}

		/*if (opacity >= (int) colors)
		{
		for (i=colors; i < (opacity+1); i++)
		{
		image->colormap[i].red=0;
		image->colormap[i].green=0;
		image->colormap[i].blue=0;
		}
		image->colors=opacity+1;
		}*/
		/*
		Decode image.
		*/
		//status=DecodeImage(image,opacity,exception);

		//if (global_colormap != (unsigned char *) NULL)
		// LiberateMemory((void **) &global_colormap);
		if (global_colormap != NULL)
		{
			delete [] global_colormap;
			global_colormap = NULL;
		}

		//while (image->previous != (Image *) NULL)
		//    image=image->previous;

		#define MaxStackSize  4096
		#define NullCode  (-1)

		int
			available,
			bits,
			code,
			clear,
			code_mask,
			code_size,
			count,
			end_of_information,
			in_code,
			offset,
			old_code,
			pass,
			y;

		register int
			x;

		register unsigned int
			datum;

		short
			*prefix;

		unsigned char
			data_size,
			first,
			*packet,
			*pixel_stack,
			*suffix,
			*top_stack;

		/*
		Allocate decoder tables.
		*/

		packet=new unsigned char[256];
		prefix=new short[MaxStackSize];
		suffix=new unsigned char [MaxStackSize];
		pixel_stack= new unsigned char[MaxStackSize+1];

		/*
		Initialize GIF data stream decoder.
		*/
		p_fread(&data_size, sizeof(char), 1, fp);
		clear=1 << data_size;
		end_of_information=clear+1;
		available=clear+2;
		old_code=NullCode;
		code_size=data_size+1;
		code_mask=(1 << code_size)-1;
		for (code=0; code < clear; code++)
		{
			prefix[code]=0;
			suffix[code]=(unsigned char) code;
		}
		/*
		Decode GIF pixel stream.
		*/
		datum=0;
		bits=0;
		c=0;
		count=0;
		first=0;
		offset=0;
		pass=0;
		top_stack=pixel_stack;

		unsigned long* aBits = new unsigned long[width*height];

		register unsigned char *c = NULL;

		for (y=0; y < (int) height; y++)
		{
			//q=SetImagePixels(image,0,offset,width,1);
			//if (q == (PixelPacket *) NULL)
			//break;
			//indexes=GetIndexes(image);

			unsigned long* q = aBits + offset*width;



			for (x=0; x < (int) width; )
			{
				if (top_stack == pixel_stack)
				{
					if (bits < code_size)
					{
						/*
						Load bytes until there is enough bits for a code.
						*/
						if (count == 0)
						{
							/*
							Read a new data block.
							*/
							int pos = p_ftell(fp);

							count=ReadBlobBlock(fp,(char *) packet);
							if (count <= 0)
							break;
							c=packet;
						}
						datum+=(*c) << bits;
						bits+=8;
						c++;
						count--;
						continue;
					}
					/*
					Get the next code.
					*/
					code=datum & code_mask;
					datum>>=code_size;
					bits-=code_size;
					/*
					Interpret the code
					*/
					if ((code > available) || (code == end_of_information))
					break;
					if (code == clear)
					{
						/*
						Reset decoder.
						*/
						code_size=data_size+1;
						code_mask=(1 << code_size)-1;
						available=clear+2;
						old_code=NullCode;
						continue;
					}
					if (old_code == NullCode)
					{
						*top_stack++=suffix[code];
						old_code=code;
						first=(unsigned char) code;
						continue;
					}
					in_code=code;
					if (code >= available)
					{
						*top_stack++=first;
						code=old_code;
					}
					while (code >= clear)
					{
						*top_stack++=suffix[code];
						code=prefix[code];
					}
					first=suffix[code];
					/*
					Add a new string to the string table,
					*/
					if (available >= MaxStackSize)
						break;
					*top_stack++=first;
					prefix[available]=old_code;
					suffix[available]=first;
					available++;
					if (((available & code_mask) == 0) && (available < MaxStackSize))
					{
						code_size++;
						code_mask+=available;
					}
					old_code=in_code;
				}
				/*
				Pop a pixel off the pixel stack.
				*/
				top_stack--;

				int index=(*top_stack);

				*q=colortable[index];

				if (index == opacity)
					*q = 0;

				x++;
				q++;
			}

			if (!interlaced)
				offset++;
			else
			{
				switch (pass)
				{
				case 0:
				default:
					{
						offset+=8;
						if (offset >= height)
						{
							pass++;
							offset=4;
						}
						break;
					}
				case 1:
					{
						offset+=8;
						if (offset >= height)
						{
							pass++;
							offset=2;
						}
						break;
					}
				case 2:
					{
						offset+=4;
						if (offset >= height)
						{
							pass++;
							offset=1;
						}
						break;
					}
				case 3:
					{
						offset+=2;
						break;
					}
				}
			}

			if (x < width)
				break;

			/*if (image->previous == (Image *) NULL)
			if (QuantumTick(y,image->rows))
			MagickMonitor(LoadImageText,y,image->rows);*/
		}
		delete pixel_stack;
		delete suffix;
		delete prefix;
		delete packet;

		delete colortable;

		//if (y < image->rows)
		//failed = true;

		Image* anImage = new Image();

		anImage->mWidth = width;
		anImage->mHeight = height;
		anImage->mBits = aBits;

		//TODO: Change for animation crap
		p_fclose(fp);
		return anImage;
	}

	p_fclose(fp);

	return NULL;
}

typedef struct my_error_mgr * my_error_ptr;

struct my_error_mgr
{
  struct jpeg_error_mgr pub;	/* "public" fields */

  jmp_buf setjmp_buffer;	/* for return to caller */
};

METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
  my_error_ptr myerr = (my_error_ptr) cinfo->err;

  /* Always display the message. */
  /* We could postpone this until after returning, if we chose. */
  (*cinfo->err->output_message) (cinfo);

  /* Return control to the setjmp point */
  longjmp(myerr->setjmp_buffer, 1);

}

bool ImageLib::WriteJPEGImage(const std::string& theFileName, Image* theImage)
{
	FILE *fp;

	if ((fp = fopen(theFileName.c_str(), "wb")) == NULL)
		return false;

	struct jpeg_compress_struct cinfo;
	struct my_error_mgr jerr;

	cinfo.err = jpeg_std_error(&jerr.pub);
	jerr.pub.error_exit = my_error_exit;

	if (setjmp(jerr.setjmp_buffer))
	{
		/* If we get here, the JPEG code has signaled an error.
		 * We need to clean up the JPEG object, close the input file, and return.
		 */
		jpeg_destroy_compress(&cinfo);
		fclose(fp);
		return false;
	}

	jpeg_create_compress(&cinfo);

	cinfo.image_width = theImage->mWidth;
	cinfo.image_height = theImage->mHeight;
	cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;
    cinfo.optimize_coding = 1;
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, 80, TRUE);

	jpeg_stdio_dest(&cinfo, fp);

	jpeg_start_compress(&cinfo, true);

	int row_stride = theImage->GetWidth() * 3;

	unsigned char* aTempBuffer = new unsigned char[row_stride];

	unsigned long* aSrcPtr = theImage->mBits;

	for (int aRow = 0; aRow < theImage->mHeight; aRow++)
	{
		unsigned char* aDest = aTempBuffer;

		for (int aCol = 0; aCol < theImage->mWidth; aCol++)
		{
			unsigned long src = *(aSrcPtr++);

			*aDest++ = (src >> 16) & 0xFF;
			*aDest++ = (src >>  8) & 0xFF;
			*aDest++ = (src      ) & 0xFF;
		}

		jpeg_write_scanlines(&cinfo, &aTempBuffer, 1);
	}

	delete [] aTempBuffer;

	jpeg_finish_compress(&cinfo);
	jpeg_destroy_compress(&cinfo);

	fclose(fp);

	return true;
}

bool ImageLib::WritePNGImage(const std::string& theFileName, Image* theImage)
{
	png_structp png_ptr;
	png_infop info_ptr;

	FILE *fp;

	if ((fp = fopen(theFileName.c_str(), "wb")) == NULL)
		return false;

	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
	  NULL, NULL, NULL);

	if (png_ptr == NULL)
	{
		fclose(fp);
		return false;
	}

	// Allocate/initialize the memory for image information.  REQUIRED.
	info_ptr = png_create_info_struct(png_ptr);
	if (info_ptr == NULL)
	{
		fclose(fp);
		png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
		return false;
	}

   // Set error handling if you are using the setjmp/longjmp method (this is
   // the normal method of doing things with libpng).  REQUIRED unless you
   // set up your own error handlers in the png_create_write_struct() earlier.

	if (setjmp(png_ptr->jmpbuf))
	{
		// Free all of the memory associated with the png_ptr and info_ptr
		png_destroy_write_struct(&png_ptr, &info_ptr);
		fclose(fp);
		// If we get here, we had a problem writeing the file
		return NULL;
	}

	png_init_io(png_ptr, fp);

	png_color_8 sig_bit;
	sig_bit.red = 8;
	sig_bit.green = 8;
	sig_bit.blue = 8;
	/* if the image has an alpha channel then */
	sig_bit.alpha = 8;
	png_set_sBIT(png_ptr, info_ptr, &sig_bit);
	png_set_bgr(png_ptr);

	png_set_IHDR(png_ptr, info_ptr, theImage->mWidth, theImage->mHeight, 8, PNG_COLOR_TYPE_RGB_ALPHA,
       PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

	// Add filler (or alpha) byte (before/after each RGB triplet)
	//png_set_expand(png_ptr);
	//png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
	//png_set_gray_1_2_4_to_8(png_ptr);
	//png_set_palette_to_rgb(png_ptr);
	//png_set_gray_to_rgb(png_ptr);

	png_write_info(png_ptr, info_ptr);

	for (int i = 0; i < theImage->mHeight; i++)
	{
		png_bytep aRowPtr = (png_bytep) (theImage->mBits + i*theImage->mWidth);
		png_write_rows(png_ptr, &aRowPtr, 1);
	}

	// write rest of file, and get additional chunks in info_ptr - REQUIRED
	png_write_end(png_ptr, info_ptr);

	// clean up after the write, and free any memory allocated - REQUIRED
	png_destroy_write_struct(&png_ptr, &info_ptr);

	// close the file
	fclose(fp);

	return true;
}

bool ImageLib::WriteTGAImage(const std::string& theFileName, Image* theImage)
{
	FILE* aTGAFile = fopen(theFileName.c_str(), "wb");
	if (aTGAFile == NULL)
		return false;

	BYTE aHeaderIDLen = 0;
	fwrite(&aHeaderIDLen, sizeof(BYTE), 1, aTGAFile);

	BYTE aColorMapType = 0;
	fwrite(&aColorMapType, sizeof(BYTE), 1, aTGAFile);
	
	BYTE anImageType = 2;
	fwrite(&anImageType, sizeof(BYTE), 1, aTGAFile);

	WORD aFirstEntryIdx = 0;
	fwrite(&aFirstEntryIdx, sizeof(WORD), 1, aTGAFile);

	WORD aColorMapLen = 0;
	fwrite(&aColorMapLen, sizeof(WORD), 1, aTGAFile);

	BYTE aColorMapEntrySize = 0;
	fwrite(&aColorMapEntrySize, sizeof(BYTE), 1, aTGAFile);	

	WORD anXOrigin = 0;
	fwrite(&anXOrigin, sizeof(WORD), 1, aTGAFile);

	WORD aYOrigin = 0;
	fwrite(&aYOrigin, sizeof(WORD), 1, aTGAFile);

	WORD anImageWidth = theImage->mWidth;
	fwrite(&anImageWidth, sizeof(WORD), 1, aTGAFile);	

	WORD anImageHeight = theImage->mHeight;
	fwrite(&anImageHeight, sizeof(WORD), 1, aTGAFile);	

	BYTE aBitCount = 32;
	fwrite(&aBitCount, sizeof(BYTE), 1, aTGAFile);	

	BYTE anImageDescriptor = 8 | (1<<5);
	fwrite(&anImageDescriptor, sizeof(BYTE), 1, aTGAFile);

	fwrite(theImage->mBits, 4, theImage->mWidth*theImage->mHeight, aTGAFile);

	fclose(aTGAFile);

	return true;
}

bool ImageLib::WriteBMPImage(const std::string& theFileName, Image* theImage)
{
	FILE* aFile = fopen(theFileName.c_str(), "wb");
	if (aFile == NULL)
		return false;

	BITMAPFILEHEADER aFileHeader;
	BITMAPINFOHEADER aHeader;

	memset(&aFileHeader,0,sizeof(aFileHeader));
	memset(&aHeader,0,sizeof(aHeader));

	int aNumBytes = theImage->mWidth*theImage->mHeight*4;

	aFileHeader.bfType = ('M'<<8) | 'B';
	aFileHeader.bfSize = sizeof(aFileHeader) + sizeof(aHeader) + aNumBytes;
	aFileHeader.bfOffBits = sizeof(aHeader); 

	aHeader.biSize = sizeof(aHeader);
	aHeader.biWidth = theImage->mWidth;
	aHeader.biHeight = theImage->mHeight;
	aHeader.biPlanes = 1;
	aHeader.biBitCount = 32;
	aHeader.biCompression = BI_RGB;

	fwrite(&aFileHeader,sizeof(aFileHeader),1,aFile);
	fwrite(&aHeader,sizeof(aHeader),1,aFile);
	DWORD *aRow = theImage->mBits + (theImage->mHeight-1)*theImage->mWidth;
	int aRowSize = theImage->mWidth*4;
	for (int i=0; i<theImage->mHeight; i++, aRow-=theImage->mWidth)
		fwrite(aRow,4,theImage->mWidth,aFile);

	fclose(aFile);
	return true;
}

////////////////////////////////////////////////////////////////////////// 
// JPEG Pak Reader

typedef struct {
	struct jpeg_source_mgr pub;	/* public fields */

	PFILE * infile;		/* source stream */
	JOCTET * buffer;		/* start of buffer */
	boolean start_of_file;	/* have we gotten any data yet? */
} pak_source_mgr;

typedef pak_source_mgr * pak_src_ptr;

#define INPUT_BUF_SIZE 4096

METHODDEF(void) init_source (j_decompress_ptr cinfo)
{
	pak_src_ptr src = (pak_src_ptr) cinfo->src;
	src->start_of_file = TRUE;
}

METHODDEF(boolean) fill_input_buffer (j_decompress_ptr cinfo)
{
	pak_src_ptr src = (pak_src_ptr) cinfo->src;
	size_t nbytes;

	nbytes = p_fread(src->buffer, 1, INPUT_BUF_SIZE, src->infile);
	//((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file)))

	if (nbytes <= 0) {
		if (src->start_of_file)	/* Treat empty input file as fatal error */
			ERREXIT(cinfo, JERR_INPUT_EMPTY);
		WARNMS(cinfo, JWRN_JPEG_EOF);
		/* Insert a fake EOI marker */
		src->buffer[0] = (JOCTET) 0xFF;
		src->buffer[1] = (JOCTET) JPEG_EOI;
		nbytes = 2;
	}

	src->pub.next_input_byte = src->buffer;
	src->pub.bytes_in_buffer = nbytes;
	src->start_of_file = FALSE;

	return TRUE;
}

METHODDEF(void) skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
	pak_src_ptr src = (pak_src_ptr) cinfo->src;

	if (num_bytes > 0) {
		while (num_bytes > (long) src->pub.bytes_in_buffer) {
			num_bytes -= (long) src->pub.bytes_in_buffer;
			(void) fill_input_buffer(cinfo);
		}
		src->pub.next_input_byte += (size_t) num_bytes;
		src->pub.bytes_in_buffer -= (size_t) num_bytes;
	}
}

METHODDEF(void) term_source (j_decompress_ptr cinfo)
{
	/* no work necessary here */
}

void jpeg_pak_src (j_decompress_ptr cinfo, PFILE* infile)
{
	pak_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(pak_source_mgr));
		src = (pak_src_ptr) cinfo->src;
		src->buffer = (JOCTET *)
			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
			INPUT_BUF_SIZE * sizeof(JOCTET));
	}

	src = (pak_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->infile = infile;
	src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
	src->pub.next_input_byte = NULL; /* until buffer loaded */
}


Image* GetJPEGImage(const std::string& theFileName)
{
	PFILE *fp;

	if ((fp = p_fopen(theFileName.c_str(), "rb")) == NULL)
		return NULL;

	struct jpeg_decompress_struct cinfo;
	struct my_error_mgr jerr;

	cinfo.err = jpeg_std_error(&jerr.pub);
	jerr.pub.error_exit = my_error_exit;

	if (setjmp(jerr.setjmp_buffer))
	{
		/* If we get here, the JPEG code has signaled an error.
		 * We need to clean up the JPEG object, close the input file, and return.
		 */
		jpeg_destroy_decompress(&cinfo);
		p_fclose(fp);
		return 0;
	}

	jpeg_create_decompress(&cinfo);
	jpeg_pak_src(&cinfo, fp);
	jpeg_read_header(&cinfo, TRUE);
	jpeg_start_decompress(&cinfo);
	int row_stride = cinfo.output_width * cinfo.output_components;

	unsigned char** buffer = (*cinfo.mem->alloc_sarray)
		((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

	unsigned long* aBits = new unsigned long[cinfo.output_width*cinfo.output_height];
	unsigned long* q = aBits;

	if (cinfo.output_components==1)
	{
		while (cinfo.output_scanline < cinfo.output_height)
		{
			jpeg_read_scanlines(&cinfo, buffer, 1);

			unsigned char* p = *buffer;
			for (int i = 0; i < cinfo.output_width; i++)
			{
				int r = *p++;
				*q++ = 0xFF000000 | (r << 16) | (r << 8) | (r);
			}
		}
	}
	else
	{
		while (cinfo.output_scanline < cinfo.output_height)
		{
			jpeg_read_scanlines(&cinfo, buffer, 1);

			unsigned char* p = *buffer;
			for (int i = 0; i < cinfo.output_width; i++)
			{
				int r = *p++;
				int g = *p++;
				int b = *p++;

				*q++ = 0xFF000000 | (r << 16) | (g << 8) | (b);
			}
		}
	}

	Image* anImage = new Image();
	anImage->mWidth = cinfo.output_width;
	anImage->mHeight = cinfo.output_height;
	anImage->mBits = aBits;

	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);

	p_fclose(fp);

	return anImage;
}

#if 0
Image* GetJPEG2000Image(const std::string& theFileName)
{
	DWORD aTick = GetTickCount();

	static bool inited = false;
	if (!inited)
	{
		inited = true;
		jas_init();
	}

	jas_stream_t* aStream = jas_stream_fopen(theFileName.c_str(),"rb");
	if (!aStream)
		return NULL;

	// Read Image Header
	int aFormat = jas_image_getfmt(aStream);
	if (aFormat < 0)
	{
		jas_stream_close(aStream);
		return NULL;
	}

	jas_image_t* aJasImage = jas_image_decode(aStream,aFormat,NULL);
	if (!aJasImage)
	{
		jas_stream_close(aStream);
		return NULL;
	}

	DWORD aDecodeTime = GetTickCount() - aTick;

	int aNumComponents = jas_image_numcmpts(aJasImage);
	if (aNumComponents<1 || aNumComponents>4)
	{
		jas_stream_close(aStream);
		jas_image_destroy(aJasImage);
		return NULL;
	}

	int i;
	int aMaxWidth = 0;
	int aMaxHeight = 0;
	for (i=0; i<aNumComponents; i++)
	{
		int hstep = jas_image_cmpthstep(aJasImage,i);
		int numHSteps = jas_image_cmptwidth(aJasImage,i);

		int vstep = jas_image_cmptvstep(aJasImage,i);
		int numVSteps = jas_image_cmptheight(aJasImage,i);

		int aWidth = jas_image_cmpttlx(aJasImage,i) + hstep*numHSteps;
		int aHeight = jas_image_cmpttly(aJasImage,i) + vstep*numVSteps;

		if (aWidth > aMaxWidth)
			aMaxWidth = aWidth;

		if (aHeight > aMaxHeight)
			aMaxHeight = aHeight;
	}

	// Read Image Data
	Image *anImage = new Image;
	anImage->mWidth = aMaxWidth;
	anImage->mHeight = aMaxHeight;
	anImage->mBits = new unsigned long[aMaxWidth * aMaxHeight];
	memset(anImage->mBits,0,aMaxWidth * aMaxHeight*4);

	int aColorModel = jas_image_clrspc(aJasImage);

	for (i=0; i<aNumComponents; i++)
	{
		int hstep = jas_image_cmpthstep(aJasImage,i);
		int vstep = jas_image_cmptvstep(aJasImage,i);
		int numHSteps = jas_image_cmptwidth(aJasImage,i);
		int numVSteps = jas_image_cmptheight(aJasImage,i);
		int xorig = jas_image_cmpttlx(aJasImage,i);
		int yorig = jas_image_cmpttly(aJasImage,i);
		bool sign = jas_image_cmptsgnd(aJasImage,i)?true:false;

		jas_matrix_t* aMatrix = jas_matrix_create(1,numHSteps);
		if (!aMatrix)
		{
			delete anImage;
			jas_image_destroy(aJasImage);
			jas_stream_close(aStream);

			return NULL;
		}

		int aShift = 8 - jas_image_cmptprec(aJasImage,i);
		if (aShift<0)
		{
			delete anImage;
			jas_matrix_destroy(aMatrix);
			jas_image_destroy(aJasImage);
			jas_stream_close(aStream);

			return NULL;
		}

		unsigned long* destRow = anImage->mBits + yorig*numVSteps*anImage->GetWidth() + xorig*numHSteps;

		// color model
		int aComponentType = jas_image_cmpttype(aJasImage,i);
		int aColorType = JAS_IMAGE_CT_COLOR(aComponentType);

		switch (aColorType)
		{
			case JAS_IMAGE_CT_RGB_R: aShift += 16; break;
			case JAS_IMAGE_CT_RGB_G: aShift += 8; break;
			case JAS_IMAGE_CT_RGB_B: break;
			default: aShift += 24; break; 
		}

		for (int y=0; y<numVSteps; y++)
		{
			if (jas_image_readcmpt(aJasImage,i,0,y,numHSteps,1,aMatrix) )
			{
				delete anImage;
				jas_matrix_destroy(aMatrix);
				jas_image_destroy(aJasImage);
				jas_stream_close(aStream);

				return NULL;			
			}

			unsigned long* dest = destRow;
			for (int x=0; x<numHSteps; x++)
			{
				int aVal = jas_matrix_getv(aMatrix,x);
				if (sign)
					aVal = (unsigned char)(aVal + 128);

				aVal <<= aShift;

				unsigned long *destRowWriter = dest;
				for (int j=0; j<vstep; j++)
				{
					unsigned long *destWriter = destRowWriter;
					for (int k=0; k<hstep; k++)
						*destWriter++ |= aVal;

					destRowWriter += anImage->GetWidth();
				}

				dest += hstep;
			}
			destRow += vstep*anImage->GetWidth();
		}

		// release decoding matrix
		jas_matrix_destroy(aMatrix);
	}

	DWORD aReadTime = GetTickCount() - aTick;
	char aBuf[512];
	sprintf(aBuf,"%d %d\n",aDecodeTime,aReadTime);
	OutputDebugString(aBuf);


	if (aNumComponents < 4) // add 255 alpha
	{
		int aSize = anImage->GetWidth()*anImage->GetHeight();
		unsigned long *dest = anImage->mBits;
		for (i=0; i<aSize; i++)
			*dest++ |= 0xff000000;
	}

	jas_image_destroy(aJasImage);
	jas_stream_close(aStream);

	return anImage;
}
#else

#include "j2k-codec\j2k-codec.h"

HMODULE gJ2KCodec = NULL;
std::string gJ2KCodecKey = "Your registration here";

void ImageLib::InitJPEG2000()
{
	gJ2KCodec = ::LoadLibrary(_T("j2k-codec.dll"));
}

void ImageLib::CloseJPEG2000()
{
	if (gJ2KCodec != NULL)
	{
		::FreeLibrary(gJ2KCodec);
		gJ2KCodec = NULL;
	}
}

void ImageLib::SetJ2KCodecKey(const std::string& theKey)
{
	gJ2KCodecKey = theKey;
}

int __stdcall Pak_seek(void *data_source, int offset)
{
	return p_fseek((PFILE*) data_source, offset, SEEK_SET);
}

int __stdcall Pak_read(void *ptr, int size, void *data_source)
{
	return p_fread(ptr, 1, size, (PFILE*) data_source);
}

void __stdcall Pak_close(void *data_source)
{	
}

Image* GetJPEG2000Image(const std::string& theFileName)
{
	if (gJ2KCodec != NULL)
	{
		PFILE* aFP = p_fopen(theFileName.c_str(), "rb");
		if (aFP == NULL)
			return NULL;

		static int (__stdcall *fJ2K_getVersion)() = NULL;
		static void (__stdcall *fJ2K_Unlock)(const char*) = NULL;
		static void* (__stdcall *fJ2K_OpenCustom)(void*, J2K_Callbacks*) = NULL;
		static void* (__stdcall *fJ2K_OpenFile)(const char*) = NULL;
		static void (__stdcall *fJ2K_Close)(void*) = NULL;
		static int (__stdcall *fJ2K_GetInfo)(void*, int*, int*, int*) = NULL;
		static int (__stdcall *fJ2K_GetResolutionDimensions)(void*, int, int*, int*) = NULL;
		static int (__stdcall *fJ2K_Decode)(void*, unsigned char**, int*, char*, int*) = NULL;
		static int (__stdcall *fJ2K_getLastError)() = NULL;
		static const char* (__stdcall *fJ2K_getErrorStr)(int) = NULL;
		static bool loadFuncs = true;

		if (loadFuncs)
		{
			loadFuncs = false;
			*((void**)&fJ2K_getVersion) = (void*)::GetProcAddress(gJ2KCodec, "_J2K_getVersion@0");
			*((void**)&fJ2K_Unlock) = (void*)::GetProcAddress(gJ2KCodec, "_J2K_Unlock@4");
			*((void**)&fJ2K_OpenCustom) = (void*)::GetProcAddress(gJ2KCodec, "_J2K_OpenCustom@8");
			*((void**)&fJ2K_OpenFile) = (void*)::GetProcAddress(gJ2KCodec, "_J2K_OpenFile@4");
			*((void**)&fJ2K_Close) = (void*)::GetProcAddress(gJ2KCodec, "_J2K_Close@4");
			*((void**)&fJ2K_GetInfo) = (void*)::GetProcAddress(gJ2KCodec, "_J2K_GetInfo@16");
			*((void**)&fJ2K_GetResolutionDimensions) = (void*)::GetProcAddress(gJ2KCodec, "_J2K_GetResolutionDimensions@16");
			*((void**)&fJ2K_Decode) = (void*)::GetProcAddress(gJ2KCodec, "_J2K_Decode@20");
			*((void**)&fJ2K_getLastError) = (void*)::GetProcAddress(gJ2KCodec, "_J2K_getLastError@0");
			*((void**)&fJ2K_getErrorStr) = (void*)::GetProcAddress(gJ2KCodec, "_J2K_getErrorStr@4");

			// j2k guys didn't use declare the export names. yay! now we have to update these mangled names any time the DLL changes.

			if (!(fJ2K_getVersion != NULL && 
				  fJ2K_Unlock != NULL && 
				  fJ2K_OpenCustom != NULL && 
				  fJ2K_OpenFile != NULL && 
				  fJ2K_Close != NULL && 
				  fJ2K_GetInfo != NULL && 
				  fJ2K_GetResolutionDimensions != NULL && 
				  fJ2K_Decode != NULL &&
				  fJ2K_getLastError != NULL &&
				  fJ2K_getErrorStr != NULL))
			{
				CloseJPEG2000();
				return NULL;
			}

			int aJ2kVer = (*fJ2K_getVersion)();
			if (aJ2kVer < 0x120000) 
			{
				CloseJPEG2000();
				return NULL;
			}

			(*fJ2K_Unlock)(gJ2KCodecKey.c_str()); 
		}

		J2K_Callbacks aCallbacks;
		aCallbacks.read = Pak_read;
		aCallbacks.seek = Pak_seek;
		aCallbacks.close = Pak_close;

		//theFileName.c_str()
		void* aJ2KImage = (*fJ2K_OpenCustom)(aFP, &aCallbacks);
		if (aJ2KImage == NULL)
		{
			int anErrNum = (*fJ2K_getLastError)();
			std::string anErrorMessage = (*fJ2K_getErrorStr)(anErrNum);
			return NULL;
		}

		int aWidth, aHeight, aComponents;
		if ((*fJ2K_GetInfo)(aJ2KImage, &aWidth, &aHeight, &aComponents) != J2KERR_SUCCESS) 
		{
			(*fJ2K_Close)(aJ2KImage);
			return NULL;
		}

		(*fJ2K_GetResolutionDimensions)(aJ2KImage, 0, &aWidth, &aHeight);
		
		unsigned long* aBuffer = new unsigned long[aWidth*aHeight];
		if (aBuffer == NULL)
		{
			(*fJ2K_Close)(aJ2KImage);
			return NULL;
		}

		char anOptsBuffer[32];
		strcpy(anOptsBuffer, "bpp=4,rl=0");

		int aSize = aWidth*aHeight*4;
		int aPitch = aWidth*4;
		if ((*fJ2K_Decode)(aJ2KImage, (unsigned char**)&aBuffer, &aSize, anOptsBuffer, &aPitch) != J2KERR_SUCCESS)
		{
			(*fJ2K_Close)(aJ2KImage);
			delete[] aBuffer;
			return NULL;
		}
		(*fJ2K_Close)(aJ2KImage);

		ImageLib::Image* anImage = new ImageLib::Image;
		anImage->mBits = aBuffer;
		anImage->mWidth = aWidth;
		anImage->mHeight = aHeight;
		if (gIgnoreJPEG2000Alpha)
		{
			DWORD *aPtr = anImage->mBits;
			DWORD *anEnd = aPtr+anImage->mWidth*anImage->mHeight;
			for (; aPtr!=anEnd; ++aPtr)
				*aPtr |= 0xFF000000;
		}

		p_fclose(aFP);

		return anImage;
	}
	return NULL;
}

#endif


int ImageLib::gAlphaComposeColor = 0xFFFFFF;
bool ImageLib::gAutoLoadAlpha = true;
bool ImageLib::gIgnoreJPEG2000Alpha = true;

Image* ImageLib::GetImage(const std::string& theFilename, bool lookForAlphaImage)
{
	if (!gAutoLoadAlpha)
		lookForAlphaImage = false;

	int aLastDotPos = theFilename.rfind('.');
	int aLastSlashPos = max((int)theFilename.rfind('\\'), (int)theFilename.rfind('/'));

	std::string anExt;
	std::string aFilename;

	if (aLastDotPos > aLastSlashPos)
	{
		anExt = theFilename.substr(aLastDotPos, theFilename.length() - aLastDotPos);
		aFilename = theFilename.substr(0, aLastDotPos);
	}
	else
		aFilename = theFilename;

	Image* anImage = NULL;

	if ((anImage == NULL) && ((stricmp(anExt.c_str(), ".tga") == 0) || (anExt.length() == 0)))
		 anImage = GetTGAImage(aFilename + ".tga");

	if ((anImage == NULL) && ((stricmp(anExt.c_str(), ".jpg") == 0) || (anExt.length() == 0)))
		 anImage = GetJPEGImage(aFilename + ".jpg");

	if ((anImage == NULL) && ((stricmp(anExt.c_str(), ".png") == 0) || (anExt.length() == 0)))
		 anImage = GetPNGImage(aFilename + ".png");

	if ((anImage == NULL) && ((stricmp(anExt.c_str(), ".gif") == 0) || (anExt.length() == 0)))
		 anImage = GetGIFImage(aFilename + ".gif");

	if (anImage == NULL && (stricmp(anExt.c_str(), ".j2k") == 0 || anExt.length() == 0))
		anImage = GetJPEG2000Image(aFilename + ".j2k");
	if (anImage == NULL && (stricmp(anExt.c_str(), ".jp2") == 0 || anExt.length() == 0))
		anImage = GetJPEG2000Image(aFilename + ".jp2");


	// Check for alpha images
	Image* anAlphaImage = NULL;
	if(lookForAlphaImage)
	{
		// Check _ImageName
		anAlphaImage = GetImage(theFilename.substr(0, aLastSlashPos+1) + "_" +
			theFilename.substr(aLastSlashPos+1, theFilename.length() - aLastSlashPos - 1), false);

		// Check ImageName_
		if(anAlphaImage==NULL)
			anAlphaImage = GetImage(theFilename + "_", false);
	}



	// Compose alpha channel with image
	if (anAlphaImage != NULL) 
	{
		if (anImage != NULL)
		{
			if ((anImage->mWidth == anAlphaImage->mWidth) &&
				(anImage->mHeight == anAlphaImage->mHeight))
			{
				unsigned long* aBits1 = anImage->mBits;
				unsigned long* aBits2 = anAlphaImage->mBits;
				int aSize = anImage->mWidth*anImage->mHeight;

				for (int i = 0; i < aSize; i++)
				{
					*aBits1 = (*aBits1 & 0x00FFFFFF) | ((*aBits2 & 0xFF) << 24);
					++aBits1;
					++aBits2;
				}
			}

			delete anAlphaImage;
		}
		else if (gAlphaComposeColor==0xFFFFFF)
		{
			anImage = anAlphaImage;

			unsigned long* aBits1 = anImage->mBits;

			int aSize = anImage->mWidth*anImage->mHeight;
			for (int i = 0; i < aSize; i++)
			{
				*aBits1 = (0x00FFFFFF) | ((*aBits1 & 0xFF) << 24);
				++aBits1;
			}
		}
		else
		{
			const int aColor = gAlphaComposeColor;
			anImage = anAlphaImage;

			unsigned long* aBits1 = anImage->mBits;

			int aSize = anImage->mWidth*anImage->mHeight;
			for (int i = 0; i < aSize; i++)
			{
				*aBits1 = aColor | ((*aBits1 & 0xFF) << 24);
				++aBits1;
			}
		}
	}

	return anImage;
}
