/*
	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>
*/
#include "specifics.h"


#include "sys_thread.h"
#include "sys_clock.h"
#include "sys_log.h"

#include "ressource.h"

#include "ogl.h"
#include "driver.h"

#include <memory>


namespace ogl {

	//
	// texture_t
	//

	//
	// unit_quad_t
	//
	void unit_quad_t::generate() {
		list = glGenLists(1);
		glNewList(list, GL_COMPILE);
		{
			// flipped up upside down. hmm.
			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);
				*/
				glTexCoord2f(0, 1); glVertex3f(-1, -1, -0.5f);
				glTexCoord2f(1, 1); glVertex3f( 1, -1, -0.5f);
				glTexCoord2f(1, 0); glVertex3f( 1,  1, -0.5f);
				glTexCoord2f(0, 0); glVertex3f(-1,  1, -0.5f);
			}
			glEnd();
		}
		glEndList();
	}



}


namespace ogl {
	namespace hud {
		struct font_t {
			const rsc::headers_t	* const headers;
			const rsc::glyph_t		* const glyphs ;

			const float rcp_w, rcp_h;

			ogl::texture_t			*tex;

			enum { max_glyphs = 256, default_glyph = ' ' }; // lame
			uint_t lut[max_glyphs];


			font_t(const rsc::headers_t &h, const rsc::glyph_t *g, const unsigned char *pixels)
				:
					headers(&h), glyphs(g),
					rcp_w(1.f/h.img_size_x), rcp_h(1.f/h.img_size_y),
					tex(new texture_t("console-font", GL_ALPHA8, point_t(h.img_size_x, h.img_size_y)))
			{
				tex->generate(GL_ALPHA, GL_UNSIGNED_BYTE, GL_NEAREST, GL_NEAREST, pixels);
				//tex->generate(GL_ALPHA, GL_UNSIGNED_BYTE, GL_NEAREST, GL_LINEAR, pixels);

				build();
			}

			// prepare a lookup table
			void build() {
				for (int i=0; i<max_glyphs; ++i)
					lut[i] = ~0;
				for (int i=0; glyphs[i].tag; ++i)
					lut[glyphs[i].tag] = i;
				for (int i=0; i<max_glyphs; ++i)
					if (lut[i] == ~0u)
						lut[i] = lut[default_glyph];
			}


			template <bool single> void draw_glyph(const uchar_t character, point_t &cur) const {
				const rsc::glyph_t &g(glyphs[lut[character]]);

				// (0,0) (1,0) (1,1) (0,1)
				const float
					u[2] = { float(g.x)*rcp_w, float(g.x + g.width)*rcp_w },
					v[2] = { float(g.y)*rcp_h, float(g.y + g.height)*rcp_h };
				const int
					x[2] = { cur.x+g.offset_x, cur.x+g.offset_x+g.width },
					y[2] = { cur.y+g.offset_y, cur.y+g.offset_y+g.height };


				if (single) glBegin(GL_QUADS);
				{
					glTexCoord2f(u[0],v[0]); glVertex2i(x[0],y[0]);
					glTexCoord2f(u[1],v[0]); glVertex2i(x[1],y[0]);
					glTexCoord2f(u[1],v[1]); glVertex2i(x[1],y[1]);
					glTexCoord2f(u[0],v[1]); glVertex2i(x[0],y[1]);
				}
				if (single) glEnd();

				cur.x += g.xadvance;
			}

			NOINLINE void draw_string(const char * const s, point_t &cursor) const {
				glBegin(GL_QUADS);
				{
					for (uint_t i=0; s[i]; ++i)
						draw_glyph<false>(s[i], cursor);
				}
				glEnd();
			}

			point_t compute_extent(const char * const s) const {
				point_t e(0, headers->line_height);
				for (uint_t i=0; s[i]; ++i)
					e.x += glyphs[lut[int(s[i])]].xadvance;

				return e;
			}
		};

		static font_t	*font;
		static point_t	resolution;


		struct console_t {
			enum { max_entries = 32, max_len = 128 };

			char status_line[max_len];

			struct entry_t {
				float64_t timestamp;
				int len;
				char line[max_len];
			} entries[max_entries];

			int last, count;
			sys::mutex_t gate;

			console_t() : last(max_entries-1), count(0) {
				std::strcpy(status_line, "here be dragons.");
				std::memset(entries, 0, sizeof(entries));
			}


			void set_status(const char * const s) { std::strncpy(status_line, s, max_len); }

			NOINLINE void push(const char *s, const int len) {
				sys::lock_t lock(gate);

				last = (last+1) % max_entries;
				count = ++count > max_entries ? max_entries : count;

				entries[last].timestamp = sys::wallclock::get();
				entries[last].len = len >= max_len ? max_len-1 : len;
				std::memcpy(entries[last].line, s, entries[last].len);
				entries[last].line[entries[last].len] = 0;
			}

			NOINLINE void draw() {
				sys::lock_t lock(gate);

				enum {
					status_line_off_x = 1, status_line_off_y = 0,
					num_displayable_lines = 24
				};
				const int num = count > num_displayable_lines ? num_displayable_lines : count;

				const double
					time = sys::wallclock::get(),
					max_age = 8;

				// find how many active lines we have
				int active = 0;
				while ((active < num) & (time-entries[(uint_t(last)-uint_t(active)) % max_entries].timestamp < max_age))
					++active;


				//BREAKPOINT();
				const int total_height = active * font->headers->line_height;
				const float
					alpha = 1/3.f, //0.25f;
					value = 0.9125f,
					text_color[3] = { value, value, value },
					status_color[3] = { 1,1,1 };

				// darken stuff a bit under
				glDisable(GL_TEXTURE_2D);
				{
					glColor4f(0,0,0, alpha);
					if (active > 0) glRecti(0, resolution.y - total_height, resolution.x, resolution.y);
					glRecti(0, status_line_off_y, resolution.x, status_line_off_y + font->headers->line_height + 1); // status line
				}
				glEnable(GL_TEXTURE_2D);



				if (active > 0) {
					glColor3fv(text_color);
					point_t cursor(1, resolution.y-1);
					uint_t line = last;
					for (int i=0; i<active; ++i) {
						cursor.x = 1; cursor.y -= font->headers->line_height;
						font->draw_string(entries[line].line, cursor);
						line = (line-1) % max_entries;
					}
				}
				{
					glColor3fv(status_color);
					point_t cursor(status_line_off_x,status_line_off_y);
					font->draw_string(status_line, cursor);
				}
			}
		};

		static console_t *console = 0;


		void set_status(const char * const s) { if (console) console->set_status(s); }

		static void draw_string(const char * const s, point_t &cursor) { font->draw_string(s, cursor); }

		void prologue() {
			glViewport(0, 0, resolution.x, resolution.y);
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			gluOrtho2D(0, resolution.x, resolution.y, 0);
			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();

			glAlphaFunc(GL_GREATER, 0);
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			//glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA);
			//glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE_MINUS_SRC_ALPHA);
			glEnable(GL_ALPHA_TEST);
			glEnable(GL_BLEND);

			font->tex->bind();
		}

		void epilogue() {
			if (console) console->draw();

			glDisable(GL_ALPHA_TEST);
			glDisable(GL_BLEND);

			glColor3f(1,1,1);
		}




		struct box_t {
			point_t min, max;

			box_t(point_t a, point_t b) : min(a), max(b) {}
			box_t grow(const int i) const { return box_t(min-i, max+i); }

			void draw() const { glRecti(min.x, min.y, max.x, max.y); }
		};

		static void render_splash(ogl::context_t &ctx) {
			prologue();
			// we'll cheat a bit
			const float aspect = float(resolution.y)/float(resolution.x);
			//enum { virtual_resolution = 64 };
			enum { virtual_resolution = 128 };
			const point_t vres(virtual_resolution, int(float(virtual_resolution)*aspect));
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			gluOrtho2D(0, vres.x, vres.y, 0);
			glMatrixMode(GL_MODELVIEW);

			const float
				f = 0.975f,
				comp = (1-f)*8,
				colors[4][3] =
				{
					{ 0, 0, 0 },
					{ comp, comp, comp },
					{ f, f, f },
					{ 1, 1, 1 }
				};


			const float *black, *gray, *white;
			enum { invert = 0 };
			if (invert) {
				black = colors[3];
				gray = colors[1];
				white = colors[0];
			}
			else {
				black = colors[0];
				gray = colors[2];
				white = colors[3];
			}

			glClearColor(white[0],white[1],white[2],0); ctx.clear();

			const char part1[] = "rad", part2[] = "ius"; //part2[] = "ius®©¤™";

			const point_t
				extent_part1(font->compute_extent(part1)),
				extent_part2(font->compute_extent(part2)),
				extent(extent_part1.x + extent_part2.x, extent_part1.y),
				center(vres/2),
				text_off(center - extent/2),

				demarcation(text_off + point_t(extent_part1.x,0)),
				whole(text_off + extent);

			const int nudge = 1;
			const box_t
				text(text_off, whole),
				box1(text.grow(nudge));
				//box2(box1.grow(nudge));

			glDisable(GL_TEXTURE_2D);
			glColor3fv(black); box1.draw();

			glColor3fv(black); glRecti(text_off.x, text_off.y, demarcation.x, whole.y);
			glColor3fv(gray); glRecti(demarcation.x, text_off.y, whole.x, whole.y);
			glEnable(GL_TEXTURE_2D);

			point_t c(text_off);
			glColor3fv(gray); draw_string(part1, c);
			glColor3fv(black); draw_string(part2, c);
		}
	}


	bool_t bootstrap(ui::window_t &win, ogl::context_t &ctx) {
		ctx.create(win);	// careful, the context is binded to the current thread.

		hud::resolution = point_t(win.inner_width, win.inner_height);
		hud::font = new hud::font_t(rsc::console_font::headers, rsc::console_font::glyphs, rsc::console_font::image);


		glewInit();
		sys::log("OGL: OpenGL 2.0 [%c]\n", glewIsSupported("GL_VERSION_2_0") ? 'X' : ' ');

		const bool_t
			check_glsl =
				glewGetExtension("GL_ARB_fragment_shader") &
				glewGetExtension("GL_ARB_vertex_shader") &
				glewGetExtension("GL_ARB_shader_objects") &
				glewGetExtension("GL_ARB_shading_language_100"),
			check_fbo = glewGetExtension("GL_EXT_framebuffer_object"),
			check_pbo = glewGetExtension("GL_ARB_pixel_buffer_object");

		sys::log("OGL: support for GLSL [%c] FBO [%c] PBO [%c]\n", check_glsl ? 'X' : ' ', check_fbo ? 'X' : ' ', check_pbo ? 'X' : ' ');

		//FIXME:
		#ifdef POKFKZOPFKOWINDOWS
			if (1 & wglewIsSupported("WGL_EXT_swap_control")) {
				sys::log("... switching vsync off.\n");
				wglSwapIntervalEXT(0);
			}
		#endif

		glEnable(GL_TEXTURE_2D);
		glDisable(GL_DEPTH_TEST);
		glDepthMask(0);

		hud::render_splash(ctx);

		ctx.flip(win);
		return true;
	}


}

// avoid nasty bogus over optimization at link time
NOINLINE void hud_log(const char * __restrict const s, const int len) {
	if (!ogl::hud::console)
		ogl::hud::console = new ogl::hud::console_t;

	ogl::hud::console->push(s, len);
}

/*

GL_ARB_fragment_shader
GL_ARB_vertex_shader
GL_ARB_shader_objects
GL_ARB_shading_language
GL_ARB_pixel_buffer_object
*/
