// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// 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 "context_menu.h"
#include "dag_control.h"
#include "k3ddialog.h"

#include <k3dsdk/application.h>
#include <k3dsdk/classes.h>
#include <k3dsdk/idag.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/idocument_plugin_factory.h>
#include <k3dsdk/iobject_collection.h>
#include <k3dsdk/ioptions.h>
#include <k3dsdk/iuser_interface.h>
#include <k3dsdk/iviewport.h>
#include <k3dsdk/iviewport_host.h>
#include <k3dsdk/objects.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/property.h>
#include <k3dsdk/selection.h>
#include <k3dsdk/state_change_set.h>
#include <k3dsdk/time_source.h>
#include <k3dsdk/utility.h>
#include <k3dsdk/viewport.h>

#include <sdpgtk/sdpgtkevents.h>

namespace k3d
{

namespace dag_control
{

namespace detail
{

struct sort_by_label
{
	bool operator()(const node* const LHS, const node* const RHS)
	{
		return LHS->label < RHS->label;
	}
};

struct sort_by_type
{
	bool operator()(const node* const LHS, const node* const RHS)
	{
		if(LHS->objects.size() != RHS->objects.size())
			return LHS->objects.size() < RHS->objects.size();

		for(unsigned int i = 0; i != LHS->objects.size(); ++i)
			return typeid(*LHS->objects[i]).before(typeid(*RHS->objects[i]));	

		return true;
	}
};

struct sort_by_name
{
	bool operator()(iplugin_factory* const LHS, iplugin_factory* const RHS)
	{
		return LHS->name() < RHS->name();
	}
};

} // namespace detail

////////////////////////////////////////////////////////////////////
// graph

graph::~graph()
{
	std::for_each(nodes.begin(), nodes.end(), delete_object());
	std::for_each(edges.begin(), edges.end(), delete_object());
}

////////////////////////////////////////////////////////////////////
// null_filter_policy

void null_filter_policy::populate_graph(graph& Graph)
{
}

////////////////////////////////////////////////////////////////////
// all_objects_filter_policy

all_objects_filter_policy::all_objects_filter_policy(idocument& Document) :
	m_document(Document)
{
}

void all_objects_filter_policy::populate_graph(graph& Graph)
{
	for(iobject_collection::objects_t::const_iterator object = m_document.objects().collection().begin(); object != m_document.objects().collection().end(); ++object)
		{
			node* const new_node = new node;
			new_node->label = (*object)->name();
			new_node->objects.push_back(*object);
			
			Graph.nodes.push_back(new_node);
		}
}

////////////////////////////////////////////////////////////////////
// class_id_filter_policy

class_id_filter_policy::class_id_filter_policy(idocument& Document, const uuid& ClassID) :
	m_document(Document),
	m_class_id(ClassID)
{
}

void class_id_filter_policy::populate_graph(graph& Graph)
{
	for(iobject_collection::objects_t::const_iterator object = m_document.objects().collection().begin(); object != m_document.objects().collection().end(); ++object)
		{
			if((*object)->factory().class_id() != m_class_id)
				continue;
				
			node* const new_node = new node;
			new_node->label = (*object)->name();
			new_node->objects.push_back(*object);
			
			Graph.nodes.push_back(new_node);
		}
}

////////////////////////////////////////////////////////////////////
// null_layout_policy

void null_layout_policy::update_layout(graph& Graph)
{
}

////////////////////////////////////////////////////////////////////
// sort_by_label_layout_policy

void sort_by_label_layout_policy::update_layout(graph& Graph)
{
	std::sort(Graph.nodes.begin(), Graph.nodes.end(), detail::sort_by_label());
}

////////////////////////////////////////////////////////////////////
// sort_by_type_layout_policy

void sort_by_type_layout_policy::update_layout(graph& Graph)
{
	std::sort(Graph.nodes.begin(), Graph.nodes.end(), detail::sort_by_type());
}

////////////////////////////////////////////////////////////////////
// null_drawing_policy

void null_drawing_policy::draw_graph(const graph& Graph)
{
}

////////////////////////////////////////////////////////////////////
// control::implementation

class control::implementation :
	public k3dDialog
{
	typedef k3dDialog base;
	
public:
	implementation(idocument& Document, iunknown* ParentCommandNode, const std::string& CommandNodeName) :
		base(ParentCommandNode, CommandNodeName, 0),
		m_document(Document),
		m_filter_policy(new all_objects_filter_policy(Document)),
		m_layout_policy(new sort_by_label_layout_policy()),
		m_drawing_policy(new null_drawing_policy()),
		m_idle_handler(0),
		m_context_menu(Document)
	{
		return_if_fail(LoadGTKMLTemplate("dag_control.gtkml"));
		
		// Reset the control contents anytime the document object collection changes
		m_document.objects().add_objects_signal().connect(SigC::slot(*this, &implementation::on_objects_added));
		m_document.objects().remove_objects_signal().connect(SigC::slot(*this, &implementation::on_objects_removed));
		m_document.objects().rename_object_signal().connect(SigC::slot(*this, &implementation::on_object_renamed));
		
		// Reset the control contents anytime the DAG changes
		m_document.dag().dependency_signal().connect(SigC::slot(*this, &implementation::on_dependencies_changed));
		
		update_contents();
	}

	~implementation()
	{
		if(m_idle_handler)
			gtk_idle_remove(m_idle_handler);
	}
			
	void set_filter_policy(filter_policy* const Policy)
	{
		return_if_fail(Policy);
		
		m_filter_policy.reset(Policy);
		reset_control();
	}

	void set_layout_policy(layout_policy* const Policy)
	{
		return_if_fail(Policy);
		
		m_layout_policy.reset(Policy);
		reset_control();
	}

	void set_drawing_policy(drawing_policy* const Policy)
	{
		return_if_fail(Policy);
		
		m_drawing_policy.reset(Policy);
		reset_control();
	}

	sdpGtkWidget root_widget()
	{
		return RootWidget();
	}

private:
	/// Called by the signal system anytime new objects are added to the document
	void on_objects_added(const iobject_collection::objects_t& Objects)
	{
		reset_control();
	}
	
	/// Called by the signal system anytime objects are removed from the document
	void on_objects_removed(const iobject_collection::objects_t& Objects)
	{
		reset_control();
	}
	
	/// Called by the signal system anytime an object is renamed
	void on_object_renamed(iobject* const Object)
	{
		reset_control();
	}
	
	/// Called by the signal system anytime DAG dependencies change
	void on_dependencies_changed(const idag::dependencies_t& Dependencies)
	{
		reset_control();
	}
	
	/// Called anytime the control needs to be updated
	void reset_control()
	{
		if(!m_idle_handler)
			m_idle_handler = gtk_idle_add(raw_reset, this);
	}

	static gint raw_reset(gpointer Data)
	{
		return reinterpret_cast<implementation*>(Data)->reset();
	}

	/// Called once by the event system if the controls needs to be updated since the GUI was last idle
	gint reset()
	{
		update_contents();
		
		m_idle_handler = 0;
		return false;
	}
	
	void update_contents()
	{
		m_current_graph.reset(new graph());
		m_filter_policy->populate_graph(*m_current_graph);
		m_layout_policy->update_layout(*m_current_graph);

		// Keep track of roughly where we were in the list ...
		const double position = ScrolledWindow("scrolledwindow").VerticalAdjustment().Value();

		// This is a short-term hack to get something visible ...		
		sdpGtkCList list = CList("dag");
		list.Clear();

		for(graph::nodes_t::const_iterator node = m_current_graph->nodes.begin(); node != m_current_graph->nodes.end(); ++node)
			{		
				const char* labels[] = { (*node)->label.c_str() };
				const int row = list.Append(labels);
				list.SetRowData(row, *node);
			}

		// Try to restore our original position (give or take) ...
		ScrolledWindow("scrolledwindow").VerticalAdjustment().SetValue(position);
	}

	void OnEvent(sdpGtkEvent* Event)
	{
		if(Event->Name() == "dag_clicked")
			on_dag_clicked(Event);
		else
			base::OnEvent(Event);
	}

	void on_dag_clicked(sdpGtkEvent* Event)
	{
		return_if_fail(Event);

		sdpGtkCList list = CList("dag");

		// Get the row we clicked on ...
		int row = 0;
		int column = 0;
		sdpGtkEventWidgetButtonPressEvent* event = static_cast<sdpGtkEventWidgetButtonPressEvent*>(Event);
		list.GetHitInfo(static_cast<int>(event->Event()->x), static_cast<int>(event->Event()->y), &row, &column);

		if(-1 == row)
			return;

		node* const selected_node = reinterpret_cast<node*>(list.GetRowData(row));
		return_if_fail(selected_node);

//		record_command(*this, icommand_node::command_t::USER_INTERFACE, "highlight_node", selected_node->label);

		if(event->Event()->button == 1 && event->Event()->type == GDK_2BUTTON_PRESS)
			{
				list.SelectRow(row, 0);
				if(application().user_interface())
					{
						record_command(*this, icommand_node::command_t::USER_INTERFACE, "show_properties", selected_node->label);
						for(node::objects_t::const_iterator object = selected_node->objects.begin(); object != selected_node->objects.end(); ++object)
							application().user_interface()->show(**object);
					}
			}
		else if(event->Event()->button == 3)
			{
				record_command(*this, icommand_node::command_t::USER_INTERFACE, "context_menu", selected_node->label);

				list.SelectRow(row, 0);
				on_context_menu(*selected_node);
			}
	}

	bool execute_command(const std::string& Command, const std::string& Arguments)
	{
/*
		if(Command == "highlight_node")
			{
				sdpGtkCList list = CList("dag");
				for(int row = 0; row != list.RowCount(); ++row)
					{
						node* const selected_node = reinterpret_cast<node*>(list.GetRowData(row));
						if(selected_node->label != Arguments)
							continue;
							
						list.InteractiveShow(application().options().tutorial_speed(), true);
						list.InteractiveWarpPointer(row, application().options().tutorial_speed(), true);
						list.SelectRow(row, 0);

						if(application().user_interface())
							application().user_interface()->tutorial_mouse_message("Highlight Node:", iuser_interface::LMB_CLICK, key_modifiers());

						return true;
					}
				std::cerr << error << "Unknown node: " << Arguments << std::endl;
				return false;
			}
		else*/ if(Command == "show_properties")
			{
				sdpGtkCList list = CList("dag");
				for(int row = 0; row != list.RowCount(); ++row)
					{
						node* const selected_node = reinterpret_cast<node*>(list.GetRowData(row));
						if(selected_node->label != Arguments)
							continue;
							
						list.InteractiveShow(application().options().tutorial_speed(), true);
						list.InteractiveWarpPointer(row, application().options().tutorial_speed(), true);
						list.SelectRow(row, 0);

						if(application().user_interface())
							{
								application().user_interface()->tutorial_mouse_message("Show Properties:", iuser_interface::LMB_DOUBLE_CLICK, key_modifiers());
								for(node::objects_t::const_iterator object = selected_node->objects.begin(); object != selected_node->objects.end(); ++object)
									application().user_interface()->show(**object);
							}

						return true;
					}
				std::cerr << error << "Unknown node: " << Arguments << std::endl;
				return false;
			}
		else if(Command == "context_menu")
			{
				sdpGtkCList list = CList("dag");
				for(int row = 0; row != list.RowCount(); ++row)
					{
						node* const selected_node = reinterpret_cast<node*>(list.GetRowData(row));
						if(selected_node->label != Arguments)
							continue;
							
						list.InteractiveShow(application().options().tutorial_speed(), true);
						list.InteractiveWarpPointer(row, application().options().tutorial_speed(), true);
						list.SelectRow(row, 0);

						if(application().user_interface())
							{
								application().user_interface()->tutorial_mouse_message("Context Menu:", iuser_interface::RMB_CLICK, key_modifiers());
								on_context_menu(*selected_node);
							}

						return true;
					}
				std::cerr << error << "Unknown node: " << Arguments << std::endl;
				return false;
			}

		return base::execute_command(Command, Arguments);
	}

	void on_sort_by_label()
	{
		set_layout_policy(new sort_by_label_layout_policy);
	}

	void on_sort_by_type()
	{
		set_layout_policy(new sort_by_type_layout_policy);
	}

	void on_show_all_objects()
	{
		set_filter_policy(new all_objects_filter_policy(m_document));
	}
	
	void on_filter_by_type(const uuid ClassID)
	{
		set_filter_policy(new class_id_filter_policy(m_document, ClassID));
	}

	void on_context_menu(node& SelectedNode)
	{
		// Currently, we don't display any menu for nodes with multiple objects
		if(SelectedNode.objects.size() != 1)
			return;

		// Basic functionality common to all objects ...
		iobject* const object = dynamic_cast<iobject*>(SelectedNode.objects[0]);
		if(object)
			m_context_menu.show(this, *object);
	}

	idocument& m_document;

	/// Stores the current graph being visualized
	std::auto_ptr<graph> m_current_graph;
	/// Stores a policy that controls what's visible in the current graph
	std::auto_ptr<filter_policy> m_filter_policy;
	/// Stores a policy that handles layout of the current graph
	std::auto_ptr<layout_policy> m_layout_policy;
	/// Stores a policy that handles drawing the current graph
	std::auto_ptr<drawing_policy> m_drawing_policy;
	
	/// Keeps track of the idle-handler we use to handle display updates
	guint m_idle_handler;

	/// Context menu when the user right-clicks on a node
	k3d::context_menu::object m_context_menu;
};

////////////////////////////////////////////////////////////////////
// control

control::control(idocument& Document, iunknown* ParentCommandNode, const std::string& CommandNodeName) :
	m_implementation(new implementation(Document, ParentCommandNode, CommandNodeName))
{
}

control::~control()
{
	delete m_implementation;
}

void control::set_filter_policy(filter_policy* const Policy)
{
	m_implementation->set_filter_policy(Policy);
}

void control::set_layout_policy(layout_policy* const Policy)
{
	m_implementation->set_layout_policy(Policy);
}

void control::set_drawing_policy(drawing_policy* const Policy)
{
	m_implementation->set_drawing_policy(Policy);
}

sdpGtkWidget control::root_widget()
{
	return m_implementation->root_widget();
}

} // namespace dag_control

} // namespace k3d

