/*
 * Copyright (C) 2003, 2012 Clemens Fuchslocher
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include <cstdlib>
#include <iostream>

#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gtk/gtkgl.h>

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

#include "SceneGraph.h"
#include "ToolkitGtk.h"


namespace nsToolkit {

ToolkitGtk::ToolkitGtk(int argc, char *argv[]) : Toolkit(argc, argv)
{
	DEBUG_MESSAGE("ToolkitGtk::ToolkitGtk()")

	gtk_init(&argc, &argv);
	gtk_gl_init(&argc, &argv);

	// Idle
	this->idleId = 0;

	// window
	this->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	g_signal_connect(G_OBJECT(this->window), "delete-event", G_CALLBACK(gtk_main_quit), NULL);
	g_signal_connect(G_OBJECT(this->window), "map-event", G_CALLBACK(ToolkitGtk::event), (gpointer *) this);
	g_signal_connect(G_OBJECT(this->window), "unmap-event", G_CALLBACK(ToolkitGtk::event), (gpointer *) this);

	// drawingArea
	this->drawingArea = gtk_drawing_area_new();

	GdkGLConfig *glConfig = gdk_gl_config_new_by_mode((GdkGLConfigMode) (GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE));
	if (glConfig == NULL)
	{
		std::cerr << __FILE__ << '[' << __LINE__ << "]: ToolkitGtk::ToolkitGtk()" << std::endl;
		std::exit(TOOLKIT_KO);
	}

	gtk_widget_set_gl_capability(this->drawingArea, glConfig, NULL, TRUE, GDK_GL_RGBA_TYPE);

	gtk_widget_add_events(this->drawingArea,
		GDK_BUTTON_PRESS_MASK |
		GDK_BUTTON_RELEASE_MASK |
		GDK_BUTTON1_MOTION_MASK |
		GDK_BUTTON2_MOTION_MASK |
		GDK_BUTTON3_MOTION_MASK |
		GDK_VISIBILITY_NOTIFY_MASK);

	GTK_WIDGET_SET_FLAGS(this->drawingArea, GTK_CAN_FOCUS);

	g_signal_connect(G_OBJECT(this->drawingArea), "realize", G_CALLBACK(ToolkitGtk::realize), (gpointer *) this);
	g_signal_connect(G_OBJECT(this->drawingArea), "unrealize", G_CALLBACK(ToolkitGtk::unrealize), (gpointer *) this);
	g_signal_connect(G_OBJECT(this->drawingArea), "configure-event", G_CALLBACK(ToolkitGtk::event), (gpointer *) this);
	g_signal_connect(G_OBJECT(this->drawingArea), "expose-event", G_CALLBACK(ToolkitGtk::event), (gpointer *) this);
	g_signal_connect(G_OBJECT(this->drawingArea), "visibility-notify-event", G_CALLBACK(ToolkitGtk::event), (gpointer *) this);
	g_signal_connect(G_OBJECT(this->drawingArea), "key-press-event", G_CALLBACK(ToolkitGtk::event), (gpointer *) this);
	g_signal_connect(G_OBJECT(this->drawingArea), "motion-notify-event", G_CALLBACK(ToolkitGtk::event), (gpointer *) this);
	g_signal_connect(G_OBJECT(this->drawingArea), "button-press-event", G_CALLBACK(ToolkitGtk::event), (gpointer *) this);
	g_signal_connect(G_OBJECT(this->drawingArea), "button-release-event", G_CALLBACK(ToolkitGtk::event), (gpointer *) this);
}


void ToolkitGtk::performInit(void)
{
	DEBUG_MESSAGE("ToolkitGtk::performInit()")

	std::cerr << __FILE__ << '[' << __LINE__ << "]: ToolkitGtk::performInit() should be implemented in the derived class." << std::endl;

	gtk_window_set_title(GTK_WINDOW(this->window), "ToolkitGtk");
	gtk_container_add(GTK_CONTAINER(this->window), this->drawingArea);
}


void ToolkitGtk::performAction(void)
{
	DEBUG_MESSAGE("ToolkitGtk::performAction()")

	gtk_widget_show_all(this->window);
	gtk_main();
}


void ToolkitGtk::swapBuffers(void)
{
	DEBUG_MESSAGE("ToolkitGtk::swapBuffers()")

	gdk_gl_drawable_swap_buffers(gtk_widget_get_gl_drawable(this->drawingArea));
}


void ToolkitGtk::redrawScene(void)
{
	DEBUG_MESSAGE("ToolkitGtk::redrawScene()")

	gtk_widget_queue_draw(this->drawingArea);
}


bool ToolkitGtk::beginGLExecution(void)
{
	DEBUG_MESSAGE("ToolkitGtk::beginGLExecution()")

	GdkGLContext *glcontext = gtk_widget_get_gl_context(this->drawingArea);
	GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(this->drawingArea);

	if (!gdk_gl_drawable_gl_begin(gldrawable, glcontext))
	{
		std::cerr << __FILE__ << '[' << __LINE__ << "]: gdk_gl_drawable_gl_begin()" << std::endl;
		return false;
	}

	return true;
}


void ToolkitGtk::endGLExececution(void)
{
	DEBUG_MESSAGE("ToolkitGtk::endGLExececution()")

	GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(this->drawingArea);

	/*
	 * Removing the following line would result in a lot
	 * of GL errors (glGetError() != GL_NO_ERROR) in the
	 * Win32 version. :-/
	 */
#ifdef __MINGW32__
	gdk_gl_drawable_wait_gl(gldrawable);
#endif
	gdk_gl_drawable_gl_end(gldrawable);
}


void ToolkitGtk::enableIdleEvent(void)
{
	DEBUG_MESSAGE("ToolkitGtk::enableIdleEvent()")

	if (idleId == 0)
	{
		// Don't use GDK_PRIORITY_REDRAW here.
		this->idleId = gtk_idle_add_priority(G_PRIORITY_DEFAULT_IDLE, ToolkitGtk::idle, (gpointer *) this);
	}
}


void ToolkitGtk::disableIdleEvent(void)
{
	DEBUG_MESSAGE("ToolkitGtk::disableIdleEvent()")

	if (idleId != 0)
	{
		gtk_idle_remove	(this->idleId);
		this->idleId = 0;
	}
}


int ToolkitGtk::timeOutAdd(int interval, GtkFunction callback)
{
	DEBUG_MESSAGE("ToolkitGtk::timeOutAdd()")

	return gtk_timeout_add(interval, callback, (gpointer *) this);
}


void ToolkitGtk::timeOutRemove(int timeOutHandler)
{
	DEBUG_MESSAGE("ToolkitGtk::timeOutRemove()")

	gtk_timeout_remove(timeOutHandler);
}


GtkWidget *ToolkitGtk::getWindow(void)
{
	DEBUG_MESSAGE("ToolkitGtk::getWindow()")

	return this->window;
}


GtkWidget *ToolkitGtk::getDrawingArea(void)
{
	DEBUG_MESSAGE("ToolkitGtk::getDrawingArea()")

	return this->drawingArea;
}


void ToolkitGtk::realize(GtkWidget *widget, gpointer toolkit)
{
	DEBUG_MESSAGE("ToolkitGtk::realize()")

	if (((ToolkitGtk *) toolkit)->beginGLExecution() == false)
	{
		return;
	}

	((ToolkitGtk *) toolkit)->eventRealize();
	GL_ERROR_CHECK
	((ToolkitGtk *) toolkit)->endGLExececution();
}


void ToolkitGtk::unrealize(GtkWidget *widget, gpointer toolkit)
{
	DEBUG_MESSAGE("ToolkitGtk::unrealize()")

	if (((ToolkitGtk *) toolkit)->beginGLExecution() == false)
	{
		return;
	}

	((ToolkitGtk *) toolkit)->eventUnrealize();
	GL_ERROR_CHECK
	((ToolkitGtk *) toolkit)->endGLExececution();
}


gboolean ToolkitGtk::event(GtkWidget *widget, GdkEvent *event, gpointer toolkit)
{
	DEBUG_MESSAGE("ToolkitGtk::event()")

	if (((ToolkitGtk *) toolkit)->beginGLExecution() == false)
	{
		return false;
	}

	bool ret = true;
	switch (event->type)
	{
		// Generated when a window size or position has changed.
		case GDK_CONFIGURE:
		{
			ret = ((ToolkitGtk *) toolkit)->eventConfigure(event);
			break;
		}

		// Generated when a window has been mapped.
		case GDK_MAP:
		{
			ret = ((ToolkitGtk *) toolkit)->eventMap(event);
			break;
		}

		// Generated when a window has been unmapped.
		case GDK_UNMAP:
		{
			ret = ((ToolkitGtk *) toolkit)->eventUnmap(event);
			break;
		}

		// Generated when the window visibility status has changed.
		case GDK_VISIBILITY_NOTIFY:
		{
			ret = ((ToolkitGtk *) toolkit)->eventVisibilityNotify(event);
			break;
		}

		// Generated when all or part of a window becomes visible and needs to be redrawn.
		case GDK_EXPOSE:
		{
			ret = ((ToolkitGtk *) toolkit)->eventExpose(event);
			break;
		}

		// Generated when a key has been pressed.
		case GDK_KEY_PRESS:
		{
			ret = ((ToolkitGtk *) toolkit)->eventKeyPress(event);
			break;
		}

		// Generated when the pointer moves.
		case GDK_MOTION_NOTIFY:
		{
			ret = ((ToolkitGtk *) toolkit)->eventMotionNotify(event);
			break;
		}

		// Generated when a mouse button has been pressed.
		case GDK_BUTTON_PRESS:
		case GDK_2BUTTON_PRESS:
		case GDK_3BUTTON_PRESS:
		{
			ret = ((ToolkitGtk *) toolkit)->eventButtonPress(event);
			break;
		}

		// Generated when a mouse button has been released.
		case GDK_BUTTON_RELEASE:
		{
			ret = ((ToolkitGtk *) toolkit)->eventButtonRelease(event);
			break;
		}

		default:
		{
			std::cerr << __FILE__ << '[' << __LINE__ << "]: ToolkitGtk::event(): Unknown event!" << std::endl;
		}
	}

	GL_ERROR_CHECK

	((ToolkitGtk *) toolkit)->endGLExececution();

	return ret;
}


gboolean ToolkitGtk::idle(gpointer toolkit)
{
	DEBUG_MESSAGE("ToolkitGtk::idle()")

	if (((ToolkitGtk *) toolkit)->beginGLExecution() == false)
	{
		return true;
	}

	((ToolkitGtk *) toolkit)->eventIdle();
	GL_ERROR_CHECK
	((ToolkitGtk *) toolkit)->endGLExececution();

	return true;
}

}

