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

/** \file
		\brief Implements the k3d::viewport::window class, which displays a 3D view of an open K-3D document
		\author Tim Shead (tshead@k-3d.com)
*/

#include "button.h"
#include "chooser.h"
#include "k3ddialog.h"
#include "menu_item.h"
#include "toggle_button.h"
#include "viewport_control.h"
#include "viewport_window.h"

#include <k3dsdk/application.h>
#include <k3dsdk/basic_math.h>
#include <k3dsdk/frames.h>
#include <k3dsdk/ianimation_render_engine.h>
#include <k3dsdk/iapplication.h>
#include <k3dsdk/icommand_tree.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/istill_render_engine.h>
#include <k3dsdk/iviewport.h>
#include <k3dsdk/iviewport_host.h>
#include <k3dsdk/property.h>
#include <k3dsdk/time_source.h>
#include <k3dsdk/user_interface.h>

#include <sdpgtk/sdpgtkevents.h>

#include <boost/any.hpp>

// We have an unfortunate clash with X
#ifdef RootWindow
#undef RootWindow
#endif // RootWindow

namespace k3d
{

namespace viewport
{

/////////////////////////////////////////////////////////////////////////////
// window::implementation

class window::implementation :
	public k3dDialog
{
	typedef k3dDialog base;
	
public:
	implementation(idocument& Document) :
		base(&Document, "viewport_window", new k3d::options_window_geometry_store()),
		m_document(Document),
		m_control(0),
		m_viewport(0)
	{
		return_if_fail(LoadGTKMLTemplate("viewport_window.gtkml"));

		m_control = new viewport::control(Document, *this, Widget("viewport"));
		m_control->viewport_changed_signal().connect(SigC::slot(*this, &implementation::on_viewport_changed));

		if(get_menu_item("file_close_menu_item"))
			get_menu_item("file_close_menu_item")->signal_activate().connect(m_close_signal.slot());		

		if(get_button("save_viewport_frame_button"))
			get_button("save_viewport_frame_button")->signal_activate().connect(SigC::slot(*this, &implementation::on_save_viewport_frame));
		if(get_button("save_viewport_animation_button"))
			get_button("save_viewport_animation_button")->signal_activate().connect(SigC::slot(*this, &implementation::on_save_viewport_animation));
		if(get_button("render_preview_button"))
			get_button("render_preview_button")->signal_activate().connect(SigC::slot(*this, &implementation::on_render_preview));
		if(get_button("render_frame_button"))
			get_button("render_frame_button")->signal_activate().connect(SigC::slot(*this, &implementation::on_render_frame));
		if(get_button("render_animation_button"))
			get_button("render_animation_button")->signal_activate().connect(SigC::slot(*this, &implementation::on_render_animation));
						
		if(get_menu_item("render_preview_menu_item"))
			get_menu_item("render_preview_menu_item")->signal_activate().connect(SigC::slot(*this, &implementation::on_render_preview));
		if(get_menu_item("render_frame_menu_item"))
			get_menu_item("render_frame_menu_item")->signal_activate().connect(SigC::slot(*this, &implementation::on_render_frame));
		if(get_menu_item("render_animation_menu_item"))
			get_menu_item("render_animation_menu_item")->signal_activate().connect(SigC::slot(*this, &implementation::on_render_animation));
		
		icommand_node* const control = get_command_node(*this, "viewport_control");
		if(control)
			{
				iproperty* const navigation_mode = get_property(*control, "navigation_mode");
				iproperty* const active_axis = get_property(*control, "active_axis");
				iproperty* const select_objects = get_property(*control, "select_objects");
				iproperty* const select_meshes = get_property(*control, "select_meshes");
				iproperty* const select_edges = get_property(*control, "select_edges");
				iproperty* const select_faces = get_property(*control, "select_faces");
				iproperty* const select_curves = get_property(*control, "select_curves");
				iproperty* const select_patches = get_property(*control, "select_patches");
				iproperty* const select_point_groups = get_property(*control, "select_point_groups");
				iproperty* const select_points = get_property(*control, "select_points");

				if(navigation_mode && get_chooser("navigation_mode"))
					get_chooser("navigation_mode")->attach(chooser::proxy(*navigation_mode), 0, navigation_mode->name());
				
				if(active_axis && get_chooser("active_axis"))
					get_chooser("active_axis")->attach(chooser::proxy(*active_axis), 0, active_axis->name());

				if(select_objects && get_toggle_button("select_objects"))
					get_toggle_button("select_objects")->attach(toggle_button::proxy(*select_objects), 0, select_objects->name());

				if(select_meshes && get_toggle_button("select_meshes"))
					get_toggle_button("select_meshes")->attach(toggle_button::proxy(*select_meshes), 0, select_meshes->name());

				if(select_edges && get_toggle_button("select_edges"))
					get_toggle_button("select_edges")->attach(toggle_button::proxy(*select_edges), 0, select_edges->name());

				if(select_faces && get_toggle_button("select_faces"))
					get_toggle_button("select_faces")->attach(toggle_button::proxy(*select_faces), 0, select_faces->name());

				if(select_curves && get_toggle_button("select_curves"))
					get_toggle_button("select_curves")->attach(toggle_button::proxy(*select_curves), 0, select_curves->name());

				if(select_patches && get_toggle_button("select_patches"))
					get_toggle_button("select_patches")->attach(toggle_button::proxy(*select_patches), 0, select_patches->name());

				if(select_point_groups && get_toggle_button("select_point_groups"))
					get_toggle_button("select_point_groups")->attach(toggle_button::proxy(*select_point_groups), 0, select_point_groups->name());

				if(select_points && get_toggle_button("select_points"))
					get_toggle_button("select_points")->attach(toggle_button::proxy(*select_points), 0, select_points->name());
			}
																																												
		RootWindow().SetTitle("Unattached");
		Show();
	}
	
	~implementation()
	{
		delete m_control;
	}
	
	void attach(iviewport& Viewport)
	{
		m_control->attach(Viewport);

		k3d::icommand_node* const viewport_node = dynamic_cast<k3d::icommand_node*>(&Viewport);
		k3d::icommand_node* const document_node = dynamic_cast<k3d::icommand_node*>(&m_document);
		k3d::application().command_tree().add_node(*this, viewport_node ? *viewport_node : *document_node);
		
		restore_geometry();
	}

	SigC::Signal0<void>& close_signal()
	{
		return m_close_signal;
	}

private:
	void OnDelete(sdpGtkEvent* Event)
	{
		// Don't let it happen ...
		((sdpGtkEventWidgetDeleteEvent*)Event)->SetResult(true);
		m_close_signal.emit();
	}

	void on_viewport_changed(k3d::iviewport* const Viewport)
	{
		m_viewport = Viewport;

		// Update our position in the command tree ...
		k3d::icommand_node* const viewport_node = dynamic_cast<k3d::icommand_node*>(m_viewport);
		k3d::icommand_node* const document_node = dynamic_cast<k3d::icommand_node*>(&m_document);
		k3d::application().command_tree().add_node(*this, viewport_node ? *viewport_node : *document_node);

		// Update the titlebar ...		
		k3d::iobject* const object = dynamic_cast<k3d::iobject*>(m_viewport);
		RootWindow().SetTitle(object ? object->name() : "Unattached");

		// Update render buttons ...
		k3d::ianimation_render_engine* animation_render_engine = 0;
		k3d::istill_render_engine* still_render_engine = 0;
		if(m_viewport)
			{
				animation_render_engine = dynamic_cast<k3d::ianimation_render_engine*>(m_viewport->host());
				still_render_engine = dynamic_cast<k3d::istill_render_engine*>(m_viewport->host());
			}
			
		if(animation_render_engine)
			{
				get_button("render_animation_button")->show();
			}
		else
			{
				get_button("render_animation_button")->hide();
			}
			
		if(still_render_engine)
			{
				get_button("render_preview_button")->show();
				get_button("render_frame_button")->show();
			}
		else
			{
				get_button("render_preview_button")->hide();
				get_button("render_frame_button")->hide();
			}
	}

	void on_save_viewport_frame()
	{
		return_if_fail(m_viewport);

		// Prompt the user for a filename ...
		boost::filesystem::path file;
		if(!k3d::get_file_path("render_frame", "Render Frame:", true, boost::filesystem::path(), file))
			return;

		// If we're connected to a viewport that's hosted by a render engine, defer to it to do the work ...
		k3d::istill_render_engine* render_engine = dynamic_cast<k3d::istill_render_engine*>(m_control);
		return_if_fail(render_engine);

		assert_warning(render_engine->render_frame(file, true));
	}

	void on_save_viewport_animation()
	{
		return_if_fail(m_viewport);
		
		// Ensure that the document has animation capabilities, first ...
		k3d::iproperty* const start_time_property = k3d::get_start_time(m_document);
		k3d::iproperty* const end_time_property = k3d::get_end_time(m_document);
		k3d::iproperty* const frame_rate_property = k3d::get_frame_rate(m_document);
		return_if_fail(start_time_property && end_time_property && frame_rate_property);

		// Prompt the user for a base filename ...
		boost::filesystem::path file;
		if(!k3d::get_file_path("renderanimation", "Choose Animation Base Filepath:", false, boost::filesystem::path(), file))
			return;

		// Make sure the supplied filepath has enough digits to render the entire animation ...
		const double start_time = boost::any_cast<double>(k3d::get_property_value(m_document.dag(), *start_time_property));
		const double end_time = boost::any_cast<double>(k3d::get_property_value(m_document.dag(), *end_time_property));
		const double frame_rate = boost::any_cast<double>(k3d::get_property_value(m_document.dag(), *frame_rate_property));
		
		const long start_frame = static_cast<long>(k3d::round(frame_rate * start_time));
		const long end_frame = static_cast<long>(k3d::round(frame_rate * end_time));

		k3d::frames frames(file, start_frame, end_frame);
		if(frames.max_frame() < end_frame)
			{
				std::string message = "The Base Filepath doesn't contain enough digits to render the entire animation.\n"
					"Try a filepath of the form [ myanim0000.tif ] ... the placement of digits is flexible,\n"
					"and any prefix / postfix / file extension is optional, but the path must contain\n"
					"enough consecutive digits to enumerate all of the frames in the animation.";

				k3d::error_message(message, "Render Animation:");
				return;
			}

		// See if the user wants to view frames as they're completed ...
		std::vector<std::string> buttons;
		buttons.push_back("Yes");
		buttons.push_back("No");
		buttons.push_back("Cancel");

		const unsigned long result = k3d::query_message("Do you want to see rendered frames as they're completed?", "Render Animation:", 1, buttons);
		if(0 == result || 3 == result)
			return;

		const bool viewcompleted = (1 == result);

		// If we're connected to a viewport that's hosted by a render engine, defer to it to do the work ...
		k3d::ianimation_render_engine* render_engine = dynamic_cast<k3d::ianimation_render_engine*>(m_control);
		return_if_fail(render_engine);
		
		assert_warning(render_engine->render_animation(file, viewcompleted));
	}

	void on_render_preview()
	{
		return_if_fail(m_viewport);
		
		k3d::istill_render_engine* render_engine = dynamic_cast<k3d::istill_render_engine*>(m_viewport->host());
		return_if_fail(render_engine);

		assert_warning(render_engine->render_preview());
	}
	
	void on_render_frame()
	{
		return_if_fail(m_viewport);
		
		// Prompt the user for a filename ...
		boost::filesystem::path file;
		if(!k3d::get_file_path("render_frame", "Render Frame:", true, boost::filesystem::path(), file))
			return;

		// If we're connected to a viewport that's hosted by a render engine, defer to it to do the work ...
		k3d::istill_render_engine* render_engine = dynamic_cast<k3d::istill_render_engine*>(m_viewport->host());
		return_if_fail(render_engine);

		assert_warning(render_engine->render_frame(file, true));
	}
	
	void on_render_animation()
	{
		return_if_fail(m_viewport);
		
		// Ensure that the document has animation capabilities, first ...
		k3d::iproperty* const start_time_property = k3d::get_start_time(m_document);
		k3d::iproperty* const end_time_property = k3d::get_end_time(m_document);
		k3d::iproperty* const frame_rate_property = k3d::get_frame_rate(m_document);
		return_if_fail(start_time_property && end_time_property && frame_rate_property);

		// Prompt the user for a base filename ...
		boost::filesystem::path file;
		if(!k3d::get_file_path("renderanimation", "Choose Animation Base Filepath:", false, boost::filesystem::path(), file))
			return;

		// Make sure the supplied filepath has enough digits to render the entire animation ...
		const double start_time = boost::any_cast<double>(k3d::get_property_value(m_document.dag(), *start_time_property));
		const double end_time = boost::any_cast<double>(k3d::get_property_value(m_document.dag(), *end_time_property));
		const double frame_rate = boost::any_cast<double>(k3d::get_property_value(m_document.dag(), *frame_rate_property));
		
		const long start_frame = static_cast<long>(k3d::round(frame_rate * start_time));
		const long end_frame = static_cast<long>(k3d::round(frame_rate * end_time));

		k3d::frames frames(file, start_frame, end_frame);
		if(frames.max_frame() < end_frame)
			{
				std::string message = "The Base Filepath doesn't contain enough digits to render the entire animation.\n"
					"Try a filepath of the form [ myanim0000.tif ] ... the placement of digits is flexible,\n"
					"and any prefix / postfix / file extension is optional, but the path must contain\n"
					"enough consecutive digits to enumerate all of the frames in the animation.";

				k3d::error_message(message, "Render Animation:");
				return;
			}

		// See if the user wants to view frames as they're completed ...
		std::vector<std::string> buttons;
		buttons.push_back("Yes");
		buttons.push_back("No");
		buttons.push_back("Cancel");

		const unsigned long result = k3d::query_message("Do you want to see rendered frames as they're completed?", "Render Animation:", 1, buttons);
		if(0 == result || 3 == result)
			return;

		const bool viewcompleted = (1 == result);

		// If we're connected to a viewport that's hosted by a render engine, defer to it to do the work ...
		k3d::ianimation_render_engine* render_engine = dynamic_cast<k3d::ianimation_render_engine*>(m_viewport->host());
		return_if_fail(render_engine);
		
		assert_warning(render_engine->render_animation(file, viewcompleted));
	}

	idocument& m_document;
	viewport::control* m_control;
	k3d::iviewport* m_viewport;

	SigC::Signal0<void> m_close_signal;
};

/////////////////////////////////////////////////////////////////////////////
// window

window::window(idocument& Document) :
	m_implementation(new implementation(Document))
{
	// We wish to be notified if the user wants to close the window
	m_implementation->close_signal().connect(SigC::slot(*this, &window::on_close));
	// We wish to be notified if the document is closed
	Document.close_signal().connect(SigC::slot(*this, &window::on_close));
}

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

void window::attach(iviewport& Viewport)
{
	m_implementation->attach(Viewport);
}

void window::on_close()
{
	delete this;
}

} // namespace viewport

} // namespace k3d





