/*
	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 <memory>

#include "driver.h"
#include <GL/gl.h>
#include <GL/glu.h>

#include "sys_log.h"

#ifdef __MSVC__
	// annoying as usual
	#pragma warning(disable: 4311)	// 'reinterpret_cast' : pointer truncation from 'ui::window_t *const ' to 'long'
	#pragma warning(disable: 4312)	// 'type cast' : conversion from 'const long' to 'ui::window_t *' of greater size
#endif

namespace ui {
	struct opaque_t {
		HWND		wnd;
		HINSTANCE	hinstance;
	};

	static const wchar_t window_class_name[] = L"ogl-windowing";
	LRESULT	CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

	window_t::window_t(const char *display_name, const point_t &res)
		: thread_t(this), inner_width(res.x), inner_height(res.y), opq(*new opaque_t)
	{
		std::memset(&opq, 0, sizeof(opaque_t));

		sys::mutex_t::make(inputs.mutex);
		// cough.
		valid = sys::thread_t::spawn(this);
	}

	window_t::~window_t() {
		opaque_t *p = &opq;
		delete p;
	}

	bool_t window_t::open(const uint_t width, const uint_t height) {
		RECT rect;
		std::memset(&rect, 0, sizeof(RECT));
		rect.bottom = height;
		rect.right = width;


		should_bail	= false;
		const int w = width, h = height;

		opq.wnd	= 0;
		opq.hinstance = GetModuleHandle(0);

		WNDCLASS wc;
		std::memset(&wc, 0, sizeof(wc));

		//CS_HREDRAW | CS_VREDRAW |  // CS_HREDRAW | CS_VREDRAW | CS_OWNDC;	// Redraw On Size, And Own DC For Window.
		wc.style = CS_OWNDC; // | CS_DROPSHADOW; <-- hah, really really really buggy.
		wc.lpfnWndProc	= (WNDPROC) WndProc;
		wc.hInstance	= opq.hinstance;
		wc.hIcon		= LoadIcon(0, IDI_EXCLAMATION); //LoadIcon(NULL, IDI_WINLOGO);
		wc.hCursor		= LoadCursor(0, IDC_ARROW);
		wc.lpszClassName= window_class_name;


		if (!RegisterClass(&wc))
			fatal("ogl::window_t::open -- failed to register.");

		// should not be exposed right after creation.
		const uint_t
			style = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, // WS_OVERLAPPEDWINDOW,
			//exstyle = WS_EX_APPWINDOW | WS_EX_NOACTIVATE; //  | WS_EX_WINDOWEDGE;
			exstyle = WS_EX_APPWINDOW;
		AdjustWindowRectEx((RECT*)&rect,style,false,exstyle);

		// try to stick it side by side
		const int
			// CW_USEDEFAULT,CW_USEDEFAULT
			dx = rect.right-rect.left,
			dy = rect.bottom-rect.top,
			smcx = GetSystemMetrics(SM_CXSCREEN),
			smcy = GetSystemMetrics(SM_CYSCREEN),
			px = dx >= smcx ? 0 : (smcx - dx) >> 1,
			py = dy >= smcy ? 0 : (smcy - dy) >> 1;

		opq.wnd = CreateWindowEx(exstyle, window_class_name, L"warming up...",
			style|WS_CLIPSIBLINGS|WS_CLIPCHILDREN,
			px,py,
			dx, dy,
			0,0,
			opq.hinstance, 0);
		if (opq.wnd == 0)
			fatal("ogl::window_t::open -- failed to open da window.");

		// That way the retarded WndProc will refer to this instance. Silly.
		SetWindowLongPtr(opq.wnd, GWL_USERDATA, reinterpret_cast<long>(this));

		return true;
	}


	void window_t::expose() {
		ShowWindow(opq.wnd, SW_SHOW);
		SetForegroundWindow(opq.wnd);
		SetFocus(opq.wnd);
	}

	void window_t::close() {
		//FIX: ReleaseDC(wnd,dc);
		DestroyWindow(opq.wnd);
		UnregisterClass(window_class_name, opq.hinstance);
	}

	void window_t::set_title(const char *s)		{ SetWindowTextA(opq.wnd, s); }

	int window_t::process() {
		/*
			That message pump is uterly stupid & borked,
			horrible stuff happens behind our back (ie when moving the window) so
			we can't hold the lock when handling a serie of events, ie
			GetMessage / wait
			lock
			do
				Translate/DispatchMessage
			while PeekMessage
			Plus our thread yields even with PM_NOYIELD. A mess.
		*/
		int rc = - 1;
		while (true) {
			MSG message;
			// there's a subtle difference between GetMessage(..., wnd, ...) and GetMessage(..., 0, ....)
			// the later will also catch thread specific messages (as opposed to thread *and* window specific).
			while ((rc = GetMessage(&message, 0, 0, 0)) > 0) {
				TranslateMessage(&message);
				DispatchMessage(&message);
				if (should_bail) goto gniii; // eh
			}
		}
	gniii:
		close();
		bail(rc < 0 ? "screwed message pump" : "user request", 0);
		return 0;
	}

	// handling of "immediate" keys
	bool_t window_t::process_key(const bool_t keyup, const uint_t key) {
		sys::lock_t lock(inputs.mutex);

		// sys::log("keymap: 0x%4x %c\n", key, keyup ? '^' : '_');
		const bool_t keydown = !keyup;
		const float value = keydown ? 1.f : 0.f;
		switch (key) {
			// axis
			case VK_LEFT:		inputs.axis[1] = -value; return false; // cam 'x'
			case VK_RIGHT:		inputs.axis[1] = +value; return false;
			case VK_DOWN:		inputs.axis[0] = -value; return false;	// cam 'y'
			case VK_UP:			inputs.axis[0] = +value; return false;
			case VK_DELETE:		inputs.axis[2] = -value; return false;	// cam 'z'
			case VK_NEXT:		inputs.axis[2] = +value; return false;

			case VK_INSERT:		inputs.axis[3] = -value; return false;	// fov
			case VK_PRIOR:		inputs.axis[3] = +value; return false;

			//case VK_HOME:		inputs.axis[3] = keyup ? 0.f : -1.f; break;
			//case VK_END:		inputs.axis[3] = keyup ? 0.f : +1.f; break;


			// modifiers,
			// on doze, only GetAsyncKeyState() distinguishes between left & right versions
			case VK_SHIFT:		inputs.modifiers.shift = keydown; return false;
			case VK_CONTROL:	inputs.modifiers.control = keydown; return false;
			case VK_MENU:		inputs.modifiers.menu = keydown; return false;

			case VK_ESCAPE: 	bail("escaped", 0); return false;

			default: return true;
		}
	}

	// careful, the ptr wont be available right from the start.
	// doh, this article could have saved me quite some headscratching.
	// http://www.rpi.edu/~pudeyo/articles/wndproc/
	LRESULT CALLBACK WndProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) {
		const long ptr = GetWindowLongPtr(wnd, GWL_USERDATA);
		if (ptr) {
			window_t &win(*((window_t *) ptr));

			switch (msg) {
				case WM_CLOSE:			win.should_bail = true; return 0;	// not serialized because no one care.

				/*
					Serialized,
					Seems that there's a race cond of some sort with high fps (>2000) on
					small window, certainly because of re-entrancy issues:
					lock already acquired and that stupid win32 calling us again from somewhere else
					- this routine gets called both from the Dispatch call and directly from win32 -
					I don't give a flying fuck, that's m$ problem. That thing is already a mess to deal with.
					If that doesn't suit you, detect & kludge. Or drop some more barriers around.
				*/
				case WM_KEYDOWN:
				case WM_KEYUP:			win.process_key(msg == WM_KEYDOWN ? 0 : 1, uint_t(wparam)); return 0;

				case WM_CHAR:			win.process_key(uint_t(wparam)); return 0;

				case WM_LBUTTONDOWN:
				case WM_RBUTTONDOWN:
				case WM_MBUTTONDOWN:	win.process_click(msg-WM_LBUTTONDOWN, LOWORD(lparam), HIWORD(lparam)); return 0;

				default: break;
			}
		}

		return DefWindowProc(wnd, msg, wparam, lparam);
	}
}

namespace ogl {
	bool_t context_t::create(ui::window_t &win) {
		enum { fb_bits = 24 };

		static	PIXELFORMATDESCRIPTOR pfd= {
			sizeof(PIXELFORMATDESCRIPTOR),	// Size Of This Pixel Format Descriptor
			1,						// Version Number
			PFD_DRAW_TO_WINDOW |	// Format Must Support Window
			PFD_SUPPORT_OPENGL |	// Format Must Support OpenGL
			PFD_DOUBLEBUFFER,		// Must Support Double Buffering
			PFD_TYPE_RGBA,			// Request An RGBA Format
			fb_bits,				// Select Our Color Depth
			0, 0, 0, 0, 0, 0,		// Color Bits Ignored
			0,						// No Alpha Buffer
			0,						// Shift Bit Ignored
			0,						// No Accumulation Buffer
			0, 0, 0, 0,				// Accumulation Bits Ignored
			16,						// 16Bit Z-Buffer (Depth Buffer)
			0,						// No Stencil Buffer
			0,						// No Auxiliary Buffer
			PFD_MAIN_PLANE,			// Main Drawing Layer
			0,						// Reserved
			0, 0, 0					// Layer Masks Ignored
		};

		dc = GetDC(win.opq.wnd);
		const int pf = ChoosePixelFormat(dc, &pfd);
		if (!SetPixelFormat(dc, pf, &pfd))
			fatal("ogl::oglw_t::open -- failed to SetPixelFormat.");

		glrc = wglCreateContext(dc);
		if (!wglMakeCurrent(dc,glrc))
			fatal("ogl::oglw_t::open -- failed to make a gl context.");

		return true;
	}

	void context_t::destroy(ui::window_t &win) {
		wglMakeCurrent(0,0);
		wglDeleteContext(glrc);
	}

	void context_t::clear() {
		// glClearColor(1,0,1, 1);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}

	void context_t::flip(ui::window_t &win) {
		// there's a race condition where it seems our context gets screwed
		// it's hard to reproduce and sadly it's not caught by that code. meh.
		if (!SwapBuffers(dc)) {
			const int rc = GetLastError();
			sys::log("ogl::context_t::flip: someone screwed our rendering context, error %d\n", rc);
		}
	}
}
