/*
	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>
*/
/*
	http://cvsweb.freedesktop.org/xorg/xc/programs/glxinfo/glxinfo.c?revision=1.3&view=markup
*/

#include "specifics.h"

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

#include "sys_log.h"
#include "sys_cpu.h"


#include <cstring>
#include <memory>

#define XK_MISCELLANY
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysymdef.h>

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



namespace ui {
	struct opaque_t {
		const char *display_name;
		Display *display;
		int screen;
		Window	root_window, window;
		// GC gc;
	};


	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));
		opq.display_name = display_name;

		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) {
		if (!XInitThreads())
			sys::log("ui::window_t::open: ouch, no support for concurrent Xlib hammering.\n");

		Display *dpy = XOpenDisplay(opq.display_name);
		if (dpy) {
			const int
				screen = DefaultScreen(dpy),
				depth = DefaultDepth(dpy, screen),
				screen_width = DisplayWidth(dpy, screen),
				screen_height = DisplayHeight(dpy, screen);

			Visual *visual = DefaultVisual(dpy, screen);


			/* Initialize window's attribute structure */
			XSetWindowAttributes window_attributes;
			{
				window_attributes.border_pixel = BlackPixel(dpy, screen);
				window_attributes.background_pixel = BlackPixel(dpy, screen);
				window_attributes.backing_store = NotUseful;
			}


			const int
				off_x = (screen_width - width) / 2,
				off_y = (screen_height - height) / 2;

			sys::log("ui::window_t::open: asked for %dx%d, screen(%d, %dx%d) depth %d\n", width, height, screen, screen_width, screen_height, depth);
			// http://www.ac3.edu.au/SGI_Developer/books/XLib_PG/sgi_html/ch03.html
			Window
				root_window = DefaultRootWindow(dpy),
				window = XCreateWindow(
							dpy, root_window,
							off_x, off_y, width, height, 0, depth,
							InputOutput, visual,
							CWBackPixel | CWBorderPixel | CWBackingStore,
							&window_attributes);

			XStoreName(dpy, window, "bootstrap in progress...");

			/* Tell the server to report only keypress-related events */
			XSelectInput(dpy, window, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask);

			/* Initialize window's sizehint definition structure */
			XSizeHints window_sizehints;
			{
				window_sizehints.flags = PPosition | PMinSize | PMaxSize;
				window_sizehints.x = 0;
				window_sizehints.y = 0;
				window_sizehints.min_width = width;
				window_sizehints.max_width = width;
				window_sizehints.min_height = height;
				window_sizehints.max_height = height;
			}
			XSetWMNormalHints(dpy, window, &window_sizehints);

			if (0) {
				Atom wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", false);
				XSetWMProtocols(dpy, window, &wm_delete_window, 1);
			}

			XClearWindow(dpy, window);

			// XSynchronize(dpy, true); debug: synch mode
			XSync(dpy, false);

			opq.display = dpy;
			opq.screen = screen;
			opq.window = window;
			opq.root_window = root_window;
			return true;
		}
		else
			sys::log("ui::window_t::open: couldn't open display '%s'\n", opq.display_name);

		return false;
	}

	void window_t::close() {
		// sync before we quit to see errors.
		XSync(opq.display, false);

		XDestroyWindow(opq.display, opq.window);
		//XUnloadFont(display, font->fid);
		//XFreeGC(display, gc);
		XCloseDisplay(opq.display);
	}

	void window_t::expose() { XMapRaised(opq.display, opq.window); }

	void window_t::set_title(const char *s) {
		XStoreName(opq.display, opq.window, s);
		XSync(opq.display, false);
	}

}

/*
	XUnmapWindow(glxsrv.dpy, glxsrv.windowID);
	glXDestroyContext(glxsrv.dpy, glxsrv.cx);
	XDestroyWindow(glxsrv.dpy, glxsrv.windowID);
	XCloseDisplay(glxsrv.dpy);
*/

// http://216.239.59.104/search?q=cache:_fDAVqR6yKIJ:www.ks.uiuc.edu/Research/vmd/doxygen/OpenGLDisplayDevice_8C-source.html+opengl+x11+init&hl=en&ct=clnk&cd=5
// glXSwapBuffers(glxsrv.dpy, glxsrv.windowID);
namespace ogl {
	struct display_lock_t {
		Display *dpy;
		display_lock_t(Display *d) : dpy(d) { XLockDisplay(dpy); }
		~display_lock_t() { XUnlockDisplay(dpy); }
	};

	// GLXDrawable
	bool_t context_t::create(ui::window_t &win) {
		// not really needed since multithread access is enabled but...
		display_lock_t lock(win.opq.display);

		if (!glXQueryExtension(win.opq.display, 0, 0)) {
			sys::log("ogl::context_t::create: the X server does not support the OpenGL GLX extension.\n");
			return false;
		}

		int conf[] = {
			GLX_DOUBLEBUFFER,
			GLX_RGBA,	//  true or direct color
			//GLX_DEPTH_SIZE, dsize, GLX_SAMPLE_BUFFERS_ARB, 1, GLX_SAMPLES_ARB, ns,
			None
		};
		XVisualInfo *vi = glXChooseVisual(win.opq.display, win.opq.screen, conf);

		if (vi) {
			glrc = glXCreateContext(win.opq.display, vi, 0, true);
			if (!glrc) {
				sys::log("ogl::context_t::create: trying again with an indirect context.\n");
				glrc = glXCreateContext(win.opq.display, vi, 0, false);
			}
			XFree(vi);

			if (glrc) {
				// bind it
				glXMakeCurrent(win.opq.display, win.opq.window, glrc);
				return true;
			}
		}

		return false;
	}

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

	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.
		glXSwapBuffers(win.opq.display, win.opq.window);
		//sys::log("ogl::context_t::flip: someone screwed our rendering context, error %d\n", rc);
	}

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




/*

	Event processing crap.

*/


namespace ui {
	bool_t window_t::process_key(const bool_t keyup, const uint_t key) {
		sys::lock_t lock(inputs.mutex);

		const float value = !keyup ? 1.f : 0.f;
		switch (key) {
			// axis
			case XK_Left:		inputs.axis[1] = -value; return false; // cam 'x'
			case XK_Right:		inputs.axis[1] = +value; return false;
			case XK_Down:		inputs.axis[0] = -value; return false;	// cam 'y'
			case XK_Up:			inputs.axis[0] = +value; return false;
			case XK_Delete:		inputs.axis[2] = -value; return false;	// cam 'z'
			case XK_Page_Down:	inputs.axis[2] = +value; return false;

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


			// modifiers,
			case XK_Shift_L:
			case XK_Shift_R:	inputs.modifiers.shift = !keyup; return false;
			case XK_Control_L:
			case XK_Control_R: inputs.modifiers.control = !keyup; return false;

			// only really catches XK_Alt_L atm :)
			case XK_Meta_L:
			case XK_Meta_R:
			case XK_Alt_L:
			case XK_Alt_R: inputs.modifiers.menu = !keyup; return false;


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

			default: 			return true;
		}
	}

	// http://users.actcom.co.il/~choo/lupg/tutorials/xlib-programming/xlib-programming.html
	// http://www.ac3.edu.au/SGI_Developer/books/XLib_PG/sgi_html/ch06.html#S2-1002-6-5
	int window_t::process() {
		//while (XPending(opq.display)) {
		while (true) {
			XEvent xevent;
			XNextEvent(opq.display, &xevent);	// sit on it.

			switch (xevent.type) {
				// XButtonEvent
				case ButtonPress:
					{
						enum { LEFT_CLICK = 1, RIGHT_CLICK = 4, MIDDLE_CLICK = 7 };
						// X11 sends 1(left),2(mid),3(right)
						// xevent.xbutton.button, unsigned...
						const int
							remap[4] = { 0,LEFT_CLICK,MIDDLE_CLICK,RIGHT_CLICK },
							button = remap[ xevent.xbutton.button > 3 ? 0 : xevent.xbutton.button], // remap
							state = xevent.xbutton.state,
							x = xevent.xbutton.x, y = xevent.xbutton.y;
						// sys::log("button %d (originaly %d) state %d @ %dx%d.\n",button,xevent.xbutton.button,state,x,y);
						process_click(button, x, y);
					}
					break;

				// see <X11/keysymdef.h>, /usr/X11R6/include/X11/keysymdef.h
				case KeyPress:
				case KeyRelease:
					{
						// man XKeyEvent
						KeySym keysym = XLookupKeysym(&xevent.xkey, 0);
						union {
							uint16_t bits;
							struct { uchar_t low, high; };
						} vk;
						vk.bits = keysym;

						// sys::log("type 0x%x state 0x%x, keysym 0x%x { low %x high %x }.\n", xevent.xkey.type, xevent.xkey.state, keysym, vk.low, vk.high);
						// "function" key?
						// yep, they go there; we should really unify all that crap.
						// if (vk.high == 0xFFu)
						if (process_key(xevent.type == KeyRelease, vk.bits) & (xevent.type == KeyPress)) {
							char keybuf[16];
							const int numchar = XLookupString(&xevent.xkey, keybuf, sizeof (keybuf), &keysym, 0);
							if (numchar)
								process_key(keybuf[0]);
						}
					}
					break;
			}
		}

		sys::log("\n\nui::window_t::process: MAYDAY! MAYDAY! about to exit!\n\n\n");
		return false;
	}
}
