#include <iostream>

#include "gpudataviz.h"
#include "boring_parts.h"
#include "gtk_win_main.h"
#include "my_gtk_gl_scene_widget.h"

// 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;

	int meshWidth=64, meshHeight=64; // TODO : those vars should not be hardcoded

	// 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 OpenCL (if available)
	EXIT_IF_FAIL(3, initLibs()==0 ); // See boring_parts.cc

	// Initialize host work memory (array for all the vertex coordinates computed by OpenCL and displayed by OpenGL)
	cl_float4 *hostWorkMem = (cl_float4 *) calloc(meshWidth * meshHeight, sizeof(cl_float4));
	EXIT_IF_FAIL(4, hostWorkMem);

	// Initialize the OpenGL scene
	MyGTKGLSceneWidget glScene(glconfig);

	// Instantiate and run the GTK app
	GTKWinMain gtkwinmain(glScene);
	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
}

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 ***
	if (!glwindow->gl_begin(get_gl_context())) {
		std::cerr << "Oups : glwindow->gl_begin(get_gl_context())" << std::endl;
		return;
	}

	// 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);
	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.cc. 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();
	gluLookAt(0.0, 0.0, 3.0,
			0.0, 0.0, 0.0,
			0.0, 1.0, 0.0);
	glTranslatef(0.0, 0.0, -3.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, 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 KEYBOARD_MODIFIERS ( \
	GDK_SHIFT_MASK | GDK_LOCK_MASK | GDK_CONTROL_MASK \
	| GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK \
)
/* | GDK_MOD1_MASK | GDK_MOD2_MASK | GDK_MOD3_MASK | GDK_MOD4_MASK |GDK_MOD5_MASK \ */

#define MOUSE_CLIC(button, allowed_modifier_mask) if ( \
	type == GDK_BUTTON_RELEASE \
	&& ( state & button ) == button \
	&& ( ( state & KEYBOARD_MODIFIERS ) & ~allowed_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_DRAGING(state_mask) if ( \
	( type == GDK_BUTTON_PRESS || 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 GdkEventType prev_type = GDK_NOTHING;
//	static guint          prev_state= 0;   // Empty mask
//
	std::cout << "event type " << type << " state " << state << " on (" << x << "," << y << ") " << std::endl;
//	std::cout << "DEBUG ( state & KEYBOARD_MODIFIERS ) == " << ( state & KEYBOARD_MODIFIERS ) << std::endl;


	/* *** BEGIN event filtering *** */ 
	MOUSE_DOUBLECLIC(GDK_BUTTON1_MASK) {
		std::cout << "Mouse 1 double clic" << std::endl;
	}

	MOUSE_DRAGING(GDK_BUTTON2_MASK) {
		std::cout << "Mouse 2 dragging" << " on (" << x << "," << y << ") " << std::endl;
	}

	MOUSE_CLIC(GDK_BUTTON3_MASK, 0) {
		std::cout << "Mouse 3 clic without any key modifier" << std::endl;
	}
	
	//FIXME : this is buggy !!!
	MOUSE_CLIC(GDK_BUTTON3_MASK, GDK_SHIFT_MASK) {
		std::cout << "Mouse 3 clic with shift or control" << 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;
	}
	return true;
}