//	GLImmediateDrawer.cpp - OpenGL immediate 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 "GLImmediateDrawer.h"
#include "FreetypeFontAtlas.h"
#include "DebugHeaders.h"
#include "ImageLoader.h"
#include "MiscHelper.h"
#include "UTF8File.h"

#include <cassert>

using std::wstringstream;
using std::make_pair;
using std::wstring;
using std::string;
using std::vector;
using std::map;

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

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

//------------------------------------------------------------------------------
GLImmediateDrawer::~GLImmediateDrawer()
{

}

//------------------------------------------------------------------------------
void GLImmediateDrawer::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;
		}
	}

	DebugStatement(L"GLImmediateDrawer: Window dimensions: " << windowSize.x << L"x" << windowSize.y);
	DebugStatement(L"GLImmediateDrawer: Drawing area dimensions: " << drawingSize.x << L"x" << drawingSize.y);

	//Initialise GLEW.
#ifndef OSX
	GLenum err;

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

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

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

	//Print out OpenGL system details.
	DebugStatement(L"GLImmediateDrawer: OpenGL implementation = " << (char *)glGetString(GL_VENDOR) << L" : " << (char *)glGetString(GL_RENDERER) << L" : " << (char *)glGetString(GL_VERSION));

	//Check the maximum texture size on this system.
	{
		GLint tempint;
		glGetIntegerv(GL_MAX_TEXTURE_SIZE, &tempint);
		DebugStatement("GLImmediateDrawer: 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("GLImmediateDrawer: GL_TEXTURE_WIDTH = " << tempint);
	}

	//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"GLImmediateDrawer: KeepRatio; 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;

	//Initialise frame buffer object.
	if(GL_EXT_framebuffer_object)
	{
		GLuint depthBuffer;
		ImmediateImage 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));

		//Check it's okay.
		GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
		if(status != GL_FRAMEBUFFER_COMPLETE_EXT)
		{
			DebugStatement(L"GLImmediateDrawer: Could not create frame buffer.");
		}
		else
			DebugStatement(L"GLImmediateDrawer: Frame buffer initialised.");

		//Unbind stuff.
		glBindTexture(GL_TEXTURE_2D, 0);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	}

	//Now set up everything else.
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	//So our co-ordinates for drawing are 0-drawingAreaWidth (x)
	//and drawingAreaHeight-0 (y).
	gluOrtho2D(0.0f, drawingSize.x, drawingSize.y, 0.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	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_ONE, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);

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

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

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

//------------------------------------------------------------------------------
void GLImmediateDrawer::loadSingleImage(uint8_t *data,
										int width,
										int height,
										const wstring& name)
{
	ImmediateImage tempIm;
	int texWidth, texHeight;

	DebugStatement(L"GLImmediateDrawer: Loading single image: " << name);

	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];

			DebugStatement(L"GLImmediateDrawer: Image is not power-of-two. Making it so.");

			//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;

			texWidth = w;
			texHeight = h;

			DebugStatement(L"GLImmediateDrawer: Updated image dimensions: " << width << "x" << height << "(image) : " << w << "x" << h << L"(texture)");

			//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;

			texWidth = width;
			texHeight = height;
		}

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

		glBindTexture(GL_TEXTURE_2D, tempIm.texture);

		glTexImage2D(GL_TEXTURE_2D,
					 0,
					 GL_RGBA,
					 texWidth,
					 texHeight,
					 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 tempIm to our list of images.
		images.insert(make_pair(name, tempIm));

		DebugStatement(L"GLImmediateDrawer: Single image " << name << L" loaded.");
	}
	else
		DebugStatement(L"GLImmediateDrawer: loadSingleImage() could not load image << " << name << L" No data passed in!");
}

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

	DebugStatement(L"GLImmediateDrawer: Loading texture atlas: " << atlas.getImage(0).atlasName);
	DebugStatement(L"GLImmediateDrawer: Atlas num images: " << atlas.getNumImages());
	DebugStatement(L"GLImmediateDrawer: Atlas dimensions: " << width << L"x" << height);

	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];

			DebugStatement(L"GLImmediateDrawer: Image is not power-of-two. Making it so.");

			//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)w;
			tempTexture.size.y = (float)h;
			tempTexture.sizeWithin.x = (float)width/(float)w;
			tempTexture.sizeWithin.y = (float)height/(float)h;

			DebugStatement(L"GLImmediateDrawer: Updated image dimensions: " << width << "x" << height << "(image) : " << w << "x" << h << L"(texture)");

			//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)
			{
				ImmediateImage 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;

				images.insert(make_pair(atlasIm.name, tempIm));
			}
		}

		DebugStatement(L"GLImmediateDrawer: TextureAtlas image " << atlas.getImage(0).atlasName << L" loaded.");
	}
	else
		DebugStatement(L"GLImmediateDrawer: loadTextureAtlas() could not load image << " << atlas.getImage(0).atlasName << L" No data passed in!");
}

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

	DebugStatement(L"GLImmediateDrawer: Releasing image " << name);

	if(it != images.end())
	{
		texture = it->second.texture;
		glDeleteTextures(1, &texture);
		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, ImmediateImage>::iterator it2 = it;

			++it;

			if(it2->second.texture == texture)
				images.erase(it2);
		}
		DebugStatement(L"GLImmediateDrawer: Image " << name << L" released.");
	}
	else
		DebugStatement(L"GLImmediateDrawer: Could not find image " << name << " to release.");
}

//------------------------------------------------------------------------------
bool GLImmediateDrawer::loadShader(const wstring& vertex,
								   const wstring& fragment,
								   const wstring& name)
{
	bool retval = false;

	DebugStatement(L"GLImmediateDrawer: Loading shader " << 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"GLImmediateDrawer: loadShader(); Could not compile Vertex shader " << name);
		glGetShaderiv(fragmentObject, GL_COMPILE_STATUS, &err);
		if(err == GL_FALSE)
			DebugStatement(L"GLImmediateDrawer: 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);

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

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

			//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);

			tempShader.shader = shaderProgram;

			//Get the texture variable locations.
			tempShader.baseTexLoc = glGetUniformLocation(shaderProgram, "base");

			//Add shaderProgram to our shaders map.
			shaders.insert(make_pair(name, tempShader));

			retval = true;
			DebugStatement(L"GLImmediateDrawer: Shader " << name << L" loaded.");
		}
		else
			DebugStatement(L"GLImmediateDrawer: Could not create shader program for shader " << name);
	}
	else
		DebugStatement(L"GLImmediateDrawer: System does not support shaders. Shader " << name << L" not loaded.");

	return retval;
}

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

	UTF8File::wstringToChar(name, narrowName);

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

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

	DebugStatement(L"GLImmediateDrawer: Releasing shader " << name);

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

		DebugStatement(L"GLImmediateDrawer: Shader " << name << L" released.");
	}
	else
		DebugStatement(L"GLImmediateDrawer: Could not find shader " << name << L" to release.");
}

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

	DebugStatement(L"GLImmediateDrawer: Loading font face " << name << L" to " << identifier);

	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;

			DebugStatement(L"GLImmediateDrawer: Font " << identifier << L" loaded.");
		}
		else
		{
			DebugStatement(L"GLImmediateDrawer: Could not load font file: " << fontPath.getPath().c_str());

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

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

	DebugStatement(L"GLImmediateDrawer: Releasing font " << identifier);

	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);

		DebugStatement(L"GLImmediateDrawer: Font " << identifier << L" released.");
	}
	else
		DebugStatement(L"GLImmediateDrawer: Could not find font " << identifier << L" to release.");
}

//------------------------------------------------------------------------------
FontMetrics::iterator GLImmediateDrawer::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;

	DebugStatement(L"GLImmediateDrawer: Loading missing glyph " << missingCharacter << L" to font " << fontId);

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

	if(atlasImage)
	{
		ImmediateImage tempIm;

		if(needNewAtlas)
		{
			DebugStatement(L"GLImmediateDrawer: Need new atlas to accommodate missing glyph.");

			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;
			images.insert(make_pair(atlasImage->name, tempIm));

			delete [] imageData;

			DebugStatement(L"GLImmediateDrawer: New atlas initialised.");
		}
		else
		{
			DebugStatement(L"GLImmediateDrawer: Inserting glyph into existing atlas.");

			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;
			images.insert(make_pair(atlasImage->name, tempIm));

			delete [] imageData;

			DebugStatement(L"GLImmediateDrawer: Glyph inserted into existing atlas.");
		}
		retval = metrics.find(missingCharacter);
	}
	else
		DebugStatement(L"GLImmediateDrawer: Could not load missing glyph: " << missingCharacter << L" for font: " << fontId);

	return retval;
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::beginDrawing()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::endDrawing()
{
	lastTexture = 0;
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::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 GLImmediateDrawer::useShader(const wstring& shader)
{
	currentShader = shader;

	if(currentShader != L"")
	{
		assert(shaders.find(currentShader) != shaders.end());

		glUseProgram(shaders[currentShader].shader);
		//glUniform1f(shaders[currentShader].parameterLoc, param);
	}
	else
		glUseProgram(0);
}

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

//------------------------------------------------------------------------------
void GLImmediateDrawer::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"GLImmediateDrawer: Could not find custom blend mode: " << val);
			}
			break;
	}
}

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

//------------------------------------------------------------------------------
void GLImmediateDrawer::drawImage(const wstring& name,
								  const TwoFloats& position,
								  const TwoFloats& size,
								  const ThreeFloats& colour,
								  float alpha)
{
	float w, h;
	ImmediateImage *image1;

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

	it = images.find(name);

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

	image1 = &(images[name]);

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

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

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

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

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

	if(currentShader != L"")
		glUniform1i(shaders[currentShader].baseTexLoc, 0);
	
	glColor4f(colour.x*alpha, colour.y*alpha, colour.z*alpha, alpha);
	glBegin(GL_QUADS);
	{
		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 image1->posWithin.x,
							 image1->posWithin.y);
		glVertex2f(0.0f, 0.0f);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 image1->posWithin.x + image1->sizeWithin.x,
							 image1->posWithin.y);
		glVertex2f(w, 0.0f);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 image1->posWithin.x + image1->sizeWithin.x,
							 image1->posWithin.y + image1->sizeWithin.y);
		glVertex2f(w, h);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 image1->posWithin.x,
							 image1->posWithin.y + image1->sizeWithin.y);
		glVertex2f(0.0f, h);
	}
	glEnd();

	glPopMatrix();
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::drawRotImage(const wstring& name,
									 const TwoFloats& position,
									 float angle,
									 const TwoFloats& pivot,
									 const TwoFloats& size,
									 const ThreeFloats& colour,
									 float alpha)
{
	float w, h;
	ImmediateImage *image1;

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

	it = images.find(name);

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

	image1 = &(images[name]);

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

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

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

	//Rotate around the pivot point.
	glTranslatef(pivot.x*size.x, pivot.y*size.y, 0.0f);
	glRotatef(angle*(180.0f/NIALL_PI), 0.0f, 0.0f, 1.0f);
	glTranslatef(-pivot.x*size.x, -pivot.y*size.y, 0.0f);

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

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

	if(currentShader != L"")
		glUniform1i(shaders[currentShader].baseTexLoc, 0);

	glColor4f(colour.x*alpha, colour.y*alpha, colour.z*alpha, alpha);
	glBegin(GL_QUADS);
	{
		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 image1->posWithin.x,
							 image1->posWithin.y);
		glVertex2f(0.0f, 0.0f);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 image1->posWithin.x + image1->sizeWithin.x,
							 image1->posWithin.y);
		glVertex2f(w, 0.0f);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 image1->posWithin.x + image1->sizeWithin.x,
							 image1->posWithin.y + image1->sizeWithin.y);
		glVertex2f(w, h);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
							 image1->posWithin.x,
							 image1->posWithin.y + image1->sizeWithin.y);
		glVertex2f(0.0f, h);
	}
	glEnd();

	glPopMatrix();
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::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;
	ImmediateImage *image1;

#ifdef DEBUG
	map<wstring, ImmediateImage>::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();

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

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

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

	if(currentShader != L"")
		glUniform1i(shaders[currentShader].baseTexLoc, 0);

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

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

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

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

	glPopMatrix();
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::drawTiledImage(const wstring& name,
									   const TwoFloats& position,
									   const TwoFloats& size,
									   const TwoFloats& tilePos,
									   const TwoFloats& tileNum,
									   const ThreeFloats& colour,
									   float alpha)
{
	ImmediateImage *image1;

#ifdef DEBUG
	map<wstring, ImmediateImage>::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(currentShader != L"")
		glUniform1i(shaders[currentShader].baseTexLoc, 0);

	

	glColor4f(colour.x*alpha, colour.y*alpha, colour.z*alpha, alpha);
	glBegin(GL_QUADS);
	{
		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, tilePos.x, tilePos.y);
		glVertex2f(position.x, position.y);

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

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

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

//------------------------------------------------------------------------------
void GLImmediateDrawer::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 GLImmediateDrawer::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 GLImmediateDrawer::drawLine(const wstring& image,
								 const TwoFloats& start,
								 const TwoFloats& end,
								 const ThreeFloats& colour,
								 float alpha)
{
	float x[4];
	float y[4];
	map<wstring, ImmediateImage>::iterator it;
	const float dist = sqrtf(((end.x-start.x)*(end.x-start.x)) + ((end.y-start.y)*(end.y-start.y)));
	const float cX = (-(end.y-start.y)/dist)*4.0f;
	const float cY = ((end.x-start.x)/dist)*4.0f;

	//Bottom left point.
	x[0] = start.x + cX;
	y[0] = start.y + cY;
	//Bottom right point.
	x[1] = end.x + cX;
	y[1] = end.y + cY;
	//Top right point.
	x[2] = end.x - cX;
	y[2] = end.y - cY;
	//Top left point.
	x[3] = start.x - cX;
	y[3] = start.y - cY;

	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;
	}

	if(currentShader != L"")
		glUniform1i(shaders[currentShader].baseTexLoc, 0);

	glColor4f(colour.x*alpha, colour.y*alpha, colour.z*alpha, alpha);
	glBegin(GL_QUADS);
	{
		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 0.0f);
		glVertex2f(x[0], y[0]);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 1.0f);
		glVertex2f(x[1], y[1]);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 1.0f);
		glVertex2f(x[2], y[2]);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 0.0f);
		glVertex2f(x[3], y[3]);
	}
	glEnd();
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::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, ImmediateImage>::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;
	}

	if(currentShader != L"")
		glUniform1i(shaders[currentShader].baseTexLoc, 0);

	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*pointsAlpha[i],
						  pointsColour[i].y*pointsAlpha[i],
						  pointsColour[i].z*pointsAlpha[i], 
						  pointsAlpha[i]);
				glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 0.0f);
				glVertex2f(corners[0].x, corners[0].y);

				glColor4f(pointsColour[i+1].x*pointsAlpha[i+1],
						  pointsColour[i+1].y*pointsAlpha[i+1],
						  pointsColour[i+1].z*pointsAlpha[i+1], 
						  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*pointsAlpha[i],
						  pointsColour[i].y*pointsAlpha[i],
						  pointsColour[i].z*pointsAlpha[i], 
						  pointsAlpha[i]);
				glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 0.0f);
				glVertex2f(corners[3].x, corners[3].y);
			}
			glEnd();
		}
	}
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::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;

	//Draw the rectangle.
	glColor4f(colour.x*alpha, colour.y*alpha, colour.z*alpha, alpha);
	glBegin(GL_QUADS);
	{
		glTexCoord2f(0.0f, 0.0f);
		glVertex2f(position.x, position.y);
		glTexCoord2f(1.0f, 0.0f);
		glVertex2f(position.x+size.x, position.y);
		glTexCoord2f(1.0f, 1.0f);
		glVertex2f(position.x+size.x, position.y+size.y);
		glTexCoord2f(0.0f, 1.0f);
		glVertex2f(position.x, position.y+size.y);
	}
	glEnd();
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::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*alphaTop, colourTop.y*alphaTop, colourTop.z*alphaTop, alphaTop);
	glBegin(GL_QUADS);
	{
		glVertex2f(position.x, position.y);
		glVertex2f(position.x+size.x, position.y);
		glColor4f(colourBottom.x*alphaBottom, colourBottom.y*alphaBottom, colourBottom.z*alphaBottom, alphaBottom);
		glVertex2f(position.x+size.x, position.y+size.y);
		glVertex2f(position.x, position.y+size.y);
	}
	glEnd();
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::translate(const TwoFloats& offset)
{
	glTranslatef(offset.x, offset.y, 0.0f);
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::scale(const TwoFloats& size)
{
	glScalef(size.x, size.y, 0.0f);
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::rotate(float angle, const TwoFloats& pivot)
{
	//Rotate around the pivot point.
	glTranslatef(pivot.x, pivot.y, 0.0f);
	glRotatef(angle*(180.0f/NIALL_PI), 0.0f, 0.0f, 1.0f);
	glTranslatef(-pivot.x, -pivot.y, 0.0f);
}

//------------------------------------------------------------------------------
void GLImmediateDrawer::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.
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	//So our co-ordinates for drawing are 0-size.x (x) and size.y-0 (y).
	gluOrtho2D(0.0f, (float)size.x, (float)size.y, 0.0f);

	glMatrixMode(GL_MODELVIEW);
}

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

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	//So our co-ordinates for drawing are 0-drawingSize.x (x) and
	//drawingSize.y-0 (y).
	gluOrtho2D(0.0f, drawingSize.x, drawingSize.y, 0.0f);

	glMatrixMode(GL_MODELVIEW);
}

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

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBufferObject);
	glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);

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

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

	resetViewport();
}

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

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

	return retval;
}

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

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

//------------------------------------------------------------------------------
void GLImmediateDrawer::printInfoLog(GLuint shader)
{
	float version;
	std::stringstream tempstr;
	const GLubyte *versionString = glGetString(GL_VERSION);
	//const GLubyte *extensionString = glGetString(GL_EXTENSIONS);

	tempstr << versionString;
	tempstr >> version;

	//Some older machines (my netbook, for example) support shaders, but will
	//crash on glGetProgramInfoLog(). This should make sure they ignore the
	//problematic calls here.
	if(version >= 2.0f)
	{
		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);
		}
	}
}

//------------------------------------------------------------------------------
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* GLImmediateDrawer::line_png = (const char*)temp1;
