#include <iostream>


#include <GL/glew.h>
//#include "gtk_includes.h"
#include "gtk_win_main.hpp"
//#include "my_gtk_gl_scene_widget.hpp"
#include "boring_parts.hpp"

#include <gdk/x11/gdkglx.h> // X11 specific

// Macro to make things readable in main() function
#define EXIT_IF_FAIL(val, expr) do { \
	if ( ! (expr) ) { \
		std::cerr << "Init error " << val << std::endl; \
		exit(val); \
	} \
} while(0)

// For understanding the event sequences
#define CALL_TRACE do { \
	std::cout << "trace " <<__PRETTY_FUNCTION__ << std::endl; \
} while(0)

int main(int argc, char* argv[]) {
	CALL_TRACE;

	// Initialize GTK
	Gtk::Main gtkKit(argc, argv); // gtk itself
	Gtk::GL::init(argc, argv);    // gtkglextmm

	// Query and print OpenGL version
	int glVersionMajor, glVersionMinor;
	EXIT_IF_FAIL(1, Gdk::GL::query_version(glVersionMajor, glVersionMinor) );
	std::cout << "OpenGL extension version - " << glVersionMajor << "." << glVersionMinor << std::endl;

	// Initialize OpenGL
	Gdk::GL::ConfigMode glMode = Gdk::GL::MODE_RGB | Gdk::GL::MODE_DEPTH | Gdk::GL::MODE_DOUBLE;
	Glib::RefPtr<Gdk::GL::Config> glconfig;
	EXIT_IF_FAIL(2, glconfig=Gdk::GL::Config::create(glMode) );

	// Initialize the OpenGL scene widget (realization came later)
	MyGTKGLSceneWidget glScene(glconfig);

	// Instantiate the GTK app (and realize glScene)
	// Could exit() the program if problem with OpenGL or OpenCL
	GTKWinMain gtkwinmain(glScene);

	// Run the app
	gtkKit.run(gtkwinmain);

	return 0;
}

/* MyGTKGLSceneWidget implementation
	I want to keep interesting code part in this file in natural reading order
*/
MyGTKGLSceneWidget::MyGTKGLSceneWidget(Glib::RefPtr<Gdk::GL::Config> &glconfig) {
	set_gl_capability(glconfig);
	Gdk::EventMask mask = Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK;
	set_events(mask); // The containing window should have those attributes too
	this->camera.rx = 0.0f; this->camera.ry = 0.0f; this->camera.tz = -3.0f;
}

MyGTKGLSceneWidget::~MyGTKGLSceneWidget() { }

void MyGTKGLSceneWidget::on_size_request(Gtk::Requisition* requisition) {
	CALL_TRACE; // Technical stuff : GTK call this to ask the widget minimal size 
 	*requisition = Gtk::Requisition();
	requisition->width = 320; requisition->height = 240;
}

void MyGTKGLSceneWidget::on_realize() {
	CALL_TRACE; // This one runs once at window creation time
	// It's time to setup GL things that don't change on each frame
	Gtk::DrawingArea::on_realize();
	Glib::RefPtr<Gdk::GL::Window> glwindow = get_gl_window();

	// *** OpenGL BEGIN ***
	GLenum gl_res;
	if (!glwindow->gl_begin(get_gl_context())) {
		std::cerr << "Oups : glwindow->gl_begin(get_gl_context())" << std::endl;
		return;
	}

	EXIT_IF_FAIL(3, Gdk::GL::query_gl_extension("GL_ARB_vertex_buffer_object") );
	EXIT_IF_FAIL(4, glewInit() == 0 );
	
	size_t mesh_width=512, mesh_height=512, group_size=256; // TODO : not here

	GLuint gl_vbo=0;
	GLsizeiptr gl_vbo_data_size = mesh_width * mesh_height * sizeof(cl_float4);
	std::cout << "gl_vbo_data_size==" << gl_vbo_data_size << std::endl;

	glGenBuffers(1, &gl_vbo);
	glBindBuffer(GL_ARRAY_BUFFER, gl_vbo);
	glBufferData(GL_ARRAY_BUFFER, gl_vbo_data_size, NULL, GL_DYNAMIC_DRAW);
	gl_res=glGetError();
	if ( gl_res != GL_NO_ERROR ) {
		std::cerr << "glBufferData(). Unable to allocate " << gl_vbo_data_size << "bytes in VRAM" << std::endl;
		std::cerr << gluErrorString(gl_res);
		EXIT_IF_FAIL(5, false);
	}

//#ifdef HAS_OPENCL
//	#ifdef X11
		intptr_t gl_context = (intptr_t)glXGetCurrentContext();
		intptr_t gl_display = (intptr_t)glXGetCurrentDisplay();

		int cl_res = clKit.initCL(gl_display, gl_context, gl_vbo, mesh_width, mesh_height, group_size);
		EXIT_IF_FAIL(cl_res, cl_res==0);

//	std::cerr << "DEBUG : begin initOpenCL()" << std::endl;
//		int cl_res = initOpenCL(gl_display, gl_context, gl_vbo); /* See boring_parts.cpp */
//		EXIT_IF_FAIL(cl_res, cl_res==0);
//	#else
//		#error initOpenCL works only for X11 systems for now
//	#endif
//	}
//#endif


	// Programmatically create rendering lists : opengl will able to replay that efficiently
	GLUquadricObj* qobj = gluNewQuadric();
	gluQuadricDrawStyle(qobj, GLU_FILL);
	glNewList(1, GL_COMPILE);
	//gluSphere(qobj, 1.0, 20, 20);
	gluSphere(qobj, 1.0, 5, 5);
	glEndList();

	// Setup scene envrionnement
	static GLfloat light_diffuse[] = {1.0, 0.0, 0.0, 1.0};
	static GLfloat light_position[] = {1.0, 1.0, 1.0, 0.0};
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_DEPTH_TEST);

	glClearColor(1.0, 1.0, 1.0, 1.0);
	glClearDepth(1.0);
	
	// Projection setup is done at on_configure_event
	// Camera setup (ie initial MODELVIEW matrix) is done at on_expose_event
	
	glwindow->gl_end();
	// *** OpenGL END ***
}

bool MyGTKGLSceneWidget::on_configure_event(GdkEventConfigure* event) {
	CALL_TRACE ; // This one runs mainly when GTK GL Widget is resized
	// See boring_parts.cpp. In short : gluPerspective(60.0, aspect, 0.1, 10.0);
	return updateGLProjectionMatrix(get_gl_context(), get_gl_window(), get_width(), get_height());
}

bool MyGTKGLSceneWidget::on_expose_event(GdkEventExpose* event) {
//	CALL_TRACE ; // This one runs mainly when GTK GL Widget have to be redrawn

	Glib::RefPtr<Gdk::GL::Window> glwindow = get_gl_window();

	// *** OpenGL BEGIN ***
	if (!glwindow->gl_begin(get_gl_context())) {
		std::cerr << "Oups : glwindow->gl_begin(get_gl_context())" << std::endl;
		return false;
	}

	//Camera position update
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.0, 0.0, this->camera.tz);
	glRotatef(this->camera.rx, 1.0, 0.0, 0.0);
	glRotatef(this->camera.ry, 0.0, 1.0, 0.0);

	// Drawing all the stuff
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glCallList(1);


	glwindow->gl_end();
	// *** OpenGL END ***

	glwindow->swap_buffers(); // Display the rendered image

	return true;
}

bool MyGTKGLSceneWidget::on_motion_notify_event (GdkEventMotion *event) {
	return do_mouse_logic(event->type, event->state, event->x, event->y);
}

bool MyGTKGLSceneWidget::on_button_press_event(GdkEventButton *event) {
	return do_mouse_logic(event->type, event->state | 1<<(7+event->button) , event->x, event->y);
}

bool MyGTKGLSceneWidget::on_button_release_event(GdkEventButton *event) {
	return do_mouse_logic(event->type, event->state, event->x, event->y);
}

#define ALL_KEYBOARD_MODIFIERS ( GDK_SHIFT_MASK | GDK_CONTROL_MASK \
	| GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK )

#define MOUSE_CLIC(button, required_modifier_mask, allowed_extra_modifier_mask) if ( \
	type == GDK_BUTTON_RELEASE \
	&& ( state & button ) == button \
	&& ( state & required_modifier_mask ) == required_modifier_mask \
	&& ( ( state & ALL_KEYBOARD_MODIFIERS ) & ~( required_modifier_mask | allowed_extra_modifier_mask ) ) == 0 \
)
#define MOUSE_DOUBLECLIC(state_mask) if ( \
	type == GDK_BUTTON_RELEASE \
	&& ( state & state_mask ) == state_mask \
	&& prev_type == GDK_2BUTTON_PRESS \
)
#define MOUSE_DRAG_START(state_mask) if ( \
	type == GDK_BUTTON_PRESS \
	&& ( state & state_mask ) == state_mask \
)
#define MOUSE_DRAGING(state_mask) if ( \
	( type == GDK_MOTION_NOTIFY ) \
	&& ( state & state_mask ) == state_mask \
)
#define MOUSE_DRAG_END(state_mask) if ( \
	type == GDK_BUTTON_RELEASE \
	&& ( state & state_mask ) == state_mask \
)

bool MyGTKGLSceneWidget::do_mouse_logic(GdkEventType type, guint state, guint x, guint y) {
/*
 * type : the type of the event.
 *	Simple motion : GDK_MOTION_NOTIFY (3)
 *	Simple clic : GDK_BUTTON_PRESS then GDK_BUTTON_RELEASE (4 then 7)
 *	Double clic : GDK_BUTTON_PRESS, GDK_BUTTON_RELEASE, GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_BUTTON_RELEASE (4 7 4 5 7)
 *
 * stat : a bit-mask representing the state of the modifier keys and the pointer buttons.
 * 	GDK_BUTTON1_MASK, ... , GDK_BUTTON5_MASK (mouse buttons)
 * 	GDK_SHIFT_MASK, GDK_LOCK_MASK, GDK_CONTROL_MASK (keyboard standard modifier keys)
 *	GDK_MOD1_MASK, ... (normally MOD1 it is the Alt key)
 * 	GDK_SUPER_MASK, GDK_HYPER_MASK, GDK_META_MASK (extra keybord modifier keys)
 */
	// Static variables to hold previous mouse button event
	static GdkEventType prev_type = GDK_NOTHING;
       	/*static guint prev_state=0; UNUSED FOR NOW */
	static guint drag_x=0, drag_y=0;

	bool redraw=false; // Setting it to true will queue a redraw to the widget (invalidate)

//	std::cout << "event type " << type << " state " << state << " on (" << x << "," << y << ") " << std::endl;

	/* *** BEGIN event filtering *** */ 
	MOUSE_DRAG_START(GDK_BUTTON2_MASK) {
		drag_x=x; drag_y=y;
	}

	MOUSE_DRAGING(GDK_BUTTON2_MASK) {
		float mouse_sensivity = 0.2f;
		gint dx = drag_x - x; // Delta movement (since last event)
		gint dy = drag_y - y; // Not unsigned !
		// Yes dy for camera.rx, and -= operator
		// GTK mouse coords and Opengl are not on the same coords system
		this->camera.rx -= mouse_sensivity * dy;
		this->camera.ry -= mouse_sensivity * dx;
		drag_x = x; drag_y = y;
		redraw=true;
	}

	MOUSE_CLIC(GDK_BUTTON1_MASK, 0, 0) {
		//TODO 
	}

	
	// Demo filters
	MOUSE_CLIC(GDK_BUTTON1_MASK, GDK_SHIFT_MASK, GDK_CONTROL_MASK) {
		std::cout << "Mouse 1 clic with shift or control-shift" << std::endl;
	}
	
	MOUSE_DOUBLECLIC(GDK_BUTTON3_MASK) {
		std::cout << "Mouse 1 double clic" << std::endl;
	}
	/* *** END event filtering *** */


	// Previous button event retention for double-clic filtering
	switch(type) {
		case GDK_BUTTON_PRESS:
		case GDK_BUTTON_RELEASE:
		case GDK_2BUTTON_PRESS:
			prev_type=type;
//			prev_state=state;
			break;
		default:
			break;
	}

	if ( redraw ) queue_draw();
	return true;
}