// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation; either version 2 of the License, or
//   (at your option) any later version.
//  
//   This program is distributed in the hope that it will be useful, but
//   WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//   General Public License for more details.
// 
//   You should have received a copy of the GNU General Public License
//   along with this program; if not, write to the Free Software
//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
//   USA
// -------------------------- END HEADER -------------------------------------

#include "../hacks/winhacks.h"

#include <iostream>
#include <iomanip>
#include <cstdlib>
using namespace std;

#ifdef USE_SDL
#include <SDL.h>
#endif

#ifndef NO_GL
#include <GL/gl.h>
#include <GL/glext.h>
#endif

#include "image.h"

Image::Image(void) {
	pixels = NULL;
	width = 0;
	height = 0;
}

Image::Image(int width, int height) {
	this->width = width;
	this->height = height;
	this->pixels = new Pixel[width * height];
}

Image::~Image(void) {
	delete[] pixels;
}

// Move forward in the input stream.
#define INCPTR(x) if(p - (unsigned char*)tgaData + x > length) { if(img) delete img; return NULL; } p += x

// Move forward in our output stream.  There's no overflow check here
// because we allocate enough to begin with.  Uh.... in theory.
#define SKIPBYTES(x) { for(int i = x; i > 0; i--) { *p = 0; p++; } }

unsigned char *Image::saveTGA(int *length) {

	// This assumes we want a 32-bit, uncompressed, non-color-mapped
	// TGA with alpha.

	*length = sizeof(Pixel) * width * height + 18;
	unsigned char *out = new unsigned char[*length];
	unsigned char *p = out;

	// We're going to skip through most of the header, except for the
	// imageType, which must be 2 (RGBA, uncompressed).

	SKIPBYTES(2); // idLength
	              // colorMapType

	*p = 2;	p++;  // imageType

	SKIPBYTES(9); // firstEntryIndex (2)
                  // colorMapLength (2)
                  // colorMapEntrySize
                  // xOrigin (2)
                  // yOrigin (2)

	// Important stuff! 

	// FIXME: Endian issues will happen here with
	// all the unsigned shorts.

	// Width and height
	*(unsigned short*)p = width;  p += 2;
	*(unsigned short*)p = height; p += 2;

	// Depth
	*p = 32; p++;

	// Flip bit thing. We need this or the image pops out upside down.
	*p = 1 << 5; p++;

	// Write out the pixels.
	for(int y = 0; y < height; y++) {
		for(int x = 0; x < width; x++) {
			Pixel *dp = getPixel(x, y);
			
			*p = dp->rgba.r; p++;
			*p = dp->rgba.g; p++;
			*p = dp->rgba.b; p++;
			*p = dp->rgba.a; p++;
		}
	}

	return out;
}

#ifndef NO_GL

void Image::createGLTexture(ImageTexture *imgTex) {
	GLuint texNum;
	glGenTextures(1, &texNum);
	
	glBindTexture(GL_TEXTURE_2D, texNum);
	
	imgTex->glTexNum = texNum;
	
	imgTex->pow2Width = 1;
	while(imgTex->pow2Width < (unsigned int)width) {
		imgTex->pow2Width <<= 1;
	}
	
	imgTex->pow2Height = 1;
	while(imgTex->pow2Height < (unsigned int)height) {
		imgTex->pow2Height <<= 1;
	}
	
	imgTex->origWidth = width;
	imgTex->origHeight = height;
	
	Image newImg(imgTex->pow2Width, imgTex->pow2Height);
	
	for(unsigned int x = 0; x < imgTex->pow2Width; x++) {
		for(unsigned int y = 0; y < imgTex->pow2Height; y++) {
			if(x < (unsigned int)width && y < (unsigned int)height) {
				const Pixel *src = getPixel(x, y);
				Pixel p;
				p.colorsAsArray[3] = src->rgba.a;
				p.colorsAsArray[1] = src->rgba.g;
				
				p.colorsAsArray[0] = src->rgba.r;
				p.colorsAsArray[2] = src->rgba.b;
				
				newImg.setPixel(p, x, y);
			} else {
				Pixel black = {0};
				newImg.setPixel(black, x, y);
			}
		}
	}
	
	// FIXME: Endian issues here, probably.
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imgTex->pow2Width, imgTex->pow2Height, 0, GL_BGRA, GL_UNSIGNED_BYTE, newImg.pixels);
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	
}
#endif

#ifdef USE_SDL
SDL_Surface *Image::createSurface(void) {
	// Fuck your goddamn endian stupidity. :argh:
	return SDL_CreateRGBSurfaceFrom(
		pixels,
		width,
		height,
		32,
		4 * width,
		0x00ff0000,
		0x0000ff00,
		0x000000ff,
		0xff000000);
}
#endif


void loadTGAHeaderGetSize(void *tgaData, int *width, int *height) {

	unsigned char *p = ((unsigned char*)tgaData) + 12;	
	*width             = *(unsigned short*)p; p += 2;
	*height            = *(unsigned short*)p;

}

Image *loadTGA(void *tgaData, int length, bool convertPink) {
	
	Image *img = NULL;
	
	int idLength;
	int colorMapType;
	int imageType;

	// Color map crap.
	int firstEntryIndex;
	int colorMapLength;
	int colorMapEntrySize;

	// This is meaningless to me~
	int xOrigin;
	int yOrigin;

	int width;
	int height;
	int depth;
	int imageDescriptor;

	unsigned char *p = (unsigned char*)tgaData;
	
	// Buffer not even long enough for the header?
	if(length < 18) return NULL;

	// Read in the header.
	// FIXME: Endian issues will be here.
	idLength          = *p; p++;
	colorMapType      = *p; p++;
	imageType         = *p; p++;
	firstEntryIndex   = *(unsigned short*)p; p += 2;
	colorMapLength    = *(unsigned short*)p; p += 2;
	colorMapEntrySize = *p; p++;
	xOrigin           = *(unsigned short*)p; p += 2;
	yOrigin           = *(unsigned short*)p; p += 2;
	width             = *(unsigned short*)p; p += 2;
	height            = *(unsigned short*)p; p += 2;
	depth             = *p; p++;
	imageDescriptor   = *p; p++;

	// Skip image descriptor.
	INCPTR(idLength);

	// This loader only works with true-color images and greyscale
	// right now. RLE and non-RLE are okay, though.
	switch(imageType) {
	case 2:  // Truecolor, uncompressed.
	case 3:  // Greyscale, uncompressed.
	case 10: // Truecolor, RLE
	case 11: // Greyscale, RLE
		break;
	default:
		// Can't load this image. Not the right type.
		return NULL;
	}

	// TODO: Add colormap support?

	int colorMapSize;
	if(colorMapEntrySize != 15) {
		colorMapSize = (colorMapLength * colorMapEntrySize) / 8;
	} else {
		// If the color map entries are 15 bits each, we still have to
		// read them as though they're 16 bits, and just ignore the
		// last bit.
		colorMapSize = (colorMapLength * 16) / 8;
	}

	// Skip color map. Lalala
	if(length < 18 + colorMapSize) return NULL;
	p += colorMapSize;

	// Allocate the image.
	img = new Image(width, height);
	if(!img) return NULL;

	int totalPx = width * height;
	int index = 0;

	bool useRle = (imageType >= 9 && imageType <= 11);
	bool flipVertically = !(imageDescriptor & (1 << 5));

	if(flipVertically) {
		// If we're vertically flipped, then we need to start on the last row.
		index = width * (height - 1);
	}

	while(index < totalPx && index >= 0) {
		
		int runLength = 1;
		int rawLength = 1;

		if(useRle) {
			// Get the number of pixels this next thing represents.
			runLength = (*p);
			INCPTR(1);

			// Figure out if it was actually a run length or a raw
			// length. Note that the value read in is actually the
			// number of bytes minus one, so we need to add one to
			// whichever one of these it was.
			if(runLength >= 128) {
				// Run length. Remove the first bit and add
				// 1. (Subtracting 127 does this.)
				runLength -= 127;
				rawLength = 1;
			} else {
				// Raw length. Add one and swap run/raw.
				rawLength = runLength + 1;
				runLength = 1;
			}
		}
		
		while(rawLength) {

			// Get the pixel value.
			Pixel dp;

			if(imageType == 10 || imageType == 2) {

				// True color

				if(depth == 32) {				

					// 32 bit BGRA.
				
					INCPTR(4);
					dp.rgba.r = *(p - 4);
					dp.rgba.g = *(p - 3);
					dp.rgba.b = *(p - 2);
					dp.rgba.a = *(p - 1);

				} else if(depth == 24) {

					// 24 bit BGR.

					INCPTR(3);
					dp.rgba.r = *(p - 3);
					dp.rgba.g = *(p - 2);
					dp.rgba.b = *(p - 1);
					dp.rgba.a = 255;

				} else {

					// Uh...... 16 or 15 bit?
					dp.value = 0xffffffff;
				}

			} else if(imageType == 3 || imageType == 11) {

				// Greyscale

				if(depth == 16) {

					// Grey with alpha.
					INCPTR(2);
					dp.rgba.b = dp.rgba.g = dp.rgba.r = *(p - 2);
					dp.rgba.a = *(p - 1);

				} else if(depth == 8) {

					// Grey, no alpha.
					INCPTR(1);
					dp.rgba.b = dp.rgba.g = dp.rgba.r = *(p - 1);
					dp.rgba.a = 255;

				} else {

					// I don't know blah blah blah
					dp.value = 0xffffffff;

				}

			} else {

				// I don't even know what this is blah blah blah.
				dp.value = 0xffffffff;

			}

			if(convertPink) {

				// Convert 0xff00ff?? to alpha 0.
				if(dp.rgba.r == 255 && dp.rgba.g == 0 && dp.rgba.b == 255) {
					dp.rgba.a = 0;
				}
			}

			// Set the pixels.
			while(runLength) {

				img->setPixel(dp, index);

				index++;

				if(flipVertically) {
					if(!(index % width)) {
						// Went past an edge.
						index -= width * 2;
					}
				}

				runLength--;
			}

			runLength = 1;
			rawLength--;
		}
	}

	return img;
}




