/* -*- mia-c++ -*-
 *
 * This file is part of viewitgui - a library and program for the
 * visualization of 3D data sets. 
 *
 * Copyright (c) Leipzig, Madrid 1999-2013 Mirco Hellmann, Gert Wollny
 *
 * viewitgui 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 3 of the License, or
 * (at your option) any later version.
 *
 * viewitgui 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 viewitgui; if not, see <http://www.gnu.org/licenses/>.
 */


#include "config.h"
#include "WinScene.hh"
#include "userini.hh"
#include <viewit/glviewer.hh>

#define SOURCE_MESH "SourceMesh"
#define REF_MESH "ReferenceMesh"

extern SignalHandler	*viewit_signal_handler;
extern XMLini		*inifile;
extern ViewitObjects	*vobjects;
extern CEventQueue	*pending_events;

///////////////////////////////////////////////////////////////////////////////
//
// OpenGL frame buffer configuration utilities.
//
///////////////////////////////////////////////////////////////////////////////

//
// Print a configuration attribute.
//
void GLConfigUtil::print_gl_attrib(const Glib::RefPtr<const Gdk::GL::Config>& glconfig,
                                   const char* attrib_str,
                                   int attrib,
                                   bool is_boolean, std::ostream &bstream)
{
	int value;
  
	if (glconfig->get_attrib(attrib, value)) {
		bstream << attrib_str << " = ";
		if 
			(is_boolean) bstream << (value == true ? "true" : "false") << std::endl;
		else
			bstream << value << std::endl;
	} else {
		bstream << "*** Cannot get "
			<< attrib_str
			<< " attribute value\n";
	}
}

//
// Print configuration attributes.
//
void GLConfigUtil::examine_gl_attrib(const Glib::RefPtr<const Gdk::GL::Config>& glconfig, std::ostream &bstream)
{
	bstream << "\nOpenGL visual configurations :\n\n";

	bstream << "glconfig->is_rgba() = "
		<< (glconfig->is_rgba() ? "true" : "false")
		<< std::endl;
	bstream << "glconfig->is_double_buffered() = "
		<< (glconfig->is_double_buffered() ? "true" : "false")
		<< std::endl;
	bstream << "glconfig->is_stereo() = "
		<< (glconfig->is_stereo() ? "true" : "false")
		<< std::endl;
	bstream << "glconfig->has_alpha() = "
		<< (glconfig->has_alpha() ? "true" : "false")
		<< std::endl;
	bstream << "glconfig->has_depth_buffer() = "
		<< (glconfig->has_depth_buffer() ? "true" : "false")
		<< std::endl;
	bstream << "glconfig->has_stencil_buffer() = "
		<< (glconfig->has_stencil_buffer() ? "true" : "false")
		<< std::endl;
	bstream << "glconfig->has_accum_buffer() = "
		<< (glconfig->has_accum_buffer() ? "true" : "false")
		<< std::endl;

	bstream << std::endl;

	print_gl_attrib(glconfig, "Gdk::GL::USE_GL",           Gdk::GL::USE_GL,           true,  bstream);
	print_gl_attrib(glconfig, "Gdk::GL::BUFFER_SIZE",      Gdk::GL::BUFFER_SIZE,      false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::LEVEL",            Gdk::GL::LEVEL,            false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::RGBA",             Gdk::GL::RGBA,             true,  bstream);
	print_gl_attrib(glconfig, "Gdk::GL::DOUBLEBUFFER",     Gdk::GL::DOUBLEBUFFER,     true,  bstream);
	print_gl_attrib(glconfig, "Gdk::GL::STEREO",           Gdk::GL::STEREO,           true,  bstream);
	print_gl_attrib(glconfig, "Gdk::GL::AUX_BUFFERS",      Gdk::GL::AUX_BUFFERS,      false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::RED_SIZE",         Gdk::GL::RED_SIZE,         false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::GREEN_SIZE",       Gdk::GL::GREEN_SIZE,       false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::BLUE_SIZE",        Gdk::GL::BLUE_SIZE,        false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::ALPHA_SIZE",       Gdk::GL::ALPHA_SIZE,       false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::DEPTH_SIZE",       Gdk::GL::DEPTH_SIZE,       false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::STENCIL_SIZE",     Gdk::GL::STENCIL_SIZE,     false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::ACCUM_RED_SIZE",   Gdk::GL::ACCUM_RED_SIZE,   false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::ACCUM_GREEN_SIZE", Gdk::GL::ACCUM_GREEN_SIZE, false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::ACCUM_BLUE_SIZE",  Gdk::GL::ACCUM_BLUE_SIZE,  false, bstream);
	print_gl_attrib(glconfig, "Gdk::GL::ACCUM_ALPHA_SIZE", Gdk::GL::ACCUM_ALPHA_SIZE, false, bstream);

	bstream << std::endl;
}


///////////////////////////////////////////////////////////////////////////////
//
// Simple OpenGL scene.
//
///////////////////////////////////////////////////////////////////////////////


SimpleGLScene::SimpleGLScene(const Glib::RefPtr< Gdk::Screen > & screen, PGLViewer glview):
	m_glview(glview)
{
	//
	// Configure OpenGL-capable visual.
	//

	if (GL_init(screen)) {
		Gtk::MessageDialog dialog("Cannot find any OpenGL-capable visual", true, Gtk::MESSAGE_ERROR, (Gtk::ButtonsType)(Gtk::BUTTONS_OK));
		dialog.run();	
		Gtk::Main::quit();
		return;
	}
	//
	// Set OpenGL-capability to the widget.
	//

	set_gl_capability(glconfig);
	
	//
	// Add events.
	//
	add_events(Gdk::BUTTON1_MOTION_MASK    |
		   Gdk::BUTTON2_MOTION_MASK    |
		   Gdk::BUTTON3_MOTION_MASK    |
		   Gdk::BUTTON_PRESS_MASK      |
		   Gdk::BUTTON_RELEASE_MASK    |
		   Gdk::POINTER_MOTION_MASK    |
		   Gdk::VISIBILITY_NOTIFY_MASK);
	
	
	// init alpha shapes
	vobjects->alpha = new T2DAlphaSphere(32);
}


SimpleGLScene::~SimpleGLScene()
{
	if (vobjects->alpha) delete vobjects->alpha;
}

//
// initialize OpenGL
//
bool SimpleGLScene::GL_init(const Glib::RefPtr< Gdk::Screen > & screen)
{

	std::stringstream ss;
	std::string temp;
	
	
	// Try double-buffered visual
	glconfig = Gdk::GL::Config::create(Gdk::GL::MODE_RGB    |
					   Gdk::GL::MODE_DEPTH  |
					   Gdk::GL::MODE_DOUBLE);
	
	if (! (glconfig) ) {
		ss << "*** Cannot find the double-buffered visual.\n" << "*** Trying single-buffered visual.\n";
		
		// Try single-buffered visual
		glconfig = Gdk::GL::Config::create(screen, Gdk::GL::MODE_RGB   |
						   Gdk::GL::MODE_DEPTH);
		if (! (glconfig)) {
			ss << "*** Cannot find any OpenGL-capable visual.\n";
			ss.str(temp);
			viewit_signal_handler->WinStatus_append_text().emit(temp);
			return true;
		}
	}

	// Query OpenGL extension version.
	int major, minor;
	Gdk::GL::query_version(major, minor);
	ss << "OpenGL extension version - " << major << "." << minor << std::endl;

	// print frame buffer attributes.
	GLConfigUtil::examine_gl_attrib(glconfig,ss);
	
	temp = ss.str();
	temp += "\n";

	viewit_signal_handler->WinStatus_append_text().emit(temp);
	std::cout << temp << std::flush;
	
	return false;
}

void SimpleGLScene::on_realize()
{
	// We need to call the base on_realize()
	Gtk::GL::DrawingArea::on_realize();
	
	//
	// Get GL::Drawable.
	//
	Glib::RefPtr<Gdk::GL::Drawable> gldrawable = get_gl_drawable();
    

	// *** OpenGL BEGIN ***
	if (!gldrawable->gl_begin(get_gl_context()))
		return;

	m_glview->gl_attach();
	vobjects->alpha->gl_attach();
	
	gldrawable->gl_end();
	// *** OpenGL END ***

	viewit_signal_handler->WinMain_objectmap_changed().emit();
}

void SimpleGLScene::on_unrealize()
{
	Gtk::GL::DrawingArea::on_unrealize();
}

bool SimpleGLScene::on_configure_event(GdkEventConfigure* event)
{
	cvdebug() << "begin: on_configure_event\n"; 
	//
	// Get GL::Drawable.
	//
	
	Glib::RefPtr<Gdk::GL::Drawable> gldrawable = get_gl_drawable();
	
	//
	// GL calls.
	//

	// *** OpenGL BEGIN ***
	if (!gldrawable->gl_begin(get_gl_context()))
		return false;

	glViewport(0, 0, get_width(), get_height());
	m_glview->reshape(get_width(), get_height());
	
	gldrawable->gl_end();
	// *** OpenGL END ***
	
	cvdebug() << "end: on_configure_event\n"; 

	
	return true;
}

bool SimpleGLScene::on_expose_event(GdkEventExpose* event)
{ 
	if (event->count != 0) return true;
	
	cvdebug() << "begin: on_expose_event\n"; 
	//
	// Get GL::Drawable.
	//

	Glib::RefPtr<Gdk::GL::Drawable> gldrawable = get_gl_drawable();

	//
	// GL calls.
	//

	// *** OpenGL BEGIN ***
	if (!gldrawable->gl_begin(get_gl_context()))
		return false;

	cvdebug() << "begin draw\n"; 
	m_glview->gl_draw();
	cvdebug() << "end draw\n"; 
	
	// Swap buffers.
	gldrawable->swap_buffers();
	
	gldrawable->gl_end();
	// *** OpenGL END ***
	cvdebug() << "end: on_expose_event\n"; 
	
	return true;
}


bool SimpleGLScene::on_unmap_event(GdkEventAny* event)
{
	cvdebug() << "on_unmap_event\n"; 
	idle_remove();
	return true;
}

bool SimpleGLScene::on_visibility_notify_event(GdkEventVisibility* event)
{
	cvdebug() << "on_visibility_notify_event\n"; 
	if (event->state == GDK_VISIBILITY_FULLY_OBSCURED)
		idle_remove();
	else
		idle_add();
	
	return true;
}

bool SimpleGLScene::on_idle()
{
	bool test1 = pending_events->get_handled();
	bool test2 = pending_events->handle_events();
	bool test3 = m_glview->idle();

	if (test1 || test2 || test3) {
		cvdebug() << "on_idle\n"; 
		queue_draw();
	}
	
	pending_events->reset_handled();
	
	return true;
}


void SimpleGLScene::idle_add()
{
	if (!m_ConnectionIdle.connected())
		m_ConnectionIdle = Glib::signal_idle().connect(sigc::mem_fun(*this, &SimpleGLScene::on_idle), GDK_PRIORITY_REDRAW);
}

void SimpleGLScene::idle_remove()
{
	if (m_ConnectionIdle.connected())
		m_ConnectionIdle.disconnect();
}

void SimpleGLScene::detach()
{
	//
	// Get GL::Drawable.
	//
	
	Glib::RefPtr<Gdk::GL::Drawable> gldrawable = get_gl_drawable();
    
	// *** OpenGL BEGIN ***
	if (!gldrawable->gl_begin(get_gl_context()))
		return;

	vobjects->alpha->gl_detach();
	m_glview->gl_detach();
	
	gldrawable->gl_end();
	
	// *** OpenGL END ***
}


bool SimpleGLScene::on_button_press_event(GdkEventButton* event)
{
	event_t myevent;
	myevent.what = event_t::ev_mouse; 
	myevent.where.x = static_cast<int>(event->x); 
	myevent.where.y = static_cast<int>(event->y);
	myevent.action.button = 0; 

	if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) 
		myevent.action.button |= EV_KEY_MODIFIER_SHIFT; 
	
	if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) 
		myevent.action.button |= EV_KEY_MODIFIER_CTRL; 
	
	if ((event->state & GDK_MOD1_MASK) & GDK_MOD1_MASK) // Alt, Meta
		myevent.action.button |= EV_KEY_MODIFIER_ALT; 

	switch (event->button) {
	case 1: myevent.action.button |= EV_MOUSE1_DOWN; break;
	case 2: myevent.action.button |= EV_MOUSE2_DOWN; break;
	case 3: myevent.action.button |= EV_MOUSE3_DOWN; break;
	}
	
	pending_events->push(myevent); 
		
	// don't block
	return false;
}


bool SimpleGLScene::on_button_release_event(GdkEventButton* event)
{
	event_t myevent;
	myevent.what = event_t::ev_mouse; 
	myevent.where.x = static_cast<int>(event->x); 
	myevent.where.y = static_cast<int>(event->y);
	myevent.action.button = 0; 

	if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) 
		myevent.action.button |= EV_KEY_MODIFIER_SHIFT; 
	
	if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) 
		myevent.action.button |= EV_KEY_MODIFIER_CTRL; 
	
	if ((event->state & GDK_MOD1_MASK) & GDK_MOD1_MASK) // Alt, Meta
		myevent.action.button |= EV_KEY_MODIFIER_ALT; 

	switch (event->button) {
	case 1: myevent.action.button |= EV_MOUSE1_UP; break;
	case 2: myevent.action.button |= EV_MOUSE2_UP; break;
	case 3: myevent.action.button |= EV_MOUSE3_UP; break;
	}
	pending_events->push(myevent); 
		
	// don't block
	return false;
}


bool SimpleGLScene::on_motion_notify_event(GdkEventMotion* event)
{
	event_t myevent;
	myevent.what = event_t::ev_mouse; 
	myevent.where.x = static_cast<int>(event->x); 
	myevent.where.y = static_cast<int>(event->y);
	myevent.action.button = EV_MOUSE_MOVE; 

	pending_events->push(myevent); 

	// don't block
	return false;
}


///////////////////////////////////////////////////////////////////////////////
//
// class WinScene - functions taken from original viewit sourcecode
//
///////////////////////////////////////////////////////////////////////////////


bool SimpleGLScene::handle_key(int key)
{
	switch (key) {
	case 'm':
		m_glview->toggle_movie_mode();
		break; 
	default: 
		cerr << "unhandled key " << key << endl; 
		return false; 
		
	}
	return true; 
}


///////////////////////////////////////////////////////////////////////////////
//
// The WinScene class.
//
///////////////////////////////////////////////////////////////////////////////


WinScene::WinScene(const Glib::RefPtr< Gdk::Screen > & screen, PGLViewer glview)
	: m_VBox(false, 0), 
	  m_SimpleGLScene(screen, glview), 
	  m_screen(screen)
{
	//
	// Top-level window.
	//

	set_title("Scene");
	
#ifndef G_OS_WIN32
	// Perform the resizes immediately.
	set_resize_mode(Gtk::RESIZE_IMMEDIATE);
#endif
	// Get automatically redrawn if any of their children changed allocation.
	set_reallocate_redraws(true);

	//set_screen(screen);
	
	add(m_VBox);
  
	//
	// Simple OpenGL scene.
	//

	m_VBox.pack_start(m_SimpleGLScene);

	m_SimpleGLScene.show();
	m_VBox.show();

	// set the position for this widget
	std::string section = "WinScene";
	std::string key	    = "position";
	std::string value;
	
	int width  = 312;
	int height = 0;
	
	if (inifile->get_value(section,key,value)) {
		unsigned int pos = value.find_first_of(' ');
		width  = atoi(std::string(value.substr(0,pos)).c_str());
		height = atoi(std::string(value.substr(pos+1,value.length())).c_str());
	}		
	this->move(width,height);

	// set the size for this widget
	key	= "size";
	width   = 400;
	height  = 400;
	if (inifile->get_value(section,key,value)) {
		unsigned int pos = value.find_first_of(' ');
		width  = atoi(std::string(value.substr(0,pos)).c_str());
		height = atoi(std::string(value.substr(pos+1,value.length())).c_str());
	}		
	this->resize(width,height);

	//
	// Show window.
	//
	show_all_children();
}

WinScene::~WinScene()
{
	save_state();
}


void WinScene::save_state() {
	// write the current size of the widget to the config file	
	std::string section = "WinScene";
	std::string key	= "size";
	std::string value;
	char buffer[255];
	int width,height;
	get_size(width,height);
	sprintf(buffer,"%d %d",width,height);
	value = buffer;
	inifile->set_value(section,key,value);
	
	// write the current position of the widget to the config file	
	key	= "position";
	get_position(width,height);
	sprintf(buffer,"%d %d",width,height);
	value = buffer;
	inifile->set_value(section,key,value);	
}

bool WinScene::on_WinScene_delete_event(GdkEventAny *ev)
{  
	viewit_signal_handler->WinMain_hide_notification().emit("WinScene");
	return false;
}


void WinScene::on_realize()
{
	set_screen(m_screen); 
}


void WinScene::repaint()
{
	show_all();
}


void WinScene::on_WinScene_detach() 
{
	m_SimpleGLScene.detach();
}
	

void WinScene::on_WinScene_resize(unsigned int width, unsigned int height)
{
	resize(width,height);
}
	

void WinScene::on_WinScene_set_visibility(bool visible)
{
	if (visible)
		this->show();
	else
		this->hide();
}


void WinScene::on_WinScene_press_key(ViewitKeyTypes key)
{
	event_t myevent;
	myevent.what = event_t::ev_key; 
	myevent.action.key = key;	
	pending_events->push(myevent); 	
}


bool WinScene::on_WinScene_key_press_event(GdkEventKey* event) 
{
// 	event_t myevent;
// 	myevent.what = event_t::ev_key; 
	
// 	m_SimpleGLScene.get_pointer(myevent.where.x, myevent.where.y);
// 	myevent.action.key = event->string[0];
// 	pending_events->push(myevent); 	
	
	return true; 
}	


