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

// TODO: This whole file needs to be gutted. The DirectX renderer is no
//   longer used.

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

#include "renderer.h"

#include <SDL.h>
#include <SDL_syswm.h>

#include <assert.h>

using namespace std;

#include "../console/console.h"
using namespace Console;

#include "../texturemanager/texturemanager.h"

#ifdef RENDERER_COMPILE_OPENGL

#	include <GL/glew.h>
#	include <GL/gl.h>
#	include <GL/glu.h>

#endif

#ifdef RENDERER_COMPILE_DIRECTX

#	ifdef WIN32
#		include <d3d9.h>
#		include <d3dx9.h>
#	endif

#	ifdef WINELIB
#		include <wine/windows/d3d9.h>
#		include <wine/windows/d3dx9.h>
#	endif

	struct CUSTOMVERTEX {
		FLOAT x, y, z;
		DWORD color;
		FLOAT u, v;
	};

	// Windows API callback thinger.
	LRESULT CALLBACK wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
		
		switch(message) {
			case WM_CLOSE:
				return 0;
				break;
			case WM_DESTROY:
				return 0;
				break;
		}
		
		return DefWindowProc(hWnd, message, wParam, lParam);
	}

	HWND createDXWindow(int screenWidth, int screenHeight, const std::string windowTitle) {
		
		HWND hWnd;
		
		WNDCLASS wndClass;
		
		const char className[] = "lilyEngineWindow";
		
		wndClass.style = CS_HREDRAW | CS_VREDRAW;
		wndClass.lpfnWndProc = wndProc;
		wndClass.cbClsExtra = 0;
		wndClass.cbWndExtra = 0;
		wndClass.hInstance = GetModuleHandle(NULL);
		wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
		wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
		wndClass.hbrBackground = NULL;
		wndClass.lpszMenuName = NULL;
		wndClass.lpszClassName = className;
		
		if(RegisterClass(&wndClass) == 0) {
			quitWithError("Couldn't make window for DirectX renderer.");
			return NULL;
		}
		
		hWnd = CreateWindow(
			className,
			(windowTitle + " (DirectX)").c_str(),
			WS_OVERLAPPEDWINDOW,
			CW_USEDEFAULT,
			CW_USEDEFAULT,
			screenWidth,
			screenHeight,
			NULL,
			NULL,
			GetModuleHandle(NULL),
			NULL);
		
		ShowWindow(hWnd, SW_SHOW);
		UpdateWindow(hWnd);
		
		return hWnd;
	}
	
	void handleWindowsMessages(HWND hWnd) {
		MSG msg;
		
		while(PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

#endif




using namespace std;

Renderer::Renderer() {
	initialized = false;
	boundTexture = -1;
	
#	ifdef RENDERER_COMPILE_DIRECTX
		dxhWnd = NULL;
		direct3d = NULL;
		direct3dDev = NULL;
#	endif
}

Renderer::~Renderer(void) {
	if(initialized) shutdown();
}

void Renderer::initGL(int width, int height, unsigned int videoInitFlags) {

	screen = SDL_SetVideoMode(width, height, 32, videoInitFlags);
	glewInit();
	
	glMatrixMode(GL_PROJECTION);
	gluOrtho2D(0, screen->w, 0, screen->h);
	glMatrixMode(GL_MODELVIEW);
	
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	glAlphaFunc(GL_GREATER, 0);
	glEnable(GL_ALPHA_TEST);

	glEnable(GL_TEXTURE_2D);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
}

void Renderer::init(int screenWidth, int screenHeight) {
	
	string windowTitle = "Lily Engine 3";
	
	boundTexture = -1;
	totalTextures = 0;
	totalLists = 0;
	
	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
	
	unsigned int videoInitFlags = 0;
	
	r = g = b = a = 1;
	
#	ifdef RENDERER_COMPILE_OPENGL

		out("system") << "OpenGL flag set." << endl;
		videoInitFlags |= SDL_OPENGL;
		
		out("system") << "Resizable flag set." << endl;
		videoInitFlags |= SDL_RESIZABLE;
		
#	endif

	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);

	SDL_EnableUNICODE(1);
	
	initGL(screenWidth, screenHeight, videoInitFlags);
	
	// GLEW is now initialized, so we can turn shaders on if
	// they're detected.
	TextureManager *manager = TextureManager::get();
	bool shadersEnabled = manager->getShadersDetected();
	manager->setShadersEnabled(shadersEnabled);
	
	if(!shadersEnabled) {
		out("warning") << "Shaders appear to be unsupported." << endl;
	}

#	ifdef RENDERER_COMPILE_DIRECTX

		HWND hWnd;

#		ifdef RENDERER_COMPILE_OPENGL

			// OpenGL + DirectX
			// Create a new window for DirectX.
	
			hWnd = createDXWindow(screenWidth, screenHeight, windowTitle);

#		else

#			ifdef WINELIB

				// Can't attach a windows d3d thing to a Linux SDL window, so...

				hWnd = createDXWindow(screenWidth, screenHeight, windowTitle);
				
#			else

				// Just DirectX
				// Put DirectX in the SDL Window.
				
				SDL_SysWMinfo sysWmInfo;
				SDL_VERSION(&sysWmInfo.version);
				SDL_GetWMInfo(&sysWmInfo);
				hWnd = sysWmInfo.window;
				
#			endif

#		endif

		dxhWnd = (void*)hWnd;

		LPDIRECT3D9 direct3d = Direct3DCreate9(D3D_SDK_VERSION);
		LPDIRECT3DDEVICE9 direct3dDev = NULL;
		
		if(!direct3d) {
			quitWithError("Couldn't initialize Direct3D.");
		} else {
		
			// Set up, create the device.
			
			D3DPRESENT_PARAMETERS d3dpp;
			ZeroMemory(&d3dpp, sizeof(d3dpp));
			
			d3dpp.Windowed = true;
			d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
			d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
			d3dpp.hDeviceWindow = hWnd;
			
			direct3d->CreateDevice(
				D3DADAPTER_DEFAULT,
				D3DDEVTYPE_HAL,
				hWnd,
				D3DCREATE_SOFTWARE_VERTEXPROCESSING,
				&d3dpp,
				&direct3dDev);

			this->direct3dDev = (void*)direct3dDev;
			this->direct3d = (void*)direct3d;

			if(!direct3dDev) {
				quitWithError("Couldn't make Direct3D device!");
				exit(1);
			}

			// Set up our screen transform.
			
			D3DXMATRIX orthoMatrix;
			D3DXMATRIX initialTranslation;
			D3DXMatrixOrthoLH(&orthoMatrix, screenWidth, screenHeight, -1, 1);
			D3DXMatrixTranslation(&initialTranslation, -1, -1, 0);
						
			direct3dDev->SetTransform(D3DTS_PROJECTION, &(orthoMatrix * initialTranslation));
			direct3dDev->SetRenderState(D3DRS_LIGHTING, false);
			direct3dDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
			
			//direct3dDev->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);

			direct3dDev->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
			direct3dDev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
			direct3dDev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

			//direct3dDev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
			
		}
			
#	endif

	SDL_WM_SetCaption(windowTitle.c_str(), windowTitle.c_str());

	initialized = true;
}

void Renderer::shutdown(void) {

#ifdef RENDERER_COMPILE_DIRECTX

	// Shutdown DirectX
	
	((LPDIRECT3D9)direct3d)->Release();
	((LPDIRECT3DDEVICE9)direct3dDev)->Release();

#endif	
	
	// TODO: Shutdown OpenGL?
		
	SDL_Quit();
	initialized = false;
}


void Renderer::flipScreen(void) {

#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
	
		((LPDIRECT3DDEVICE9)direct3dDev)->Present(NULL, NULL, NULL, NULL);

#	endif
	
#	ifdef RENDERER_COMPILE_OPENGL

		// OpenGL
		
		SDL_GL_SwapBuffers();
		
#	endif
}

void Renderer::clearScreen(float r, float g, float b) {

#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
	
		((LPDIRECT3DDEVICE9)direct3dDev)->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(int(r * 255), int(g * 255), int(b * 255)), 0, 0);

#	endif

#	ifdef RENDERER_COMPILE_OPENGL

		// OpenGL
		glClearColor(r, g, b, 0);
		glClear(GL_COLOR_BUFFER_BIT);
		
#	endif
}

void Renderer::drawLineStrip(const Vector2D *points, int numPoints) {
	
#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		
		LPDIRECT3DVERTEXBUFFER9 vb = NULL;

		d3dd->CreateVertexBuffer(
			numPoints * sizeof(CUSTOMVERTEX),
			0,
			D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1,
			D3DPOOL_DEFAULT,
			&vb,
			NULL);
		
		void *vbVerts;
		vb->Lock(0, numPoints, &vbVerts, 0);
		CUSTOMVERTEX *cv = (CUSTOMVERTEX*)vbVerts;
		
		for(int i = 0; i < numPoints; i++) {
			
			cv[i].x = points[i].x;
			cv[i].y = points[i].y;
			cv[i].z = 0;
			
			cv[i].color = D3DCOLOR_ARGB(
				int(a * 255),
				int(r * 255),
				int(g * 255),
				int(b * 255));

			cv[i].u = 0;
			cv[i].v = 0;
			
		}
		
		vb->Unlock();
		
		d3dd->SetStreamSource(0, vb, 0, sizeof(CUSTOMVERTEX));
		d3dd->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);
		
		d3dd->DrawPrimitive(D3DPT_LINESTRIP, 0, numPoints - 1);
		
		vb->Release();

#	endif
	
#	ifdef RENDERER_COMPILE_OPENGL
		
		// OpenGL

		glColor4f(r, g, b, a);

		glBegin(GL_LINE_STRIP);
			for(int i = 0; i < numPoints; i++) {
				glVertex2f(points[i].x, points[i].y);
			}
		glEnd();
	
#	endif
}

void Renderer::drawLines(const Vector2D *points, int numPoints) {
	
#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		
		LPDIRECT3DVERTEXBUFFER9 vb = NULL;

		d3dd->CreateVertexBuffer(
			numPoints * sizeof(CUSTOMVERTEX),
			0,
			D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1,
			D3DPOOL_DEFAULT,
			&vb,
			NULL);
		
		void *vbVerts;
		vb->Lock(0, numPoints, &vbVerts, 0);
		CUSTOMVERTEX *cv = (CUSTOMVERTEX*)vbVerts;
		
		for(int i = 0; i < numPoints; i++) {
			
			cv[i].x = points[i].x;
			cv[i].y = points[i].y;
			cv[i].z = 0;
			
			cv[i].color = D3DCOLOR_ARGB(
				int(a * 255),
				int(r * 255),
				int(g * 255),
				int(b * 255));

			cv[i].u = 0;
			cv[i].v = 0;
			
		}
		
		vb->Unlock();
		
		d3dd->SetStreamSource(0, vb, 0, sizeof(CUSTOMVERTEX));
		d3dd->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);
		
		d3dd->DrawPrimitive(D3DPT_LINELIST, 0, numPoints / 2);
		
		vb->Release();

#	endif
	
#	ifdef RENDERER_COMPILE_OPENGL
	
		// OpenGL

		glColor4f(r, g, b, a);

		glBegin(GL_LINES);
			for(int i = 0; i < numPoints; i++) {
				glVertex2f(points[i].x, points[i].y);
			}
		glEnd();
	
#	endif

}

void Renderer::drawQuads(const Vertex3D *vertices, int numQuads) {
	
#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		
		LPDIRECT3DVERTEXBUFFER9 vb = NULL;

		d3dd->CreateVertexBuffer(
			numQuads * 4 * sizeof(CUSTOMVERTEX),
			0,
			D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1,
			D3DPOOL_DEFAULT,
			&vb,
			NULL);
		
		void *vbVerts;
		vb->Lock(0, numQuads * 4, &vbVerts, 0);
		CUSTOMVERTEX *cv = (CUSTOMVERTEX*)vbVerts;
		
		for(int i = 0; i < numQuads * 4; i++) {
			
			cv[i].x = vertices[i].x;
			cv[i].y = vertices[i].y;
			cv[i].z = vertices[i].z;
			
			cv[i].color = D3DCOLOR_ARGB(
				int(vertices[i].a * a * 255),
				int(vertices[i].r * r * 255),
				int(vertices[i].g * g * 255),
				int(vertices[i].b * b * 255));

			/*
			cv[i].color = D3DCOLOR_ARGB(
				int(vertices[i].a * a * 255),
				//rand() % 255,
				rand() % 255,
				rand() % 255,
				rand() % 255);
			*/
			
			// TODO: Need these in pixel coords?
			cv[i].u = vertices[i].u;
			cv[i].v = vertices[i].v;
			
		}
		
		vb->Unlock();
		
		d3dd->SetStreamSource(0, vb, 0, sizeof(CUSTOMVERTEX));
		d3dd->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);
		
		for(int i = 0; i < numQuads; i++) {
			d3dd->DrawPrimitive(D3DPT_TRIANGLEFAN, i * 4, 2);
			
		}
		
		vb->Release();

#	endif

#	ifdef RENDERER_COMPILE_OPENGL

		// OpenGL

		glBegin(GL_QUADS);
		
		for(int i = 0; i < numQuads * 4; i++) {
			
			glColor4f(
				vertices[i].r * r,
				vertices[i].g * g,
				vertices[i].b * b,
				vertices[i].a * a);
				
			glTexCoord2f(
				vertices[i].u,
				vertices[i].v);
				
			glVertex3f(
				vertices[i].x,
				vertices[i].y,
				vertices[i].z);
		}
		
		glEnd();
	
#	endif

}

void Renderer::drawTriangleFan(const Vertex3D *vertices, int numVerts) {

#	ifdef RENDERER_COMPILE_DIRECTX
	
		// DirectX
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		
		LPDIRECT3DVERTEXBUFFER9 vb = NULL;

		d3dd->CreateVertexBuffer(
			numVerts * sizeof(CUSTOMVERTEX),
			0,
			D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1,
			D3DPOOL_DEFAULT,
			&vb,
			NULL);
		
		void *vbVerts;
		vb->Lock(0, numVerts, &vbVerts, 0);
		CUSTOMVERTEX *cv = (CUSTOMVERTEX*)vbVerts;
		
		for(int i = 0; i < numVerts; i++) {
			
			cv[i].x = vertices[i].x;
			cv[i].y = vertices[i].y;
			cv[i].z = vertices[i].z;
			
			cv[i].color = D3DCOLOR_ARGB(
				int(vertices[i].a * a * 255),
				int(vertices[i].r * r * 255),
				int(vertices[i].g * g * 255),
				int(vertices[i].b * b * 255));

			/*
			cv[i].color = D3DCOLOR_ARGB(
				int(vertices[i].a * a * 255),
				//rand() % 255,
				rand() % 255,
				rand() % 255,
				rand() % 255);
			*/
			
			cv[i].u = vertices[i].u;
			cv[i].v = vertices[i].v;
			
		}
		
		vb->Unlock();
		
		d3dd->SetStreamSource(0, vb, 0, sizeof(CUSTOMVERTEX));
		d3dd->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);
		
		d3dd->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, numVerts - 2);
		
		vb->Release();

#	endif
		
#	ifdef RENDERER_COMPILE_OPENGL

		// OpenGL

		glBegin(GL_TRIANGLE_FAN);
		
		for(int i = 0; i < numVerts; i++) {
			
			glColor4f(
				vertices[i].r * r,
				vertices[i].g * g,
				vertices[i].b * b,
				vertices[i].a * a);
				
			glTexCoord2f(
				vertices[i].u,
				vertices[i].v);
				
			glVertex3f(
				vertices[i].x,
				vertices[i].y,
				vertices[i].z);
		}
		
		glEnd();
	
#	endif

}

void Renderer::setColor(float r, float g, float b, float a) {
	this->r = r;
	this->g = g;
	this->b = b;
	this->a = a;
	
	if(a > 1) this->a = 1;

#	ifdef RENDERER_COMPILE_OPENGL
		glColor4f(r, g, b, a);
#	endif
}

/*
template<class T> void removeFromVector(std::vector<T> &v, int pos) {
	assert(pos < v.size());
	v[pos] = *(v.end() - 1);
	v.erase(v.end() - 1);
}
*/

// FIXME: This never gets called anymore.
//   Prune it.

void Renderer::bindTexture(int texNum) {
	if(texNum == RENDERER_NOTEXTURE) {
		unbindTexture();
		return;
	}

#	ifdef RENDERER_COMPILE_DIRECTX
	
		// DirectX
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		LPDIRECT3DTEXTURE9 d3dTex = (LPDIRECT3DTEXTURE9)directXTextures[texNum];
		
		d3dd->SetTexture(0, d3dTex);
		d3dd->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
		d3dd->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
		d3dd->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
		d3dd->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);

#endif

#	ifdef RENDERER_COMPILE_OPENGL
		assert(texNum < int(openGLTextures.size()));
		assert(texNum >= 0);
		
		GLuint realTex = openGLTextures[texNum];
		GLint testNum = 0;
		glGetIntegerv(GL_TEXTURE_BINDING_2D, &testNum);
		if(GLint(realTex) != testNum) {
			glBindTexture(GL_TEXTURE_2D, realTex);
		}
		
		// Just to make sure it's not getting called anymore.
		assert(0);
#	endif
	
	boundTexture = texNum;
}

void Renderer::unbindTexture(void) {
	//if(boundTexture == RENDERER_NOTEXTURE) return;

#	ifdef RENDERER_COMPILE_DIRECTX
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		d3dd->SetTexture(0, NULL);
#	endif
#	ifdef RENDERER_COMPILE_OPENGL
		glBindTexture(GL_TEXTURE_2D, 0);
		if(glUseProgramObjectARB) {
			glUseProgramObjectARB(0);
		}
#	endif

	//boundTexture = RENDERER_NOTEXTURE;
}

int Renderer::getBoundTexture(void) {
	return boundTexture;
}

int Renderer::makeTextureFromRGBA(void *pixels, int width, int height) {

	unsigned int slotNumber;
	if(textureListGaps.size()) {
		slotNumber = textureListGaps[textureListGaps.size() - 1];
		textureListGaps.erase(textureListGaps.end() - 1);
	} else {
		slotNumber = totalTextures++;
	}

	
#	ifdef RENDERER_COMPILE_DIRECTX
	
		// DirectX
		
		// TODO: This might be a lot more efficient if we put it into one
		// DX surface, then copy it to a real texture (one that's note D3DUSAGE_DYNAMIC).

		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		LPDIRECT3DTEXTURE9 d3dTex;
		LPDIRECT3DTEXTURE9 d3dFinalTex;

		d3dd->CreateTexture(width, height, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &d3dTex, NULL);
		d3dd->CreateTexture(width, height, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &d3dFinalTex, NULL);
		
		D3DLOCKED_RECT lockedRect;
		RECT rectToLock = {0, 0, width, height};
		
		if(FAILED(d3dTex->LockRect(0, &lockedRect, &rectToLock, 0))) {
			quitWithError("Couldn't DX lock texture!");
			exit(1);
		}
	
		// TODO: Pay attention to Pitch.
		//memcpy(lockedRect.pBits, pixels, width * height * 4);
		int totalPixels = width * height;
		for(int i = 0; i < totalPixels; i++) {
			((unsigned char*)lockedRect.pBits)[i * 4    ] = ((unsigned char*)pixels)[i * 4 + 2];
			((unsigned char*)lockedRect.pBits)[i * 4 + 1] = ((unsigned char*)pixels)[i * 4 + 1];
			((unsigned char*)lockedRect.pBits)[i * 4 + 2] = ((unsigned char*)pixels)[i * 4 + 0];
			((unsigned char*)lockedRect.pBits)[i * 4 + 3] = ((unsigned char*)pixels)[i * 4 + 3];
		}
		
		d3dTex->UnlockRect(0);

		d3dd->UpdateTexture(d3dTex, d3dFinalTex);

		d3dTex->Release();

		if(slotNumber == directXTextures.size()) {
			directXTextures.push_back((void*)d3dFinalTex);
		} else {
			assert(slotNumber < directXTextures.size());
			directXTextures[slotNumber] = (void*)d3dFinalTex;
		}

#	endif

#	ifdef RENDERER_COMPILE_OPENGL

		// OpenGL

		GLuint texNum;
		
		glGenTextures(1, &texNum);
		glBindTexture(GL_TEXTURE_2D, texNum);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

		// TODO: Add an option to let the user choose which texture mode they want.
	
		// This is the only thing we want from glext.h, so I'm not going to bother including the rest.
#		define GL_COMPRESSED_RGBA_ARB      0x84EE
#		define GL_TEXTURE_COMPRESSION_HINT 0x84EF
		
		// This uses a lot of memory and probably isn't necessary.
		//gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
		
		// This may be the middle ground.
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
		
		// This uses very little memory, but compression artifacts can start to manifest themselves on the pixel art.
		//glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
		//glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_ARB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
		
		int err = glGetError();
		if(err) {
			// FIXME: TODO: Get error string.
			out("error") << "OpenGL exploded: " << endl;
		}
		
		if(slotNumber == openGLTextures.size()) {
			openGLTextures.push_back(texNum);
		} else {
			assert(slotNumber < openGLTextures.size());
			openGLTextures[slotNumber] = texNum;
		}
	
#	endif
	
	return slotNumber;
}

void Renderer::freeTexture(int texNum) {
	assert(texNum < totalTextures);
	
#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX

		LPDIRECT3DTEXTURE9 d3dTex = (LPDIRECT3DTEXTURE9)directXTextures[texNum];
		d3dTex->Release();
		directXTextures[texNum] = NULL;
		
#	endif
	
#	ifdef RENDERER_COMPILE_OPENGL

		// OpenGL
	
		GLuint glTexNum = openGLTextures[texNum];
		openGLTextures[texNum] = 0;
		
		glDeleteTextures(1, &glTexNum);
	
#	endif	

	textureListGaps.push_back(texNum);
}
		
void Renderer::pushMatrix(void) {

#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
		
		D3DXMATRIX *matrix = new D3DXMATRIX();
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		d3dd->GetTransform(D3DTS_WORLD, matrix);
		directXMatrixStack.push_back((void*)matrix);

#	endif

#	ifdef RENDERER_COMPILE_OPENGL
	
		// OpenGL
		
		glPushMatrix();
	
#	endif

}

void Renderer::popMatrix(void) {

#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
		
		D3DXMATRIX *matrix = (D3DXMATRIX*)*(directXMatrixStack.end() - 1);
		directXMatrixStack.erase(directXMatrixStack.end() - 1);
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		d3dd->SetTransform(D3DTS_WORLD, matrix);
		
		delete matrix;
		
#	endif

#	ifdef RENDERER_COMPILE_OPENGL

		// OpenGL

		glPopMatrix();
		
		// TODO: Check for underflow?
	
#	endif
	
}

void Renderer::rotateZ(float radians) {

#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
		
		D3DXMATRIX currentMatrix;
		D3DXMATRIX rotationMatrix;
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		d3dd->GetTransform(D3DTS_WORLD, &currentMatrix);
		D3DXMatrixRotationZ(&rotationMatrix, radians);
		
		// Should currentMatrix and rotationMatrix be swapped here?
		d3dd->SetTransform(D3DTS_WORLD, &(rotationMatrix * currentMatrix));

#	endif

#	ifdef RENDERER_COMPILE_OPENGL
		
		// OpenGL

		glRotatef(radians * (180.0 / 3.14159), 0, 0, 1);

#	endif

}

void Renderer::scale(float x, float y, float z) {

#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
		
		D3DXMATRIX currentMatrix;
		D3DXMATRIX scaleMatrix;
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		d3dd->GetTransform(D3DTS_WORLD, &currentMatrix);
		D3DXMatrixScaling(&scaleMatrix, x, y, z);
		
		// Should currentMatrix and rotationMatrix be swapped here?
		d3dd->SetTransform(D3DTS_WORLD, &(scaleMatrix * currentMatrix));

#	endif

#	ifdef RENDERER_COMPILE_OPENGL
		
		// OpenGL

		glScalef(x, y, z);

#	endif

}

void Renderer::translate(float x, float y, float z) {

#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
		
		D3DXMATRIX currentMatrix;
		D3DXMATRIX translationMatrix;
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		d3dd->GetTransform(D3DTS_WORLD, &currentMatrix);
		D3DXMatrixTranslation(&translationMatrix, x, y, z);
		
		// Should currentMatrix and rotationMatrix be swapped here?
		d3dd->SetTransform(D3DTS_WORLD, &(translationMatrix * currentMatrix));

#	endif

#	ifdef RENDERER_COMPILE_OPENGL
	
		// OpenGL

		glTranslatef(x, y, z);

#	endif

}

#ifdef RENDERER_COMPILE_OPENGL
	struct GLList {
		GLfloat *vertices;
		GLfloat *uvs;
		int numVerts;
	};
#endif

int Renderer::createList(const Vertex3D *vertices, int numVertices, Renderer_ListType primType) {

	unsigned int listSlot;
	if(listListGaps.size()) {
		listSlot = listListGaps[listListGaps.size() - 1];
		listListGaps.erase(listListGaps.end() - 1);
	} else {
		listSlot = totalLists++;
	}

#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX

		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		LPDIRECT3DVERTEXBUFFER9 vb = NULL;

		d3dd->CreateVertexBuffer(
			numVertices * sizeof(CUSTOMVERTEX),
			0,
			D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1,
			D3DPOOL_DEFAULT,
			&vb,
			NULL);
		
		void *vbVerts;
		vb->Lock(0, numVertices, &vbVerts, 0);
		CUSTOMVERTEX *cv = (CUSTOMVERTEX*)vbVerts;
		
		for(int i = 0; i < numVertices; i++) {
			
			cv[i].x = vertices[i].x;
			cv[i].y = vertices[i].y;
			cv[i].z = vertices[i].z;
			
			cv[i].color = D3DCOLOR_ARGB(
				int(vertices[i].a * a * 255),
				int(vertices[i].r * r * 255),
				int(vertices[i].g * g * 255),
				int(vertices[i].b * b * 255));
			
			
			/*
			cv[i].color = D3DCOLOR_ARGB(
				int(vertices[i].a * a * 255),
				//rand() % 255,
				rand() % 255,
				rand() % 255,
				rand() % 255);
			*/
			
			// TODO: Need these in pixel coords?
			cv[i].u = vertices[i].u;
			cv[i].v = vertices[i].v;
			
		}
		
		vb->Unlock();

		if(listSlot == directXLists.size()) {
			directXLists.push_back(vb);
		} else {
			assert(listSlot < directXLists.size());
			directXLists[listSlot] = vb;
		}

#	endif

#	ifdef RENDERER_COMPILE_OPENGL
	
		// OpenGL
		
		GLuint newList;
		
		newList = glGenLists(1);
		glNewList(newList, GL_COMPILE);
		
		switch(primType) {
		case RENDERER_LIST_QUADS:
			glBegin(GL_QUADS);
			break;
		case RENDERER_LIST_TRIANGLES:
			glBegin(GL_TRIANGLES);
			break;
		case RENDERER_LIST_LINESTRIP:
			glBegin(GL_LINE_STRIP);
			break;
		case RENDERER_LIST_LINES:
			glBegin(GL_LINES);
			break;
		default:
			assert(0);
			break;
		}
		
		for(int i = 0; i < numVertices; i++) {
			
			glTexCoord2f(
				vertices[i].u,
				vertices[i].v);
				
			glVertex3f(
				vertices[i].x,
				vertices[i].y,
				vertices[i].z);
				
		}
		
		glEnd();
		glEndList();

		if(listSlot == openGLLists.size()) {
			openGLLists.push_back(newList);
		} else {
			assert(listSlot < openGLLists.size());
			openGLLists[listSlot] = newList;
		}

		//cout << "OpenGL created GL list #" << newList << " in slot " << listSlot << endl;

		/*
		GLList *newList = new GLList();
		newList->numVerts = numVertices;
		newList->vertices = new GLfloat[numVertices * 3];
		newList->uvs      = new GLfloat[numVertices * 2];
		for(int i = 0; i < numVertices; i++) {
			newList->vertices[i * 3    ] = vertices[i].x;
			newList->vertices[i * 3 + 1] = vertices[i].y;
			newList->vertices[i * 3 + 2] = vertices[i].z;
			newList->uvs[i * 2    ] = vertices[i].u;
			newList->uvs[i * 2 + 1] = vertices[i].v;
		}*/

		if(listSlot == openGLLists.size()) {
			openGLLists.push_back(newList);
		} else {
			assert(listSlot < openGLLists.size());
			openGLLists[listSlot] = newList;
		}

#	endif

	if(listSlot == listTypes.size()) {
		listTypes.push_back(primType);
	} else {
		assert(listSlot < listTypes.size());
		listTypes[listSlot] = primType;
	}

	return listSlot;
}

void Renderer::callList(int listNum) {

	assert(listNum >= 0);

#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		LPDIRECT3DVERTEXBUFFER9 vb = (LPDIRECT3DVERTEXBUFFER9)(directXLists[listNum]);
		D3DVERTEXBUFFER_DESC desc;
		vb->GetDesc(&desc);
		
		d3dd->SetStreamSource(0, vb, 0, sizeof(CUSTOMVERTEX));
		d3dd->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);
		
		//d3dd->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL);
		//d3dd->SetRenderState(D3DRS_COLORVERTEX, FALSE);
		
		//D3DMATERIAL9 material;
		//ZeroMemory(&material, sizeof(material));
		
		//D3DCOLORVALUE color = {
			//int(r * 255), int(g * 255), int(b * 255), int(a * 255)
			//1, 0, 1, 0,
		//};
		
		//material.Diffuse = color;
		
		//d3dd->SetMaterial(&material);
		
		
		int numVerts = desc.Size / sizeof(CUSTOMVERTEX);
		
		switch(listTypes[listNum]) {
			case RENDERER_LIST_QUADS:
				for(int i = 0; i < numVerts / 4; i++) {
					d3dd->DrawPrimitive(D3DPT_TRIANGLEFAN, i * 4, 2);
				}
				break;
		}
		
		//d3dd->SetRenderState(D3DRS_COLORVERTEX, TRUE);
		//d3dd->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
		
#	endif

#	ifdef RENDERER_COMPILE_OPENGL

		// OpenGL

		//cout << "OpenGL calling GL list #" << openGLLists[listNum] << " in slot " << listNum << endl;

		glColor4f(r, g, b, a);
		
		glCallList(openGLLists[listNum]);


		//GLList *list = (GLList*)openGLLists[listNum];

		/*
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, list->vertices);
		glTexCoordPointer(2, GL_FLOAT, 0, list->uvs);
		glDrawArrays(GL_QUADS, 0, list->numVerts / 4);
		glDisableClientState(GL_VERTEX_ARRAY);*/

		/*
		glBegin(GL_QUADS);
		for(int i = 0; i < list->numVerts; i++) {
			glTexCoord2f(
				list->uvs[i * 2],
				list->uvs[i * 2 + 1]);
			glVertex3f(
				list->vertices[i * 3],
				list->vertices[i * 3 + 1],
				list->vertices[i * 3 + 2]);
		}
		glEnd();
		*/

#	endif
	
}

void Renderer::setListColor(int listNum, float r, float g, float b, float a) {
	
#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		LPDIRECT3DVERTEXBUFFER9 vb = (LPDIRECT3DVERTEXBUFFER9)(directXLists[listNum]);
		D3DVERTEXBUFFER_DESC desc;
		vb->GetDesc(&desc);
		
		void *vbVerts;
		int numVerts = desc.Size / sizeof(CUSTOMVERTEX);

		vb->Lock(0, desc.Size, &vbVerts, 0);
		
		CUSTOMVERTEX *verts = (CUSTOMVERTEX*)vbVerts;
		
		for(int i = 0; i < numVerts; i++) {
			verts[i].color = D3DCOLOR_ARGB(
				int(a * 255),
				int(r * 255),
				int(g * 255),
				int(b * 255));
		}
		
		vb->Unlock();
		
#	endif

}

void Renderer::purgeLists(void) {

    // Not pushing list gaps here. We need to let stuff think it's
    // actually freeing the lists before they get re-used.

    for(unsigned int i = 0; i < openGLLists.size(); i++) {
		glDeleteLists(openGLLists[i], 1);
        openGLLists[i] = -1;
    }
}

void Renderer::freeList(int listNum) {

	assert(listNum >= 0);

#	ifdef RENDERER_COMPILE_DIRECTX

		// DirectX
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		LPDIRECT3DVERTEXBUFFER9 vb = (LPDIRECT3DVERTEXBUFFER9)(directXLists[listNum]);
		vb->Release();
		
		directXLists[listNum] = NULL;

#	endif

#	ifdef RENDERER_COMPILE_OPENGL
	
		// OpenGL

        if(int(openGLLists[listNum]) != -1) {
            glDeleteLists(openGLLists[listNum], 1);
            openGLLists[listNum] = -1;
        }

		/*
		GLList *list = (GLList*)openGLLists[listNum];
		delete[] list->vertices;
		delete list;

		openGLLists[listNum] = NULL;
		*/

#	endif

	listListGaps.push_back(listNum);
	
}

void Renderer::processExtraEvents(void) {
#	ifdef RENDERER_COMPILE_DIRECTX
		handleWindowsMessages((HWND)dxhWnd);
#	endif
}

void Renderer::beginScene(void) {
#	ifdef RENDERER_COMPILE_DIRECTX
		((LPDIRECT3DDEVICE9)direct3dDev)->BeginScene();
#	endif
}

void Renderer::endScene(void) {
#	ifdef RENDERER_COMPILE_DIRECTX
		((LPDIRECT3DDEVICE9)direct3dDev)->EndScene();
#	endif
}

void Renderer::setBlendMode(Renderer_BlendMode mode) {
#	ifdef RENDERER_COMPILE_DIRECTX
		
		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		
		switch(mode) {
			case RENDERER_BLEND_ADD:
				d3dd->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_ADD);
				break;
			case RENDERER_BLEND_MODULATE:
				d3dd->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
				break;
		}
		
#	endif

#	ifdef RENDERER_COMPILE_OPENGL

		switch(mode) {
			case RENDERER_BLEND_ADD:
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
				break;
			case RENDERER_BLEND_MODULATE:
				glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
				break;
		}

#	endif
}

void Renderer::setFilter(Renderer_FilterType type) {

#	ifdef RENDERER_COMPILE_DIRECTX

		LPDIRECT3DDEVICE9 d3dd = (LPDIRECT3DDEVICE9)direct3dDev;
		
		int dxType = D3DTEXF_POINT;
		if(type == RENDERER_FILTER_LINEAR) {
			dxType = D3DTEXF_LINEAR;
		}

		d3dd->SetSamplerState(0, D3DSAMP_MAGFILTER, dxType);
		d3dd->SetSamplerState(0, D3DSAMP_MINFILTER, dxType);

#	endif

#	ifdef RENDERER_COMPILE_OPENGL

		int glType = GL_NEAREST;
		if(type == RENDERER_FILTER_LINEAR) {
			glType = GL_LINEAR;
		}

		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glType);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glType);

#	endif

}

Vector2D Renderer::getScreenSize(void) {
	return Vector2D(screen->w, screen->h);
}
