//	GLESDrawer.cpp - OpenGL ES 2.0 mode drawer.
//  ----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2012  Niall Moody
//	
//	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 3 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, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#include "GLESDrawer.h"
#include "FreetypeFontAtlas.h"
#include "DebugHeaders.h"
#include "ImageLoader.h"
#include "MiscHelper.h"
#include "UTF8File.h"

#include <cassert>

using namespace std;

//------------------------------------------------------------------------------
GLESDrawer::GLESDrawer():
drawingSize(1280.0f, 720.0f),
windowSize(1280.0f, 720.0f),
lastTexture(0),
lastSecondTexture(0),
lastVBO(0),
fontAtlasCreator(FreetypeFontAtlas::creator),
texInterp(true),
line_pngSize(144)
{
	ShaderInternal tempShader;

	nullImage.texture = 0;
	nullImage.size = TwoFloats(0.0f);
	nullImage.posWithin = TwoFloats(0.0f);
	nullImage.sizeWithin = TwoFloats(1.0f);

	//This is to avoid a shaders[L""] being created with junk data.
	tempShader.shader = 0;
	tempShader.baseTexLoc = -1;
	tempShader.secondTexLoc = -1;
	shaders.insert(make_pair(L"", tempShader));
}

//------------------------------------------------------------------------------
GLESDrawer::~GLESDrawer()
{

}

//------------------------------------------------------------------------------
void GLESDrawer::initialise(int screenWidth,
								   int screenHeight,
								   const AppSettings& appSettings)
{
	float windowAspect;
	float drawingAspect;
	AppSettings::AspectRatioType ratioType = appSettings.getDrawingAreaAspect();

	//Set the sizes of the drawing area and window.
	windowSize.x = (float)screenWidth;
	windowSize.y = (float)screenHeight;
	drawingSize.x = (float)appSettings.getDrawingAreaWidth();
	drawingSize.y = (float)appSettings.getDrawingAreaHeight();
	windowAspect = windowSize.y/windowSize.x;
	drawingAspect = drawingSize.y/drawingSize.x;
	
	//If we're in an AlterRatio setup, we may need to update drawingSize
	//accordingly.
	if(ratioType == AppSettings::AlterRatio)
	{
		if(windowAspect != drawingAspect)
		{
			//Stretch/squash the drawing area along the y-axis.
			drawingSize.y = drawingSize.x * windowAspect;
		}
	}

	//Initialise GLEW.
#ifndef OSX
	GLenum err;

	if(!glewAlready)
	{
		err = glewInit();
		if(err != GLEW_OK)
		{
			wstring tempstr;

			UTF8File::charToWstring(string((const char *)glewGetErrorString(err)), tempstr);
			DebugStatement(L"GLEW error: " << tempstr.c_str());
		}
		else
		{
			wstring tempstr;

			UTF8File::charToWstring(string((const char *)glewGetString(GLEW_VERSION)), tempstr);
			DebugStatement(L"Using GLEW version: " << tempstr);
			glewAlready = true;
		}
	}
#else
	DebugStatement(L"OSX, Not using GLEW.");
#endif

#ifdef DEBUG_STATEMENTS
	//Check the maximum texture size on this system.
	{
		GLint tempint;
		glGetIntegerv(GL_MAX_TEXTURE_SIZE, &tempint);
		DebugStatement("GL_MAX_TEXTURE_SIZE = " << tempint);

		glTexImage2D(GL_PROXY_TEXTURE_2D,
						 0,
						 GL_RGBA,
						 2048,
						 2048,
						 0,
						 GL_RGBA,
						 GL_UNSIGNED_BYTE,
						 NULL);
		glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tempint);
		DebugStatement("GL_TEXTURE_WIDTH = " << tempint);
	}
#endif

	//First setup the full window as a viewport.
	glViewport(0, 0, screenWidth, screenHeight);

	//Clear to black (so that if we're widescreen and using
	//AppSettings::KeepRatio we'll have black bars at the sides).
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	//Setup the viewport to draw into.
	if(ratioType == AppSettings::KeepRatio)
	{
		//In KeepRatio mode, we setup another viewport so the desired aspect
		//ratio is retained, w/black bars to ensure it's retained.
		if(windowAspect != drawingAspect)
		{
			if(windowAspect < drawingAspect)
			{
				viewportSize.x = (drawingSize.x/drawingSize.y) * windowSize.y;
				viewportSize.y = windowSize.y;
				viewportPos.x = (windowSize.x-viewportSize.x)*0.5f;
				viewportPos.y = 0;
			}
			else if(windowAspect > drawingAspect)
			{
				viewportSize.x = windowSize.x;
				viewportSize.y = windowSize.x * drawingAspect;
				viewportPos.x = 0.0f;
				viewportPos.y = (windowSize.y-viewportSize.y)*0.5f;
			}

			DebugStatement(L"GLESDrawer: Setting viewport to: " << viewportPos.x << L"x" << viewportPos.y << L" : " << viewportSize.x << L"x" << viewportSize.y);
			glViewport((int)viewportPos.x,
					   (int)viewportPos.y,
					   (int)viewportSize.x,
					   (int)viewportSize.y);
		}
	}

	if(viewportSize.x <= 0.0f)
		viewportSize.x = windowSize.x;
	if(viewportSize.y <= 0.0f)
		viewportSize.y = windowSize.y;

	//Load the default shader.
	{
		GLuint shader;

		loadShader(defaultVertexShader, defaultFragmentShader, L"-default-shader-");
		useShader(L"-default-shader-");

		shader = shaders[currentShader].shader;

		projectionLoc = glGetUniformLocation(shader, "avlProjectionMatrix");
		matrixLoc = glGetUniformLocation(shader, "avlModelviewMatrix");

		colourLoc = glGetUniformLocation(shader, "avlColour");
		baseTextureLoc = glGetUniformLocation(shader, "avlBaseTexture");
		secondTextureLoc = glGetUniformLocation(shader, "avlSecondTexture");
		textureExistsLoc = glGetUniformLocation(shader, "avlTextureExists");
	}

	//Setup the VBO used by fillRect().
	{
		GLuint vertexBuffer;
		GLfloat bufferData[] = {0.0f, 0.0f,
								0.0f, 1.0f,
								1.0f, 0.0f,
								1.0f, 1.0f};

		glGenVertexArrays(1, &rectArray);
		glBindVertexArray(rectArray);

		glGenBuffers(1, &vertexBuffer);
		glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
		glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*8, bufferData, GL_DYNAMIC_DRAW);

		glEnableVertexAttribArray(vertexPositionLoc);
		glVertexAttribPointer(vertexPositionLoc,
								2,
								GL_FLOAT,
								GL_FALSE,
								sizeof(GLfloat)*2,
								0);

		glBindVertexArray(0);
	}

	//Initialise frame buffer object.
	if(canSupport(FrameBuffer))
	{
		GLuint depthBuffer;
		ESImage tempIm;

		//Generate and bind the frame buffer object.
		glGenFramebuffersEXT(1, &frameBufferObject);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBufferObject);
		
		//Generate and attach the frame buffer's depth buffer.
		glGenRenderbuffersEXT(1, &depthBuffer);
		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer);
		glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,
								 GL_DEPTH_COMPONENT,
								 (int)drawingSize.x,
								 (int) drawingSize.y);
		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
									 GL_DEPTH_ATTACHMENT_EXT,
									 GL_RENDERBUFFER_EXT,
									 depthBuffer);

		//Generate the frame buffer's texture.
		tempIm.size.x = drawingSize.x;
		tempIm.size.y = drawingSize.y;
		tempIm.sizeWithin.x = 1.0f;
		tempIm.sizeWithin.y = 1.0f;
		glGenTextures(1, &tempIm.texture);
		glBindTexture(GL_TEXTURE_2D, tempIm.texture);
		glTexImage2D(GL_TEXTURE_2D,
					 0,
					 GL_RGBA8,
					 (int)drawingSize.x,
					 (int)drawingSize.y,
					 0,
					 GL_RGBA,
					 GL_UNSIGNED_BYTE,
					 NULL);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

		//Attach the texture to the frame buffer.
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
								  GL_COLOR_ATTACHMENT0_EXT,
								  GL_TEXTURE_2D,
								  tempIm.texture,
								  0);

		//Add it to our images map.
		images.insert(make_pair(L"-frame-buffer-image-", tempIm));

		GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
		if(status != GL_FRAMEBUFFER_COMPLETE_EXT)
		{
			DebugStatement(L"GLImmediateDrawer: Could not create frame buffer.");
		}
		
		//Unbind stuff.
		glBindTexture(GL_TEXTURE_2D, 0);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	}

	//Now set up everything else.

	//So our co-ordinates for drawing are 0-drawingAreaWidth (x)
	//and drawingAreaHeight-0 (y).
	projectionMatrix.generateOrthoProjection(0.0f, drawingSize.x, drawingSize.y, 0.0f);
	glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, projectionMatrix.getMatrix());

	glShadeModel(GL_SMOOTH);
	glClearDepth(1.0f);
	glDisable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glEnable(GL_TEXTURE_2D);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);

	//Load the line image.
	if(images.find(L"-default-line") == images.end())
	{
		ImageLoader imLoad;
		ImageData data;
		ESImage tempIm;

		data = imLoad.loadImage(line_png, line_pngSize);
		loadSingleImage(data.data, data.width, data.height, L"-default-line-");
	}
}

//------------------------------------------------------------------------------
void GLESDrawer::setFontRenderer(FontAtlas::Creator& renderer)
{
	fontAtlasCreator = renderer;
}

//------------------------------------------------------------------------------
void GLESDrawer::loadSingleImage(uint8_t *data,
										int width,
										int height,
										const wstring& name)
{
	ESImage tempIm;

	if(data)
	{
		//If it's not a power-of-two-sized image, we need to make it so.
		if((!isPowerOfTwo(width)) || (!isPowerOfTwo(height)))
		{
			uint32_t i;
			uint32_t x, y;
			uint8_t tempint;
			const uint32_t w = powerOfTwo(width);
			const uint32_t h = powerOfTwo(height);
			const uint32_t dataSize = w*h*4;
			uint8_t *tempData = new uint8_t[dataSize];

			//Blank the new image.
			for(i=0;i<dataSize;++i)
				tempData[i] = 0;

			//Copy the old image into the upper left corner.
			i = 0;
			for(y=0;y<(unsigned)height;++y)
			{
				for(x=0;x<((unsigned)width*4);++x)
				{
					tempData[x+(y*w*4)] = data[i];
					++i;
				}
				//Copy the previous value along to fill out the texture.
				tempint = data[i-1];
				for(x=(width*4);x<(w*4);++x)
					tempData[x+(y*w*4)] = tempint;
			}

			//Copy the previous value along to fill out the texture.
			i = (width*height*4) - (width*4); //So we're back to reading the previous line.
			for(x=0;x<(w*4);++x)
			{
				tempint	= data[i];
				for(y=height;y<h;++y)
					tempData[x+(y*w*4)] = tempint;
				if(i < (((unsigned)width*4)-1))
					++i;
			}

			//Inform tempIm of the dimensions.
			tempIm.size.x = (float)width;
			tempIm.size.y = (float)height;
			tempIm.sizeWithin.x = (float)width/(float)w;
			tempIm.sizeWithin.y = (float)height/(float)h;

			DebugStatement(L"GLESDrawer: Image not power-of-two: " << (unsigned int)width << "x" << (unsigned int)height << "\tTexture: " << (unsigned int)w << "x" << (unsigned int)h);

			//Delete the old data.
			delete [] data;
			//Make sure data now points to the new data.
			data = tempData;
		}
		else
		{
			tempIm.size.x = (float)width;
			tempIm.size.y = (float)height;
			tempIm.sizeWithin.x = 1.0f;
			tempIm.sizeWithin.y = 1.0f;
		}

		//Create the texture.
		glGenTextures(1, &(tempIm.texture));

		glBindTexture(GL_TEXTURE_2D, tempIm.texture);

		glTexImage2D(GL_TEXTURE_2D,
					 0,
					 GL_RGBA,
					 (int)tempIm.size.x,
					 (int)tempIm.size.y,
					 0,
					 GL_RGBA,
					 GL_UNSIGNED_BYTE,
					 data);

		if(texInterp)
		{
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		}
		else
		{
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		}
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		glBindTexture(GL_TEXTURE_2D, 0);

		//Get rid of the image data.
		delete [] data;

		//Setup the associated vertex buffer object.
		{
			//						vertex							texture
			GLfloat bufferData[] = {0.0f, 0.0f,						0.0f, 0.0f,
									0.0f, tempIm.size.y,			0.0f, tempIm.sizeWithin.y,
									tempIm.size.x, 0.0f,			tempIm.sizeWithin.x, 0.0f,
									tempIm.size.x, tempIm.size.y,	tempIm.sizeWithin.x, tempIm.sizeWithin.y};

			glGenVertexArrays(1, &tempIm.vertexArray);
			glBindVertexArray(tempIm.vertexArray);

			glGenBuffers(1, &tempIm.vertexBuffer);
			glBindBuffer(GL_ARRAY_BUFFER, tempIm.vertexBuffer);
			glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*16, bufferData, GL_DYNAMIC_DRAW);

			glEnableVertexAttribArray(vertexPositionLoc);
			glEnableVertexAttribArray(baseTexCoordsLoc);
			glEnableVertexAttribArray(secondTexCoordsLoc);
			glVertexAttribPointer(vertexPositionLoc,
								  2,
								  GL_FLOAT,
								  GL_FALSE,
								  sizeof(GLfloat)*4,
								  0);
			glVertexAttribPointer(baseTexCoordsLoc,
								  2,
								  GL_FLOAT,
								  GL_FALSE,
								  sizeof(GLfloat)*4,
								  (void *)(sizeof(GLfloat)*2));
			glVertexAttribPointer(secondTexCoordsLoc,
								  2,
								  GL_FLOAT,
								  GL_FALSE,
								  sizeof(GLfloat)*4,
								  (void *)(sizeof(GLfloat)*2));

			glBindVertexArray(0);
			//lastVBO = tempIm.vertexArray;
		}

		//Add tempIm to our list of images.
		images.insert(make_pair(name, tempIm));
	}
	else
	{
		DebugStatement(L"GLESDrawer::loadImage(): No data passed in!");
	}
}

//------------------------------------------------------------------------------
void GLESDrawer::loadTextureAtlas(uint8_t *data,
										 int width,
										 int height,
										 const TextureAtlas& atlas)
{
	ESImage tempTexture;

	if(data)
	{
		//If it's not a power-of-two-sized image, we need to make it so.
		if((!isPowerOfTwo(width)) || (!isPowerOfTwo(height)))
		{
			uint32_t i;
			uint32_t x, y;
			uint8_t tempint;
			const uint32_t w = powerOfTwo(width);
			const uint32_t h = powerOfTwo(height);
			const uint32_t dataSize = w*h*4;
			uint8_t *tempData = new uint8_t[dataSize];

			//Blank the new image.
			for(i=0;i<dataSize;++i)
				tempData[i] = 0;

			//Copy the old image into the upper left corner.
			i = 0;
			for(y=0;y<(unsigned)height;++y)
			{
				for(x=0;x<((unsigned)width*4);++x)
				{
					tempData[x+(y*w*4)] = data[i];
					++i;
				}
				//Copy the previous value along to fill out the texture.
				tempint = data[i-1];
				for(x=(width*4);x<(w*4);++x)
					tempData[x+(y*w*4)] = tempint;
			}

			//Copy the previous value along to fill out the texture.
			i = (width*height*4) - (width*4); //So we're back to reading the previous line.
			for(x=0;x<(w*4);++x)
			{
				tempint	= data[i];
				for(y=height;y<h;++y)
					tempData[x+(y*w*4)] = tempint;
				if(i < (((unsigned)width*4)-1))
					++i;
			}

			//Inform tempIm of the dimensions.
			tempTexture.size.x = (float)width;
			tempTexture.size.y = (float)height;
			tempTexture.sizeWithin.x = (float)width/(float)w;
			tempTexture.sizeWithin.y = (float)height/(float)h;

			DebugStatement(L"GLESDrawer: Image not power-of-two: " << (unsigned int)width << "x" << (unsigned int)height << "\tTexture: " << (unsigned int)w << "x" << (unsigned int)h);

			//Delete the old data.
			delete [] data;
			//Make sure data now points to the new data.
			data = tempData;
		}
		else
		{
			tempTexture.size.x = (float)width;
			tempTexture.size.y = (float)height;
			tempTexture.sizeWithin.x = 1.0f;
			tempTexture.sizeWithin.y = 1.0f;
		}

		//Create the texture.
		glGenTextures(1, &(tempTexture.texture));

		glBindTexture(GL_TEXTURE_2D, tempTexture.texture);

		glTexImage2D(GL_TEXTURE_2D,
					 0,
					 GL_RGBA,
					 (int)tempTexture.size.x,
					 (int)tempTexture.size.y,
					 0,
					 GL_RGBA,
					 GL_UNSIGNED_BYTE,
					 data);

		if(texInterp)
		{
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		}
		else
		{
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		}
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		glBindTexture(GL_TEXTURE_2D, 0);

		//Get rid of the image data.
		delete [] data;

		//Add all the images in the atlas to images.
		{
			int i;

			for(i=0;i<atlas.getNumImages();++i)
			{
				ESImage tempIm;
				const AtlasImage& atlasIm = atlas.getImage(i);

				tempIm.texture = tempTexture.texture;
				//Atlas image size in pixels.
				tempIm.size.x = atlasIm.size.x * tempTexture.sizeWithin.x * tempTexture.size.x;
				tempIm.size.y = atlasIm.size.y * tempTexture.sizeWithin.y * tempTexture.size.y;
				//Atlas image position & size relative to the texture.
				tempIm.posWithin.x = atlasIm.position.x * tempTexture.sizeWithin.x;
				tempIm.posWithin.y = atlasIm.position.y * tempTexture.sizeWithin.y;
				tempIm.sizeWithin.x = atlasIm.size.x * tempTexture.sizeWithin.x;
				tempIm.sizeWithin.y = atlasIm.size.y * tempTexture.sizeWithin.y;

				//Setup the associated vertex buffer object.
				{
					//						vertex							texture
					GLfloat bufferData[] = {0.0f, 0.0f,						tempIm.posWithin.x, tempIm.posWithin.y,
											0.0f, tempIm.size.y,			tempIm.posWithin.x, tempIm.posWithin.y + tempIm.sizeWithin.y,
											tempIm.size.x, 0.0f,			tempIm.posWithin.x + tempIm.sizeWithin.x, tempIm.posWithin.y,
											tempIm.size.x, tempIm.size.y,	tempIm.posWithin.x + tempIm.sizeWithin.x, tempIm.posWithin.y + tempIm.sizeWithin.y};

					glGenVertexArrays(1, &tempIm.vertexArray);
					glBindVertexArray(tempIm.vertexArray);

					glGenBuffers(1, &tempIm.vertexBuffer);
					glBindBuffer(GL_ARRAY_BUFFER, tempIm.vertexBuffer);
					glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*16, bufferData, GL_DYNAMIC_DRAW);

					glEnableVertexAttribArray(vertexPositionLoc);
					glEnableVertexAttribArray(baseTexCoordsLoc);
					glEnableVertexAttribArray(secondTexCoordsLoc);
					glVertexAttribPointer(vertexPositionLoc,
										  2,
										  GL_FLOAT,
										  GL_FALSE,
										  sizeof(GLfloat)*4,
										  0);
					glVertexAttribPointer(baseTexCoordsLoc,
										  2,
										  GL_FLOAT,
										  GL_FALSE,
										  sizeof(GLfloat)*4,
										  (void *)(sizeof(GLfloat)*2));
					glVertexAttribPointer(secondTexCoordsLoc,
										  2,
										  GL_FLOAT,
										  GL_FALSE,
										  sizeof(GLfloat)*4,
										  (void *)(sizeof(GLfloat)*2));

					glBindVertexArray(0);
				}

				images.insert(make_pair(atlasIm.name, tempIm));
			}
		}
	}
	else
	{
		DebugStatement(L"GLESDrawer::loadImage(): No data passed in!");
	}
}

//------------------------------------------------------------------------------
void GLESDrawer::releaseImage(const wstring& name)
{
	GLuint texture;
	GLuint vertexArray;
	GLuint bufferObject;
	map<wstring, ESImage>::iterator it = images.find(name);

	if(it != images.end())
	{
		//Delete the texture.
		texture = it->second.texture;
		glDeleteTextures(1, &texture);

		//Delete the VBO and VAO.
		bufferObject = it->second.vertexBuffer;
		vertexArray = it->second.vertexArray;
		glDeleteBuffers(1, &bufferObject);
		glDeleteVertexArrays(1, &vertexArray);

		images.erase(it);

		//Now check whether we have any other images registered which used the
		//same texture.
		for(it=images.begin();it!=images.end();)
		{
			map<wstring, ESImage>::iterator it2 = it;

			++it;

			if(it2->second.texture == texture)
			{
				//Delete this image's VBO and VAO.
				bufferObject = it2->second.vertexBuffer;
				vertexArray = it2->second.vertexArray;
				glDeleteBuffers(1, &bufferObject);
				glDeleteVertexArrays(1, &vertexArray);

				images.erase(it2);
			}
		}
	}
	else
	{
		DebugStatement(L"GLESDrawer: Could not find image <" << name << "> to release.");
	}
}

//------------------------------------------------------------------------------
void GLESDrawer::loadShader(const wstring& vertex,
								   const wstring& fragment,
								   const wstring& name)
{
	if(canSupport(Shaders))
	{
		GLuint vertexObject, fragmentObject;
		string vertexText, fragmentText;
		GLuint shaderProgram;
		GLint err;
		
		UTF8File::wstringToChar(vertex, vertexText);
		UTF8File::wstringToChar(fragment, fragmentText);

		//Create the shader objects.
		vertexObject = glCreateShader(GL_VERTEX_SHADER_ARB);
		fragmentObject = glCreateShader(GL_FRAGMENT_SHADER_ARB);

		//Read the shaders from their files.
		const char *vertexChars = vertexText.c_str();
		const char *fragmentChars = fragmentText.c_str();

		//Read the shader source into the shader objects.
		glShaderSource(vertexObject, 1, &vertexChars, NULL);
		glShaderSource(fragmentObject, 1, &fragmentChars, NULL);

		//Compile the shaders.
		glCompileShader(vertexObject);
		glCompileShader(fragmentObject);

		//Test they both compiled okay.
		glGetShaderiv(vertexObject, GL_COMPILE_STATUS, &err);
		if(err == GL_FALSE)
			DebugStatement(L"GLESDrawer: loadShader(); Could not compile Vertex shader " << name);
		glGetShaderiv(fragmentObject, GL_COMPILE_STATUS, &err);
		if(err == GL_FALSE)
			DebugStatement(L"GLESDrawer: loadShader(); Could not compile Fragment shader " << name);

		//Create the program object.
		shaderProgram = glCreateProgram();

		if(shaderProgram)
		{
			ShaderInternal tempShader;

			//Attach the shaders to the program.
			glAttachShader(shaderProgram, vertexObject);
			glAttachShader(shaderProgram, fragmentObject);

			vertexPositionLoc = 0;
			glBindAttribLocation(shaderProgram, vertexPositionLoc, "avlVertexPosition");
			baseTexCoordsLoc = 1;
			glBindAttribLocation(shaderProgram, baseTexCoordsLoc, "avlBaseTexCoords");
			secondTexCoordsLoc = 2;
			glBindAttribLocation(shaderProgram, secondTexCoordsLoc, "avlSecondTexCoords");

			//Finally, link the program.
			glLinkProgram(shaderProgram);

			//Test the linking went okay.
			glGetShaderiv(shaderProgram, GL_LINK_STATUS, &err);
			if(err == GL_FALSE)
				DebugStatement(L"GLESDrawer: loadShader(); Could not link shader " << name);

#ifdef DEBUG_STATEMENTS
			//Output the info log.
			DebugStatement(L"Vertex shader infolog for " << name);
			printInfoLog(vertexObject);
			DebugStatement(L"Fragment shader infolog for " << name);
			printInfoLog(fragmentObject);
			DebugStatement(L"Shader program infolog for " << name);
			printInfoLog(shaderProgram);
#endif
			tempShader.shader = shaderProgram;

			//Add shaderProgram to our shaders map.
			shaders.insert(make_pair(name, tempShader));
		}
		else
			DebugStatement(L"GLESDrawer: Could not create shader program for shader " << name);
	}
}

//------------------------------------------------------------------------------
void GLESDrawer::registerShaderParam(const std::wstring& name)
{
	string narrowName;

	UTF8File::wstringToChar(name, narrowName);

	shaders[currentShader].uniformLocs[name] = glGetUniformLocation(shaders[currentShader].shader,
																	narrowName.c_str());
}

//------------------------------------------------------------------------------
void GLESDrawer::releaseShader(const wstring& name)
{
	map<wstring, ShaderInternal>::iterator it;

	it = shaders.find(name);
	if(it != shaders.end())
	{
		glDeleteProgram(it->second.shader);
		shaders.erase(it);
	}
}

//------------------------------------------------------------------------------
void GLESDrawer::loadFont(const wstring& name,
								 float size,
								 const wstring& identifier)
{
	wstringstream tempstr;
	FontAtlasPtr fontAtlas;
	tempstr << name << L".ttf";
	FilePath fontPath = FontAtlas::findFontFile(name);

	if(fontPath.exists())
	{
		ImageData data;
		ImageLoader imLoad;

		//Create the FontMetrics we'll use.
		fonts.insert(make_pair(identifier, FontInternal()));

		//Create the FontAtlas/TextureAtlas from the font file, fill out the
		//associated FontMetrics.
		fontAtlas = fontAtlasCreator.createInstance(fontPath, size, fonts[identifier].metrics, identifier);

		if(fonts[identifier].metrics.size() > 0)
		{
			//Load the image data from the FontAtlas.
			//data = imLoad.loadImage(fontAtlas->getImageData(), fontAtlas->getImageDataSize());
			data.data = (uint8_t *)(fontAtlas->getImageData());
			data.width = fontAtlas->getAtlasDimensions();
			data.height = fontAtlas->getAtlasDimensions();

			//Load the decoded image data onto the GPU, storing the individual
			//glyphs of the font as Images, identical to the Images we use in
			//drawImage() etc.
			loadTextureAtlas(data.data, data.width, data.height, *fontAtlas);

			//Get currentTexture from one of the glyphs we loaded.
			fonts[identifier].currentTexture = images[fontAtlas->getImage(0).name].texture;
			fonts[identifier].textures.push_back(fonts[identifier].currentTexture);

			fonts[identifier].atlas = fontAtlas;
		}
		else
		{
			DebugStatement(L"GLESDrawer: Could not load font file: " << fontPath.getPath().c_str());

			fonts.erase(fonts.find(identifier));
		}
	}
	else
		DebugStatement(L"GLESDrawer: Font file " << fontPath.getPath().c_str() << L" doesn't exist");
}

//------------------------------------------------------------------------------
void GLESDrawer::releaseFont(const wstring& identifier)
{
	map<wstring, FontInternal>::iterator it;

	it = fonts.find(identifier);

	if(it != fonts.end())
	{
		//Check the fonts entry actually has some glyphs in it.
		assert(it->second.metrics.size() > 0);

		//First release the font texture.
		//(we do this by grabbing the image id from the first glyph; because all
		// the glyphs are rendered on the same texture, this will have the
		// effect of removing them all from the GPU and our images map)
		releaseImage(it->second.metrics.begin()->second.image);

		//Now erase the entry in fonts.
		fonts.erase(it);
	}
}

//------------------------------------------------------------------------------
FontMetrics::iterator GLESDrawer::loadMissingGlyph(wchar_t missingCharacter,
														  FontMetrics& metrics,
														  const wstring& fontId)
{
	AtlasImage *atlasImage;
	unsigned char *imageData;
	int imageWidth, imageHeight;
	bool needNewAtlas;
	FontMetrics::iterator retval = metrics.end();
	FontAtlasPtr tempAt = fonts[fontId].atlas;

	fonts[fontId].atlas->getMissingGlyphData(missingCharacter,
											 metrics,
											 &atlasImage,
											 &imageData,
											 imageWidth,
											 imageHeight,
											 needNewAtlas);

	if(atlasImage)
	{
		ESImage tempIm;

		if(needNewAtlas)
		{
			glGenTextures(1, &(fonts[fontId].currentTexture));
			fonts[fontId].textures.push_back(fonts[fontId].currentTexture);

			glBindTexture(GL_TEXTURE_2D, fonts[fontId].currentTexture);

			glTexImage2D(GL_TEXTURE_2D,
						 0,
						 GL_RGBA,
						 imageWidth,
						 imageHeight,
						 0,
						 GL_RGBA,
						 GL_UNSIGNED_BYTE,
						 imageData);
			if(texInterp)
			{
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			}
			else
			{
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			}
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

			glBindTexture(GL_TEXTURE_2D, 0);

			tempIm.texture = fonts[fontId].currentTexture;
			tempIm.size = atlasImage->size * (float)fonts[fontId].atlas->getAtlasDimensions();
			tempIm.posWithin = atlasImage->position;
			tempIm.sizeWithin = atlasImage->size;

			//Setup the associated vertex buffer object.
			{
				//						vertex							texture
				GLfloat bufferData[] = {0.0f, 0.0f,						tempIm.posWithin.x, tempIm.posWithin.y,
										0.0f, tempIm.size.y,			tempIm.posWithin.x, tempIm.posWithin.y + tempIm.sizeWithin.y,
										tempIm.size.x, 0.0f,			tempIm.posWithin.x + tempIm.sizeWithin.x, tempIm.posWithin.y,
										tempIm.size.x, tempIm.size.y,	tempIm.posWithin.x + tempIm.sizeWithin.x, tempIm.posWithin.y + tempIm.sizeWithin.y};

				glGenVertexArrays(1, &tempIm.vertexArray);
				glBindVertexArray(tempIm.vertexArray);

				glGenBuffers(1, &tempIm.vertexBuffer);
				glBindBuffer(GL_ARRAY_BUFFER, tempIm.vertexBuffer);
				glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*16, bufferData, GL_DYNAMIC_DRAW);

				glEnableVertexAttribArray(vertexPositionLoc);
				glEnableVertexAttribArray(baseTexCoordsLoc);
				glEnableVertexAttribArray(secondTexCoordsLoc);
				glVertexAttribPointer(vertexPositionLoc,
										2,
										GL_FLOAT,
										GL_FALSE,
										sizeof(GLfloat)*4,
										0);
				glVertexAttribPointer(baseTexCoordsLoc,
										2,
										GL_FLOAT,
										GL_FALSE,
										sizeof(GLfloat)*4,
										(void *)(sizeof(GLfloat)*2));
				glVertexAttribPointer(secondTexCoordsLoc,
										2,
										GL_FLOAT,
										GL_FALSE,
										sizeof(GLfloat)*4,
										(void *)(sizeof(GLfloat)*2));

				glBindVertexArray(0);
			}

			images.insert(make_pair(atlasImage->name, tempIm));

			delete [] imageData;
		}
		else
		{
			glBindTexture(GL_TEXTURE_2D, fonts[fontId].currentTexture);
			glTexSubImage2D(GL_TEXTURE_2D,
							0,
							(int)(atlasImage->position.x * fonts[fontId].atlas->getAtlasDimensions()),
							(int)(atlasImage->position.y * fonts[fontId].atlas->getAtlasDimensions()),
							imageWidth,
							imageHeight,
							GL_RGBA,
							GL_UNSIGNED_BYTE,
							imageData);
			if(texInterp)
			{
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			}
			else
			{
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			}
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

			glBindTexture(GL_TEXTURE_2D, 0);

			tempIm.texture = fonts[fontId].currentTexture;
			tempIm.size.x = (float)imageWidth;
			tempIm.size.y = (float)imageHeight;
			tempIm.posWithin = atlasImage->position;
			tempIm.sizeWithin = atlasImage->size;

			//Setup the associated vertex buffer object.
			{
				//						vertex							texture
				GLfloat bufferData[] = {0.0f, 0.0f,						tempIm.posWithin.x, tempIm.posWithin.y,
										0.0f, tempIm.size.y,			tempIm.posWithin.x, tempIm.posWithin.y + tempIm.sizeWithin.y,
										tempIm.size.x, 0.0f,			tempIm.posWithin.x + tempIm.sizeWithin.x, tempIm.posWithin.y,
										tempIm.size.x, tempIm.size.y,	tempIm.posWithin.x + tempIm.sizeWithin.x, tempIm.posWithin.y + tempIm.sizeWithin.y};

				glGenVertexArrays(1, &tempIm.vertexArray);
				glBindVertexArray(tempIm.vertexArray);

				glGenBuffers(1, &tempIm.vertexBuffer);
				glBindBuffer(GL_ARRAY_BUFFER, tempIm.vertexBuffer);
				glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*16, bufferData, GL_DYNAMIC_DRAW);

				glEnableVertexAttribArray(vertexPositionLoc);
				glEnableVertexAttribArray(baseTexCoordsLoc);
				glEnableVertexAttribArray(secondTexCoordsLoc);
				glVertexAttribPointer(vertexPositionLoc,
										2,
										GL_FLOAT,
										GL_FALSE,
										sizeof(GLfloat)*4,
										0);
				glVertexAttribPointer(baseTexCoordsLoc,
										2,
										GL_FLOAT,
										GL_FALSE,
										sizeof(GLfloat)*4,
										(void *)(sizeof(GLfloat)*2));
				glVertexAttribPointer(secondTexCoordsLoc,
										2,
										GL_FLOAT,
										GL_FALSE,
										sizeof(GLfloat)*4,
										(void *)(sizeof(GLfloat)*2));

				glBindVertexArray(0);
			}

			images.insert(make_pair(atlasImage->name, tempIm));

			delete [] imageData;
		}
		retval = metrics.find(missingCharacter);
	}
	else
		DebugStatement(L"GLESDrawer: Could not load missing glyph: " << missingCharacter << L" for font: " << fontId);

	return retval;
}

//------------------------------------------------------------------------------
void GLESDrawer::beginDrawing()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	matrix.identity();
	glUniformMatrix4fv(matrixLoc, 1, GL_FALSE, matrix.getMatrix());
}

//------------------------------------------------------------------------------
void GLESDrawer::endDrawing()
{
	secondImage = L"";
	lastTexture = 0;

	matrix.clear();
}

//------------------------------------------------------------------------------
void GLESDrawer::getScreenPixels(vector<uint8_t>& pixelBuffer)
{
	const unsigned int bufferSize = (unsigned int)windowSize.x*(unsigned int)windowSize.y*3;

	//Make sure the buffer has enough space for what we're doing.
	if(pixelBuffer.size() < bufferSize)
		pixelBuffer.reserve(bufferSize);

	//So we don't have to call this after Drawer::endDrawing().
	//SDL_GL_SwapBuffers();
	glFinish(); //Will this do the same thing as SDL_GL_SwapBuffers()?

	//Clear the buffer.
	pixelBuffer.assign(bufferSize, 0);

	//Get pixels from opengl frame buffer.
	glReadPixels(0,					//X start pos.
				 0,					//Y start pos.
				 (int)windowSize.x,		//Width.
				 (int)windowSize.y,		//Height.
				 GL_RGB,			//What data we're reading.
				 GL_UNSIGNED_BYTE,	//Format to write to.
#ifndef OSX //I hate apple. Their STL implementation is pathetic.
				 (void *)pixelBuffer.data());	//Buffer to write to.
#else
				 (void *)(&pixelBuffer[0]));
#endif
}

//------------------------------------------------------------------------------
void GLESDrawer::useShader(const wstring& shader)
{
	if(shader == L"")
		currentShader = L"-default-shader-";
	else
		currentShader = shader;

	assert(shaders.find(currentShader) != shaders.end());

	glUseProgram(shaders[currentShader].shader);
}

//------------------------------------------------------------------------------
void GLESDrawer::setShaderParam(const std::wstring& param, float val)
{
	glUniform1f(shaders[currentShader].uniformLocs[param], val);
}

//------------------------------------------------------------------------------
void GLESDrawer::useSecondImage(const wstring& image)
{
	secondImage = image;
}

//------------------------------------------------------------------------------
void GLESDrawer::setBlendMode(int val)
{
	switch(val)
	{
		case AlphaBlend:
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			glBlendEquation(GL_FUNC_ADD);
			break;
		case Additive:
			glBlendFunc(GL_SRC_ALPHA, GL_ONE);
			glBlendEquation(GL_FUNC_ADD);
			break;
		default:
			{
				map<int, CustomBlendMode *>::iterator it = customBlendModes.find(val);

				if(it != customBlendModes.end())
					it->second->setBlendMode();
				else
				{
					DebugStatement(L"GLESDrawer: Could not find custom blend mode: " << val);
				}
			}
			break;
	}
}

//------------------------------------------------------------------------------
void GLESDrawer::registerCustomBlendMode(int identifier,
												CustomBlendMode *mode)
{
	customBlendModes.insert(make_pair(identifier, mode));
}

//------------------------------------------------------------------------------
void GLESDrawer::drawImage(const wstring& name,
								  const TwoFloats& position,
								  const TwoFloats& size,
								  const ThreeFloats& colour,
								  float alpha)
{
	ESImage *image1;
	ESImage *image2 = &nullImage;

#ifdef DEBUG
	map<wstring, ESImage>::iterator it;

	it = images.find(name);

	assert(it != images.end());
#endif

	image1 = &(images[name]);

	//Store the current model matrix.
	matrix.push();

	//Translate to the position.
	matrix.translate(position.x, position.y, 0.0f);

	//Scale the image accordingly.
	matrix.scale(size.x, size.y, 1.0f);
	glUniformMatrix4fv(matrixLoc, 1, GL_FALSE, matrix.getMatrix());

	if(lastTexture != image1->texture)
	{
		glActiveTextureARB(GL_TEXTURE0_ARB);
		glBindTexture(GL_TEXTURE_2D, image1->texture);
		glUniform1f(textureExistsLoc, 1.0f);
		lastTexture = image1->texture;
	}
	if(lastVBO != image1->vertexArray)
	{
		glBindVertexArray(image1->vertexArray);
		lastVBO = image1->vertexArray;
	}

	if(!secondImage.empty())
	{
#ifdef DEBUG
		map<wstring, ESImage>::iterator it2;

		it2 = images.find(secondImage);

		assert(it2 != images.end());
#endif
		image2 = &(images[secondImage]);

		glActiveTextureARB(GL_TEXTURE1_ARB);
		glEnable(GL_TEXTURE_2D);

		if(lastSecondTexture != image2->texture)
		{
			glBindTexture(GL_TEXTURE_2D, image2->texture);
			lastSecondTexture = image2->texture;
		}
	}
	
	glUniform4f(colourLoc, colour.x, colour.y, colour.z, alpha);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	if(!secondImage.empty())
	{
		glActiveTextureARB(GL_TEXTURE1_ARB);
		glDisable(GL_TEXTURE_2D);
	}

	matrix.pop();
	glUniformMatrix4fv(matrixLoc, 1, GL_FALSE, matrix.getMatrix());
}

//------------------------------------------------------------------------------
void GLESDrawer::drawRotImage(const wstring& name,
							  const TwoFloats& position,
							  float angle,
							  const TwoFloats& pivot,
							  const TwoFloats& size,
							  const ThreeFloats& colour,
							  float alpha)
{
	ESImage *image1;
	ESImage *image2 = &nullImage;

#ifdef DEBUG
	map<wstring, ESImage>::iterator it;

	it = images.find(name);

	assert(it != images.end());
#endif

	image1 = &(images[name]);

	//Store the current model matrix.
	matrix.push();

	//Translate to the position.
	matrix.translate(position.x, position.y, 0.0f);

	//Scale the image accordingly.
	matrix.scale(size.x, size.y, 1.0f);

	//Rotate around the pivot point.
	matrix.translate(pivot.x, pivot.y, 0.0f);
	matrix.rotate(angle, 0.0f, 0.0f, 1.0f);
	matrix.translate(-pivot.x, -pivot.y, 0.0f);

	glUniformMatrix4fv(matrixLoc, 1, GL_FALSE, matrix.getMatrix());

	if(lastTexture != image1->texture)
	{
		glActiveTextureARB(GL_TEXTURE0_ARB);
		glBindTexture(GL_TEXTURE_2D, image1->texture);
		glUniform1f(textureExistsLoc, 1.0f);
		lastTexture = image1->texture;
	}
	if(lastVBO != image1->vertexArray)
	{
		glBindVertexArray(image1->vertexArray);
		lastVBO = image1->vertexArray;
	}

	if(!secondImage.empty())
	{
#ifdef DEBUG
		map<wstring, ESImage>::iterator it2;

		it2 = images.find(secondImage);

		assert(it2 != images.end());
#endif
		image2 = &(images[secondImage]);

		glActiveTextureARB(GL_TEXTURE1_ARB);
		glEnable(GL_TEXTURE_2D);

		if(lastSecondTexture != image2->texture)
		{
			glBindTexture(GL_TEXTURE_2D, image2->texture);
			lastSecondTexture = image2->texture;
		}
	}

	glUniform4f(colourLoc, colour.x, colour.y, colour.z, alpha);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	if(!secondImage.empty())
	{
		glActiveTextureARB(GL_TEXTURE1_ARB);
		glDisable(GL_TEXTURE_2D);
	}

	matrix.pop();
	glUniformMatrix4fv(matrixLoc, 1, GL_FALSE, matrix.getMatrix());
}

//------------------------------------------------------------------------------
void GLESDrawer::drawSubImage(const wstring& name,
									 const TwoFloats& position,
									 const TwoFloats& subPos,
									 const TwoFloats& subSize,
									 const TwoFloats& size,
									 const ThreeFloats& colour,
									 float alpha)
{
	float w, h;
	float xTex, yTex;
	float xTex2, yTex2;
	ESImage *image1;
	ESImage *image2 = &nullImage;

#ifdef DEBUG
	map<wstring, ESImage>::iterator it;

	it = images.find(name);

	assert(it != images.end());
#endif

	image1 = &(images[name]);

	w = image1->size.x * subSize.x;
	h = image1->size.y * subSize.y;

	xTex = image1->posWithin.x + (image1->sizeWithin.x * subPos.x);
	yTex = image1->posWithin.y + (image1->sizeWithin.y * subPos.y);

	//Store the current model matrix.
	glMatrixMode(GL_MODELVIEW);
	//glPushMatrix();
	matrix.push();

	//Translate to the position.
	//glTranslatef(position.x, position.y, 0.0f);
	matrix.translate(position.x, position.y, 0.0f);

	//Scale the image accordingly.
	//glScalef(size.x, size.y, 1.0f);
	matrix.scale(size.x, size.y, 1.0f);

	glLoadMatrixf(matrix.getMatrix());

	if(lastTexture != image1->texture)
	{
		glActiveTextureARB(GL_TEXTURE0_ARB);
		glBindTexture(GL_TEXTURE_2D, image1->texture);
		lastTexture = image1->texture;
	}

	if(!secondImage.empty())
	{
#ifdef DEBUG
		map<wstring, ESImage>::iterator it2;

		it2 = images.find(secondImage);

		assert(it2 != images.end());
#endif
		image2 = &(images[secondImage]);

		glActiveTextureARB(GL_TEXTURE1_ARB);
		glEnable(GL_TEXTURE_2D);

		if(lastSecondTexture != image2->texture)
		{
			glBindTexture(GL_TEXTURE_2D, image2->texture);
			lastSecondTexture = image2->texture;
		}
	}

	xTex2 = image2->posWithin.x + (image2->sizeWithin.x * subPos.x);
	yTex2 = image2->posWithin.y + (image2->sizeWithin.y * subPos.y);

	glColor4f(colour.x, colour.y, colour.z, alpha);
	glBegin(GL_QUADS);
	{
		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 xTex,
							 yTex);
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB,
							 xTex2,
							 yTex2);
		glVertex2f(0.0f, 0.0f);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 xTex + (image1->sizeWithin.x * subSize.x),
							 yTex);
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB,
							 xTex2 + (image2->sizeWithin.x * subSize.x),
							 yTex2);
		glVertex2f(w, 0.0f);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 xTex + (image1->sizeWithin.x * subSize.x),
							 yTex + (image1->sizeWithin.y * subSize.y));
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB,
							 xTex2 + (image2->sizeWithin.x * subSize.x),
							 yTex2 + (image2->sizeWithin.y * subSize.y));
		glVertex2f(w, h);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 xTex,
							 yTex + (image1->sizeWithin.y * subSize.y));
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB,
							 xTex2,
							 yTex2 + (image2->sizeWithin.y * subSize.y));
		glVertex2f(0.0f, h);
	}
	glEnd();

	if(!secondImage.empty())
	{
		glActiveTextureARB(GL_TEXTURE1_ARB);
		glDisable(GL_TEXTURE_2D);
	}
	//glPopMatrix();
	matrix.pop();
	glLoadMatrixf(matrix.getMatrix());
}

//------------------------------------------------------------------------------
void GLESDrawer::drawTiledImage(const wstring& name,
									   const TwoFloats& position,
									   const TwoFloats& size,
									   const TwoFloats& tileNum,
									   const ThreeFloats& colour,
									   float alpha)
{
	ESImage *image1;
	ESImage *image2 = &nullImage;

#ifdef DEBUG
	map<wstring, ESImage>::iterator it;

	it = images.find(name);

	assert(it != images.end());
#endif

	image1 = &(images[name]);

	if(lastTexture != image1->texture)
	{
		glActiveTextureARB(GL_TEXTURE0_ARB);
		glBindTexture(GL_TEXTURE_2D, image1->texture);
		lastTexture = image1->texture;

		//Make sure the texture will actually tile.
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	}

	if(!secondImage.empty())
	{
#ifdef DEBUG
		map<wstring, ESImage>::iterator it2;

		it2 = images.find(secondImage);

		assert(it2 != images.end());
#endif
		image2 = &(images[secondImage]);

		glActiveTextureARB(GL_TEXTURE1_ARB);
		glEnable(GL_TEXTURE_2D);

		if(lastSecondTexture != image2->texture)
		{
			glBindTexture(GL_TEXTURE_2D, image2->texture);
			lastSecondTexture = image2->texture;

			//Make sure the texture will actually tile.
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		}
	}

	glColor4f(colour.x, colour.y, colour.z, alpha);
	glBegin(GL_QUADS);
	{
		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 0.0f);
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0.0f, 0.0f);
		glVertex2f(position.x, position.y);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, tileNum.x, 0.0f);
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, tileNum.x, 0.0f);
		glVertex2f(position.x+size.x, position.y);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, tileNum.x, tileNum.y);
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, tileNum.x, tileNum.y);
		glVertex2f(position.x+size.x, position.y+size.y);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, tileNum.y);
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0.0f, tileNum.y);
		glVertex2f(position.x, position.y+size.y);
	}
	glEnd();

	if(!secondImage.empty())
	{
		glActiveTextureARB(GL_TEXTURE1_ARB);
		glDisable(GL_TEXTURE_2D);
	}
}

//------------------------------------------------------------------------------
void GLESDrawer::drawText(const wstring& text,
								 const wstring& font,
								 const TwoFloats& position,
								 float boxWidth,
								 TextLayout::Justification justification,
								 float size,
								 const ThreeFloats& colour,
								 float alpha)
{
	TwoFloats pos;
	unsigned int i;
	TwoFloats scale(size, size);
	map<wstring, FontInternal>::iterator it;

	it = fonts.find(font);
	if(it != fonts.end())
	{
		const vector<TextLayout::GlyphPos>& glyphs = textLayout.getLayoutForText(text,
																				 it->second.metrics,
																				 boxWidth,
																				 size,
																				 justification,
																				 font,
																				 this);

		for(i=0;i<glyphs.size();++i)
		{
			pos = glyphs[i].position + position;

			//Does this make things unnecessarily slow?
			//Should we have dedicated glyph drawing code in this method?
			drawImage(glyphs[i].image, pos, scale, colour, alpha);
		}
	}
}

//------------------------------------------------------------------------------
void GLESDrawer::drawText(const wstring& text,
								 const wstring& font,
								 TextLayout& layout,
								 const TwoFloats& position,
								 float boxWidth,
								 TextLayout::Justification justification,
								 float size,
								 const ThreeFloats& colour,
								 float alpha)
{
	TwoFloats pos;
	unsigned int i;
	TwoFloats scale(size, size);
	map<wstring, FontInternal>::iterator it;

	it = fonts.find(font);
	if(it != fonts.end())
	{
		//If layout is empty, fill it up.
		if(layout.getUninitialised())
		{
			const vector<TextLayout::GlyphPos>& glyphs = layout.getLayoutForText(text,
																				 it->second.metrics,
																				 boxWidth,
																				 size,
																				 justification,
																				 font,
																				 this);

			for(i=0;i<glyphs.size();++i)
			{
				pos = glyphs[i].position + position;

				//Does this make things unnecessarily slow?
				//Should we have dedicated glyph drawing code in this method?
 				drawImage(glyphs[i].image, pos, scale, colour, alpha);
			}
		}
		else //Otherwise, draw it as-is.
		{
			const vector<TextLayout::GlyphPos>& glyphs = layout.getExistingLayout();

			for(i=0;i<glyphs.size();++i)
			{
				pos = glyphs[i].position + position;
				drawImage(glyphs[i].image, pos, scale, colour, alpha);
			}
		}
	}
}

//------------------------------------------------------------------------------
void GLESDrawer::drawLine(const wstring& image,
								 const TwoFloats& start,
								 const TwoFloats& end,
								 const ThreeFloats& colour,
								 float alpha)
{
	ESImage *image1;
	map<wstring, ESImage>::iterator it;
	const float dist = sqrtf(((end.x-start.x)*(end.x-start.x)) + ((end.y-start.y)*(end.y-start.y)));
	const float angle = start.getAngle(end);

	it = images.find(image);
	if(it == images.end())
		it = images.find(L"-default-line-");
	image1 = &(it->second);

	if(lastTexture != image1->texture)
	{
		glBindTexture(GL_TEXTURE_2D, image1->texture);
		lastTexture = image1->texture;
	}
	if(lastVBO != image1->vertexArray)
	{
		glBindVertexArray(image1->vertexArray);
		lastVBO = image1->vertexArray;
	}

	//Store the current model matrix.
	matrix.push();

	//Move the line to the start point, rotate it, and stretch it.
	matrix.translate(start.x, start.y, 0.0f);

	//I don't understand why we need to flip the x-axis!!?!
	matrix.scale(-1.0f, 1.0f, 0.0f);

	matrix.translate(4.0f, 0.0f, 0.0f);
	matrix.rotate(angle, 0.0f, 0.0f, 1.0f);
	matrix.translate(-4.0f, 0.0f, 0.0f);

	matrix.scale(1.0f, (dist*0.125f), 0.0f);

	//Push the matrix onto the GPU.
	glUniformMatrix4fv(matrixLoc, 1, GL_FALSE, matrix.getMatrix());
	
	//Draw the line.
	glUniform4f(colourLoc, colour.x, colour.y, colour.z, alpha);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	//Pop the matrix.
	matrix.pop();
	glUniformMatrix4fv(matrixLoc, 1, GL_FALSE, matrix.getMatrix());
}

//------------------------------------------------------------------------------
void GLESDrawer::drawConnectedLine(const wstring& image,
										  const vector<TwoFloats>& points,
										  const vector<float>& pointsScale,
										  const vector<ThreeFloats>& pointsColour,
										  const vector<float>& pointsAlpha)
{
	unsigned int i;
	map<wstring, ESImage>::iterator it;
	//The two intersecting lines where the line we're drawing meets its
	//neighbours.
	TwoFloats intersections[2];

	it = images.find(image);
	if(it == images.end())
		it = images.find(L"-default-line-");

	if(lastTexture != it->second.texture)
	{
		glBindTexture(GL_TEXTURE_2D, it->second.texture);
		lastTexture = it->second.texture;
	}

	intersections[1] = (points[1] - points[0]).perpendicular().normal();
	for(i=1;i<(points.size()-2);++i)
	{
		//The 4 trail points we need to calculate the correct corners for the
		//line trailPos[i] -> trailPos[i+1].
		TwoFloats a0(points[i-1]);
		TwoFloats a1(points[i]);
		TwoFloats a2(points[i+1]);
		TwoFloats a3(points[i+2]);

		//The 4 corners of the quad we use to draw the line
		//trailPos[i] -> trailPos[i+1]. The arrangement is:
		//3--2
		//|..|
		//|..|
		//0--1
		TwoFloats corners[4];

		//Start with the 'bottom-left' corner.
		intersections[0] = intersections[1];
		corners[0] = a1 - (intersections[0] * pointsScale[i]);

		//Now the 'top-left' corner.
		corners[3] = a1 + (intersections[0] * pointsScale[i]);

		//Now the 'bottom-right' corner.
		intersections[1] = (a3-a2).perpendicular().normal();
		corners[1] = a2 - (intersections[1] * pointsScale[i+1]);

		//Now the 'top-left' corner.
		corners[2] = a2 + (intersections[1] * pointsScale[i+1]);

		//Now draw the line.
		if(fabsf(intersections[0].getAngle(intersections[1])) < (2.0f * 1.570796f))
		{
			glBegin(GL_QUADS);
			{
				glColor4f(pointsColour[i].x,
						  pointsColour[i].y,
						  pointsColour[i].z, 
						  pointsAlpha[i]);
				glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 0.0f);
				glVertex2f(corners[0].x, corners[0].y);

				glColor4f(pointsColour[i+1].x,
						  pointsColour[i+1].y,
						  pointsColour[i+1].z, 
						  pointsAlpha[i+1]);
				glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 1.0f);
				glVertex2f(corners[1].x, corners[1].y);

				glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 1.0f);
				glVertex2f(corners[2].x, corners[2].y);
				
				glColor4f(pointsColour[i].x,
						  pointsColour[i].y,
						  pointsColour[i].z, 
						  pointsAlpha[i]);
				glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 0.0f);
				glVertex2f(corners[3].x, corners[3].y);
			}
			glEnd();
		}
	}
}

//------------------------------------------------------------------------------
void GLESDrawer::fillRect(const TwoFloats& position,
								 const TwoFloats& size,
								 const ThreeFloats& colour,
								 float alpha)
{
	//Unbind current texture first.
	glActiveTextureARB(GL_TEXTURE0_ARB);
	glBindTexture(GL_TEXTURE_2D, 0);
	lastTexture = 0;

	//Store the current model matrix.
	matrix.push();

	//Translate to the position.
	matrix.translate(position.x, position.y, 0.0f);

	//Scale the image accordingly.
	matrix.scale(size.x, size.y, 1.0f);
	glUniformMatrix4fv(matrixLoc, 1, GL_FALSE, matrix.getMatrix());

	//Make sure we don't try and do textured rendering.
	glUniform1f(textureExistsLoc, 0.0f);
	glUniform4f(colourLoc, colour.x, colour.y, colour.z, alpha);

	//Draw the rect.
	glBindVertexArray(rectArray);
	lastVBO = rectArray;
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	//Reset the modelview matrix.
	matrix.pop();
	glUniformMatrix4fv(matrixLoc, 1, GL_FALSE, matrix.getMatrix());
}

//------------------------------------------------------------------------------
void GLESDrawer::fillGradient(const TwoFloats& position,
									 const TwoFloats& size,
									 const ThreeFloats& colourTop,
									 const ThreeFloats& colourBottom,
									 float alphaTop,
									 float alphaBottom)
{
	//Unbind current texture first.
	glActiveTextureARB(GL_TEXTURE0_ARB);
	glBindTexture(GL_TEXTURE_2D, 0);
	lastTexture = 0;

	glColor4f(colourTop.x, colourTop.y, colourTop.z, alphaTop);
	glBegin(GL_QUADS);
	{
		glVertex2f(position.x, position.y);
		glVertex2f(position.x+size.x, position.y);
		glColor4f(colourBottom.x, colourBottom.y, colourBottom.z, alphaBottom);
		glVertex2f(position.x+size.x, position.y+size.y);
		glVertex2f(position.x, position.y+size.y);
	}
	glEnd();
}

//------------------------------------------------------------------------------
void GLESDrawer::translate(const TwoFloats& offset)
{
	matrix.translate(offset.x, offset.y, 0.0f);
	glUniformMatrix4fv(matrixLoc, 1, GL_FALSE, matrix.getMatrix());
}

//------------------------------------------------------------------------------
void GLESDrawer::setViewport(const TwoFloats& position,
									const TwoFloats& size)
{
	int x2, y2, w2, h2;

	//Convert input coords to pixels.
	x2 = (int)((position.x/drawingSize.x) * viewportSize.x);
	y2 = (int)((position.y/drawingSize.y) * viewportSize.y);
	w2 = (int)((size.x/drawingSize.x) * viewportSize.x);
	h2 = (int)((size.y/drawingSize.y) * viewportSize.y);

	//Set the viewport.
	glViewport((int)viewportPos.x+x2,
			   (int)viewportPos.y+((int)viewportSize.y-(y2+h2)),
			   w2,
			   h2);

	//Now setup the projection matrix.
	//(so our co-ordinates for drawing are 0-size.x (x) and size.y-0 (y))
	projectionMatrix.generateOrthoProjection(0.0f, size.x, size.y, 0.0f);
	glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, projectionMatrix.getMatrix());
}

//------------------------------------------------------------------------------
void GLESDrawer::resetViewport()
{
	glViewport((int)viewportPos.x,
			   (int)viewportPos.y,
			   (int)viewportSize.x,
			   (int)viewportSize.y);

	//Reset the projection matrix.
	projectionMatrix.generateOrthoProjection(0.0f, drawingSize.x, drawingSize.y, 0.0f);
	glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, projectionMatrix.getMatrix());
}

//------------------------------------------------------------------------------
void GLESDrawer::startFrameBufferDrawing()
{
	const int dW = (int)drawingSize.x;
	const int dH = (int)drawingSize.y;

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBufferObject);
	//glDrawBuffer(GL_COLOR_ATTACHMENT0); //Not present in OpenGL ES. Necessary?

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glViewport(0, 0, dW, dH);
	gluOrtho2D(0.0f, dW, 0.0f, dH);
	glMatrixMode(GL_MODELVIEW);
}

//------------------------------------------------------------------------------
void GLESDrawer::endFrameBufferDrawing()
{
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

	resetViewport();
}

//------------------------------------------------------------------------------
bool GLESDrawer::canSupport(GraphicsCapabilities feature) const
{
	bool retval = false;

	switch(feature)
	{
		case Shaders:
			if(glUniform1i || GL_ARB_shader_objects)
				retval = true;
			else
				retval = false;
			DebugStatement(L"canSupport(Shaders) => " << (bool)retval);
			break;
		case FrameBuffer:
			if(GL_EXT_framebuffer_object)
				retval = true;
			DebugStatement(L"canSupport(FrameBuffer) => " << (bool)retval);
			break;
		case VertexArrayObjects:
			if(GL_ARB_vertex_array_object)
				retval = true;
			DebugStatement(L"canSupport(VertexArrayObjects) => " << (bool)retval);
			break;
	}

	return retval;
}

//------------------------------------------------------------------------------
bool GLESDrawer::isSupported()
{
#ifndef OSX
	if(!glewAlready)
	{
		GLenum err = glewInit();
		if(err != GLEW_OK)
		{
			wstring tempstr;

			UTF8File::charToWstring(string((const char *)glewGetErrorString(err)), tempstr);
			DebugStatement(L"GLEW error: " << tempstr.c_str());
		}
		else
		{
			wstring tempstr;

			UTF8File::charToWstring(string((const char *)glewGetString(GLEW_VERSION)), tempstr);
			DebugStatement(L"Using GLEW version: " << tempstr);
			glewAlready = true;
		}
	}

	return (GLEW_VERSION_2_0 && glewIsSupported("GL_ARB_vertex_array_object"));
#else
	float version;
	string tempstr2;
	stringstream tempstr;
	const GLubyte *versionString = glGetString(GL_VERSION);
	const GLubyte *extensionString = glGetString(GL_EXTENSIONS);

	tempstr << versionString;
	tempstr >> version;

	tempstr2 = (const char *)extensionString;

	return ((version >= 2.0f) && (tempstr2.find("GL_ARB_vertex_array_object")));
#endif
}

//------------------------------------------------------------------------------
const Drawer::Image * const GLESDrawer::getInternalImageData(const wstring& imageId) const
{
	map<wstring, ESImage>::const_iterator it;

	it = images.find(imageId);
	if(it != images.end())
		return &(it->second);
	else
		return 0;
}

//------------------------------------------------------------------------------
void GLESDrawer::printInfoLog(GLuint shader)
{
#ifdef DEBUG_STATEMENTS
	string infoLog;
	wstring wInfoLog;
	char *infoLogChar;
	GLint infologLength = 0;
    GLsizei charsWritten  = 0;
	
    glGetProgramiv(shader,
				   GL_OBJECT_INFO_LOG_LENGTH_ARB,
				   &infologLength);

	DebugStatement(L"infologLength = " << infologLength);
	
    if(infologLength > 0)
    {
		infoLog.resize(infologLength);
		infoLogChar = const_cast<char *>(infoLog.c_str());

		glGetProgramInfoLog(shader, infologLength, &charsWritten, infoLogChar);

		UTF8File::charToWstring(infoLog, wInfoLog);
		DebugStatement(wInfoLog);
    }
#endif
}

//------------------------------------------------------------------------------
bool GLESDrawer::glewAlready = false;

//------------------------------------------------------------------------------
static const unsigned char temp1[] = {137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,8,0,0,0,8,8,6,0,0,0,196,15,190,139,0,0,0,7,116,73,77,
  69,7,217,11,5,18,24,55,156,239,108,44,0,0,0,9,112,72,89,115,0,0,13,172,0,0,13,172,1,239,6,198,71,0,0,0,4,103,65,77,
  65,0,0,177,143,11,252,97,5,0,0,0,31,73,68,65,84,120,218,99,248,255,255,63,19,16,43,2,241,129,255,8,112,0,42,198,196,196,64,0,
  12,15,5,0,208,222,27,187,199,235,156,253,0,0,0,0,73,69,78,68,174,66,96,130,0,0};
const char* GLESDrawer::line_png = (const char*)temp1;

//------------------------------------------------------------------------------
const wchar_t GLESDrawer::defaultVertexShader[] = {L"\
	#version 120\n\
	\
	attribute vec4 avlVertexPosition;\
	\
	uniform mat4x4 avlProjectionMatrix;\
	uniform mat4x4 avlModelviewMatrix;\
	\
	attribute vec2 avlBaseTexCoords;\
	attribute vec2 avlSecondTexCoords;\
	\
	varying vec2 avlFragBaseTexCoords;\
	varying vec2 avlFragSecondTexCoords;\
	\
	void main()\
	{\
		avlFragBaseTexCoords = avlBaseTexCoords;\
		avlFragSecondTexCoords = avlSecondTexCoords;\
		gl_Position = avlModelviewMatrix * avlVertexPosition * avlProjectionMatrix;\
	}\
"};
const wchar_t GLESDrawer::defaultFragmentShader[] = {L"\
	#version 120\n\
	\
	uniform vec4 avlColour;\
	\
	uniform sampler2D avlBaseTexture;\
	uniform sampler2D avlSecondTexture;\
	\
	uniform float avlTextureExists;\
	\
	varying vec2 avlFragBaseTexCoords;\
	varying vec2 avlFragSecondTexCoords;\
	\
	void main()\
	{\
		vec4 b = texture2D(avlBaseTexture, avlFragBaseTexCoords);\
		vec4 s = texture2D(avlSecondTexture, avlFragSecondTexCoords);\
		\
		gl_FragColor = mix(avlColour, avlColour * b, avlTextureExists);\
	}\
"};

