/*
 *  Texture.cpp
 *  iphone-gl-app
 *
 *  Created by John Ryland on 14/06/09.
 *  Copyright 2009 InvertedLogic. All rights reserved.
 *
 */

#include "Debug.h"
#include <OpenGLES/ES1/gl.h>
#include <OpenGLES/ES1/glext.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include "Texture.h"
#include "Image.h"


//#define USE_HEAP_FOR_LARGE_TEMPORARY_DATA				1


struct TexturePrivate
{
	int shiftU, shiftV;
	int textureCoords[8];
	unsigned int textureId;
	bool smooth;
	void initWithData(unsigned int width, unsigned int height, unsigned char *data, bool smooth);	
};


Texture::Texture(unsigned int width, unsigned int height, unsigned char *data, bool s)
{
	TexturePrivate *d = new TexturePrivate;
	d->initWithData(width, height, data, s);
	dptr = (void *)d;
}


Texture::Texture(const char *file, bool s)
{
	Image img(file);
	TexturePrivate *d = new TexturePrivate;
	d->initWithData(img.width(), img.height(), img.data(), s);
	dptr = (void *)d;
}


Texture::~Texture()
{
	glDeleteTextures(1, &((TexturePrivate *)dptr)->textureId);
	delete (TexturePrivate *)dptr;
}


void TexturePrivate::initWithData(unsigned int width, unsigned int height, unsigned char *data, bool s)
{
	// Generate an identifier for our texture and enable
	glEnable(GL_TEXTURE_2D);
	glGenTextures(1, &textureId);
	int oldTextureId;
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldTextureId); // Save current texture state before modify
	glBindTexture(GL_TEXTURE_2D, textureId);

	// Calculate factors used to convert integer pixel offsets
	// in to fixed point offsets in OpenGL texture coordinates.
	// Calculating shiftU and shiftV to contain the bit position of the MSB of width and height.
//    shiftU = integerMSB(width);
//    shiftV = integerMSB(height);
	shiftU = 0;
	shiftV = 0;
	const int mask[5] = { 65536, 256, 16, 4, 2 };
	for (int i = 0; i < 5; i++) {
		shiftU += (width >= (mask[i] << shiftU)) ? (16>>i) : 0;
		shiftV += (height >= (mask[i]<< shiftV)) ? (16>>i) : 0;
	}

	int texW = 65536;
	int texH = 65536;
	// If width is a power of 2, it will contain only one bit, eg equal to 1 << 'MSB of width'
	if (width != (1 << shiftU) || height != (1 << shiftV)) {
		// OpenGL requires the texture dimensions to be powers of 2. If our image
		// doesn't have correct dimensions, we fix it by copying the data in to a
		// temporary buffer that has dimensions of powers of 2.
		DebugMessage::warning("Texture dimensions need to be powers of 2. Fixing.\n");
		DebugMessage::warning("This wastes texture memory so consider optimizing the dimensions of your image resources.\n");
		int oldW = width;
		int oldH = height;
		if (width != (1 << shiftU)) {
			shiftU++;
			width = (1 << shiftU); // width is now the next highest power of 2
		}
		if (height != (1 << shiftV)) {
			shiftV++;
			height = (1 << shiftV); // height also rounded up to next highest power of 2
		}
		const int dataSize = width * height * 4;
#ifdef USE_HEAP_FOR_LARGE_TEMPORARY_DATA
		unsigned char *tmpData = (unsigned char *)malloc(dataSize); // Allocated on the heap
#else
		unsigned char tmpData[dataSize]; // Allocated on the stack
#endif
		for (int i = 0; i < oldH; i++)
			memcpy(tmpData + width*i*4, data + oldW*i*4, oldW*4);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmpData);
#ifdef USE_HEAP_FOR_LARGE_TEMPORARY_DATA
		free(tmpData);
#endif

		// Scale texture coordinates to only map the image bits
		texW = oldW * 65536 / width;
		texH = oldH * 65536 / height;
	} else {
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
	}

	const int defaultTextureCoords[8] = { 0, 0, texW, 0, 0, texH, texW, texH };
	for (int i = 0; i < 8; i++)
		textureCoords[i] = defaultTextureCoords[i];
	smooth = s;

	glBindTexture(GL_TEXTURE_2D, oldTextureId); // restore
}


void Texture::setExtents(int x, int y, int w, int h)
{
	if (x == -1) {
/*
		const int defaultTextureCoords[8] = { 0, 0, texW, 0, 0, texH, texW, texH };
		for (int i = 0; i < 8; i++)
			textureCoords[i] = defaultTextureCoords[i];
 */
		return;
	}
	int shiftU = 16 - ((TexturePrivate *)dptr)->shiftU;
	int shiftV = 16 - ((TexturePrivate *)dptr)->shiftV;
	int x1 = x << shiftU;
	int y1 = y << shiftV;
	int x2 = (x+w) << shiftU;
	int y2 = (y+h) << shiftV;
	int texCoords[8] = { x1, y1, x2, y1, x1, y2, x2, y2 };
	for (int i = 0; i < 8; i++)
		((TexturePrivate *)dptr)->textureCoords[i] = texCoords[i];	
}


void Texture::setSmooth(bool s)
{
	((TexturePrivate *)dptr)->smooth = s;
}


void Texture::bind()
{
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnable(GL_BLEND);
//	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	glBindTexture(GL_TEXTURE_2D, ((TexturePrivate *)dptr)->textureId);

	// The arguments below mean 2 fixed values per coord in a list that
	// is tightly packed. Our list contains 4 coords which defines 2 triangles.
	// So this code assumes that the texture is bound to a square made
	// of 2 triangles such as from verticies from a Sprite object.
	glTexCoordPointer(2, GL_FIXED, 0, ((TexturePrivate *)dptr)->textureCoords);

	if ( ((TexturePrivate *)dptr)->smooth ) {
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	} else {
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	}
}

