/** SGX

The   sss    gggg x   x   
     s      g      x x    Engine
      sss   g  gg   x
         s  g   g  x x          Copyright 1995-2009.
     ssss    gggg x   x         Steven Goodwin. All Rights Reserved.
    
This version has been released under the GNU Public License v2.0.

Please see the license files for details.

File: sgx/src/utils/imagelib/tgaparser.cpp

*/

#include "sgx/core/core.hpp"
#include "sgx/filesystem/filesystem.hpp"
#include "sgx/graphics/graphics.hpp"
#include "utils/imagelib/imagelib.hpp"

//
// Generic parser code
//
CTGAParser *CTGAParser::m_pSingleton = NULL;

void
CTGAParser::create()
{
	if (!m_pSingleton) {
		m_pSingleton = new CTGAParser();
		if (m_pSingleton) {
			CImageLoader::get()->registerObject(m_pSingleton);
		}
	}
}

//
// TGA-specific structures
//
typedef struct {
	tBYTE	identsize;
	tBYTE	colourmaptype;
	tBYTE	imagetype;

	tBYTE	colourmapstart;
	tINT16	colourmaplength;
	tBYTE	colourmapbits;

	tINT16	xstart, ystart;
	tINT16	width, height;

	tBYTE	bits;
	tBYTE	descriptor;
} sgxTGAHeader;


// BMP-specific source code
//
tBOOL CTGAParser::isRecognised(CSGXFile &file) const
{
	char id[3];

	// The TGA header doesn't really describe the file, but we can
	// determine whether the format is one we recognize.
	file.read((tBYTE *)&id, sizeof(id));
	if (id[1] == 0 && (id[2] == 2 || id[2] == 10)) {
		return TRUE;
	}
	
	return FALSE;
}

tBOOL CTGAParser::open(CSGXFile &file, CImageData &properties) const
{
	sgxTGAHeader fileheader;

	properties.release();

	// Special code to load the header, which is 18 bytes
	file.read((tBYTE *)&fileheader, sizeof(sgxTGAHeader));

	//tUINT32 attributeBits = (fileheader.descriptor & 0x0f);
	tUINT32 screenOriginAtBottom = (fileheader.descriptor & 0x20) ? 0 : 1;
	//tUINT32 interleaving = (fileheader.descriptor & 0xc0) >> 6;	//0=non-interleaved.  1=two-way (even/odd) interleaving,2=four way interleaving. 

	if (fileheader.bits == 32) {
		properties.create(fileheader.width, fileheader.height, CImageData::eRGBA);
	} else if (fileheader.bits == 24) {
		properties.create(fileheader.width, fileheader.height, CImageData::eRGB);
	} else {
		return FALSE;
	}

	if (fileheader.imagetype & 8) {	// compressed
		// Compressed images are loaded into a separate buffer
		// and then decompressed into the bitmap buffer.

		// BUGWARN: This doesn't consider the span

		tUINT32 fileSize = file.getFileSize();
		tBYTE *pFileData = new tBYTE[fileSize];
		(void)file.read(pFileData, fileSize);	// this will over-read a few bytes

		tUINT32 pixelsLeft = fileheader.width * fileheader.height;
		tUINT32 total = 0;

		tBYTE *p = pFileData;
		tBYTE *ptr = properties.getBitmap();

		while(total < pixelsLeft) {
			tBYTE code = *p++;
			tINT32 count = (code & 0x7f) + 1;
			total += count;
			if (fileheader.bits == 32) {
				if (code & 0x80) {
					// dup
					tBYTE b = *p++;
					tBYTE g = *p++;
					tBYTE r = *p++;
					tBYTE a = *p++;
					while(--count >= 0) {
						*ptr++ = r;
						*ptr++ = g;
						*ptr++ = b;
						*ptr++ = a;
					}
				} else {
					// copy
					while(--count >= 0) {
						*ptr++ = *(p+2);
						*ptr++ = *(p+1);
						*ptr++ = *(p+0);
						*ptr++ = *(p+3);
						p += 4;
					}
				}
			} else {	// 24 bits
				if (code & 0x80) {
					// dup
					tBYTE b = *p++;
					tBYTE g = *p++;
					tBYTE r = *p++;
					while(--count >= 0) {
						*ptr++ = r;
						*ptr++ = g;
						*ptr++ = b;
					}
				} else {
					// copy
					while(--count >= 0) {
						*ptr++ = *(p+2);
						*ptr++ = *(p+1);
						*ptr++ = *(p+0);
						p += 3;
					}
				}
			}
		}
		//
		if (screenOriginAtBottom) {
			tUINT32 span = (fileheader.bits/8)*fileheader.width;
			tBYTE *pBase = ptr - span;
			ptr = properties.getBitmap();
			for(tUINT32 y=fileheader.height/2;y>0;--y) {
				for(tUINT32 x=0;x<span;++x) {
					tBYTE t=*pBase;
					*pBase = *ptr;
					*ptr = t;
					++ptr;
					++pBase;
				}
				// ptr is already on the next line
				pBase -= span*2;	// the line we've just scan, and another to go to the previous
			}
		}



		delete[] pFileData;
	} else {	// uncompressed
		tBYTE *ptr = properties.getBitmap();

		if (screenOriginAtBottom) {
			ptr += properties.getSpan() * properties.getHeight();
		}

		for(tINT16 y=0;y<fileheader.height;++y) {
			
			if (screenOriginAtBottom) {
				ptr -= properties.getSpan();
			}
		
			file.read(ptr, properties.bpl());

			if (fileheader.bits == 32) {
				for(tINT16 x=0;x<fileheader.width;++x) {
					// Swap RGB: on disc it's BGRA, on memory it's RGBA
					tBYTE t = *(ptr+0);
					*(ptr+0) = *(ptr+2);
					*(ptr+2) = t;
		
					// This let's us read RGB and RGBA surfaces. We could also have
					// used .bpp()
					ptr += fileheader.bits/8;
				}
			} else {
				for(tINT16 x=0;x<fileheader.width;++x) {
					// Swap RGB
					tBYTE t = *(ptr+0);
					*(ptr+0) = *(ptr+2);
					*(ptr+2) = t;
					
					// This let's us read RGB and RGBA surfaces. We could also have
					// used .bpp()
					ptr += fileheader.bits/8;
				}
			}

			ptr -= properties.bpl();
			if (!screenOriginAtBottom) {
				ptr += properties.getSpan();
			}
		}	
	}

	return TRUE;
}

tBOOL CTGAParser::close(CImageData &properties) const
{
	SGX_UNUSED(properties);
	return TRUE;
}

tBOOL
CTGAParser::exportTexture(const sgxString &filename, CSGXTexture *pTexture) {
	tBOOL rt = FALSE;
	CSGXFile file(filename, "rw");
	if (file.open()) {
		rt = exportTexture(file, pTexture);
		file.close();
	}
	return rt;
}

tBOOL
CTGAParser::exportTexture(CSGXFile &file, CSGXTexture *pTexture) {
	if (!pTexture || !pTexture->m_pSurface) {
		return FALSE;
	}

	CImageData data;
	if (file.isValid()) {
		if (pTexture->m_pSurface->lock(data)) {
			// TGA header
			sgxTGAHeader hdr;
			hdr.identsize = 18;
			hdr.colourmaptype = 0;
			hdr.imagetype = 2;	// uncompessed
			hdr.width = (tINT16)data.getWidth();
			hdr.height = (tINT16)data.getHeight();
			hdr.xstart = hdr.ystart = 0;
			hdr.bits = 24;
			hdr.descriptor = 0;
			hdr.colourmapstart = 0;
			hdr.colourmaplength = 0;
			hdr.colourmapbits = 24;

			file.write((tBYTE *)&hdr, sizeof(hdr));

			// Pixel data
			sgxColorRGBA col;
			tBYTE *ptr = data.getBitmap();
			tINT32 bpp = data.bpp();
			tINT32 span = data.getSpan();
			for(tINT32 y=0;y<hdr.height;++y) {
				for(tINT32 x=0;x<hdr.width;++x) {
					tBYTE r = *ptr++;
					tBYTE g = *ptr++;
					tBYTE b = *ptr++;
					file << b;
					file << g;
					file << r;
				}
				ptr -= (hdr.width*bpp);
				ptr += span;
			}
			//
			pTexture->m_pSurface->unlock();
		}

		return TRUE;
	}

	return FALSE;
}
