/*
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License version 2 
	as published by the Free Software Foundation.

	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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


	Copyright (C) 2006  Thierry Berger-Perrin <tbptbp@gmail.com>
*/
#ifndef OGL_H
#define OGL_H

#include "specifics.h"
#include "math_vec.h"

// gah, we're doomed.
#define GLEW_STATIC
#include "glew.h"
#ifdef WINDOWS
	#include "wglew.h"
#else
	#include "glxew.h"
#endif


#include <memory> //FIXME:

namespace ogl {
	//bool_t extension_search(const char *);
	namespace helpers {
		static NOINLINE GLuint generate_texture_name() {
			GLuint id = 0;
			glGenTextures(1, &id);
			return id;
		}

		static NOINLINE void draw_quad(const point_t &offset, const point_t &extent) {
			glBegin(GL_QUADS);
			{
				/*
				glTexCoord2f(0, 0); glVertex3f(-1, -1, -0.5f);
				glTexCoord2f(1, 0); glVertex3f( 1, -1, -0.5f);
				glTexCoord2f(1, 1); glVertex3f( 1,  1, -0.5f);
				glTexCoord2f(0, 1); glVertex3f(-1,  1, -0.5f);
				*/
				const int d = -1;
				/*
				glTexCoord2f(0, 1); glVertex3i(offset.x,			offset.y,				d);
				glTexCoord2f(1, 1); glVertex3i(offset.x + extent.x,	offset.y,				d);
				glTexCoord2f(1, 0); glVertex3i(offset.x + extent.x,	offset.y + extent.y,	d);
				glTexCoord2f(0, 0); glVertex3i(offset.x,			offset.y + extent.y,	d);
				*/
				glTexCoord2i(0, 1); glVertex3i(offset.x,			offset.y,				d);
				glTexCoord2i(1, 1); glVertex3i(offset.x + extent.x,	offset.y,				d);
				glTexCoord2i(1, 0); glVertex3i(offset.x + extent.x,	offset.y + extent.y,	d);
				glTexCoord2i(0, 0); glVertex3i(offset.x,			offset.y + extent.y,	d);
			}
			glEnd();
		}
	}

	struct texture_t {
		GLuint			id;
		const char		* const tag;
		const GLint		internal_format;
		const point_t	size;
		const float		rcp_w, rcp_h;

		enum {
			//internal_format =
								// GL_RGBA32F_ARB,
								// GL_RGBA16F_ARB, // GL_RGBA32F_ARB, // GL_RGBA16F_ARB, //GL_RGBA8, /* GL_RGBA32F_ARB GL_RGBA16F_ARB */
			//filter_min =  //GL_NEAREST, GL_LINEAR, /*  */
			//filter_mag = filter_min,
			clamping = GL_CLAMP_TO_EDGE /* GL_CLAMP */
			//clamping = GL_REPEAT
		};

		texture_t(const char *etiquette, const GLint format, const point_t res) :
			id(-1), 
			tag(etiquette),
			internal_format(format), size(res), rcp_w(1.f/res.x), rcp_h(1.f/res.y) {}

		void bind() const { glBindTexture(GL_TEXTURE_2D, id); }

		//void generate(const void *data, const bool_t components) {
		void generate(const GLenum format, const GLenum type, const GLint filter_min, const GLint filter_mag, const void *data) {
			id = helpers::generate_texture_name();
			bind();
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter_min);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_mag);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamping);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamping);
			//glTexImage2D(GL_TEXTURE_2D, 0, internal_format, size.x, size.y, 0, components == 3 ? GL_RGB : GL_RGBA, GL_FLOAT, data);
			glTexImage2D(GL_TEXTURE_2D, 0, internal_format, size.x, size.y, 0, format, type, data);
		}


		// void attach(const GLuint att) { glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + att, GL_TEXTURE_2D, id, 0); }

		void upload(void *pixels) {
			//enum { lvl = 0, format = GL_RGBA, type = GL_FLOAT };
			enum { lvl = 0, format = GL_RGBA, type = GL_FLOAT };
			
			glBindTexture(GL_TEXTURE_2D, id);
			glTexSubImage2D(GL_TEXTURE_2D, lvl, 0,0, size.x, size.y, format, type, pixels);
		}
	};

	// http://www.nvidia.com/dev_content/nvopenglspecs/GL_ARB_pixel_buffer_object.txt
	struct stream_texture_t: public texture_t {
		GLuint			pbo_id;

		stream_texture_t(const char *etiquette, const GLint format, const point_t res): texture_t(etiquette, format, res)
		{}

		void generate(const GLenum format, const GLenum type, const GLint filter_min, const GLint filter_mag) {
			glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
			texture_t::generate(format, type, filter_min, filter_mag, 0);

			glGenBuffers(1, &pbo_id);
			glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo_id);
		}

		//FIXME: if the buffer is aligned enough we might be able to get away without that annoying memcpy
		void upload(void *pixels) {
			enum { lvl = 0, format = GL_RGBA, type = GL_FLOAT };
			const int tex_size = size.area() * sizeof(float) * 4;
			
			glBindTexture(GL_TEXTURE_2D, id);
			// glTexSubImage2D(GL_TEXTURE_2D, lvl, 0,0, size.x, size.y, format, type, pixels);
			
			// Reset the contents of the texSize-sized buffer object
            glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, tex_size, 0, GL_STREAM_DRAW);

            // Map the texture image buffer (the contents of which
            // are undefined due to the previous glBufferData)
            GLvoid *pbo_mem = glMapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY);
            
            // Modify (sub-)buffer data
			std::memcpy(pbo_mem, pixels, tex_size);

            // Unmap the texture image buffer
            glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB);

            // Update (sub-)teximage from texture image buffer
            //glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth, texHeight, GL_BGRA, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
			glTexSubImage2D(GL_TEXTURE_2D, lvl, 0,0, size.x, size.y, format, type, 0);
		}


		void *map() {
			enum { lvl = 0, format = GL_RGBA, type = GL_FLOAT };
			const int tex_size = size.area() * sizeof(float) * 4;
			
			glBindTexture(GL_TEXTURE_2D, id);
			// glTexSubImage2D(GL_TEXTURE_2D, lvl, 0,0, size.x, size.y, format, type, pixels);
			
			// Reset the contents of the texSize-sized buffer object
            glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, tex_size, 0, GL_STREAM_DRAW);

            // Map the texture image buffer (the contents of which
            // are undefined due to the previous glBufferData)
            return glMapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY);
		}

		void transfer() {
			enum { lvl = 0, format = GL_RGBA, type = GL_FLOAT };
			const int tex_size = size.area() * sizeof(float) * 4;

			// Unmap the texture image buffer
            glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB);

            // Update (sub-)teximage from texture image buffer
            //glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texWidth, texHeight, GL_BGRA, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
			glTexSubImage2D(GL_TEXTURE_2D, lvl, 0,0, size.x, size.y, format, type, 0);
		}
	};




	struct unit_quad_t {
		GLuint list;

		void generate();
		void draw() { glCallList(list); }
	};


	namespace hud {
		void prologue();
		void epilogue();

		void set_status(const char * const);
	}
}
#endif
