// 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 Declares the application_implementation class, which encapsulates the running K-3D program
		\author Tim Shead (tshead@k-3d.com)
*/

#include "application.h"
#include "document.h"

#include <k3dsdk/command_node.h>
#include <k3dsdk/data.h>
#include <k3dsdk/iapplication_plugin_factory.h>
#include <k3dsdk/icommand_node.h>
#include <k3dsdk/icommand_tree.h>
#include <k3dsdk/ideletable.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/ioptions.h>
#include <k3dsdk/iscript_engine.h>
#include <k3dsdk/ishader_collection.h>
#include <k3dsdk/iuser_interface.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/result.h>
#include <k3dsdk/state_change_set.h>

#include <sdpxml/sdpxml.h>

#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>

#include <fstream>
#include <iostream>

#ifdef	interface
#undef	interface
#endif	// interface

namespace
{

/////////////////////////////////////////////////////////////////////////////
// command_tree_implementation

/// Provides an implementation of k3d::icommand_tree
class command_tree_implementation :
	public k3d::icommand_tree
{
public:
	command_tree_implementation() :
		m_nodes(k3d::init_value(parent_map_t()))
	{
	}

	void set_root(k3d::icommand_node& RootNode)
	{
		m_nodes.value().insert(std::make_pair(&RootNode, static_cast<k3d::icommand_node*>(0)));
	}

	void add_node(k3d::icommand_node& Node, k3d::icommand_node& Parent)
	{
		parent_map_t nodes(m_nodes.value());
		nodes[&Node] = &Parent;
		m_nodes.set_value(nodes);
	}

	void remove_node(k3d::icommand_node& Node)
	{
		// Keep track of this node's parent so we can relink children ...
		k3d::icommand_node* const node_parent = parent(Node);
	
		// Erase the node ...
		parent_map_t nodes(m_nodes.value());
		nodes.erase(&Node);
		
		// Relink children ...
		for(parent_map_t::iterator child = nodes.begin(); child != nodes.end(); ++child)
			if(child->second == &Node)
				child->second = node_parent;
		
		m_nodes.set_value(nodes);
	}

	k3d::icommand_node* parent(k3d::icommand_node& Child)
	{
		parent_map_t::iterator child = m_nodes.value().find(&Child);
		return child != m_nodes.value().end() ? child->second : 0;
	}

	const children_t children(k3d::icommand_node& Node)
	{
		children_t results;

		for(parent_map_t::iterator child = m_nodes.value().begin(); child != m_nodes.value().end(); ++child)
			if(child->second == &Node)
				results.push_back(child->first);

		return results;
	}

	changed_signal_t& changed_signal()
	{
		return m_nodes.changed_signal();
	}

private:
	typedef std::map<k3d::icommand_node*, k3d::icommand_node*> parent_map_t;
	k3d_data(parent_map_t, k3d::no_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_nodes;
};

} // namespace

namespace k3d
{

/////////////////////////////////////////////////////////////////////////////
// application_implementation::implementation

/// Encapsulates the running K-3D server application
class application_implementation::implementation :
	public k3d::iapplication,
	public k3d::command_node,
	public k3d::ideletable
{
public:
	implementation(k3d::iplugin_factory_collection& Plugins, k3d::ishader_collection& Shaders, k3d::irender_farm& RenderFarm, k3d::ioptions& Options, k3d::iuser_interface* const UserInterface, const boost::filesystem::path& ShaderCachePath, const boost::filesystem::path& SharePath) :
		k3d::command_node("application"),
		m_plugins(Plugins),
		m_shaders(Shaders),
		m_render_farm(RenderFarm),
		m_options(Options),
		m_user_interface(UserInterface),
		m_shader_cache_path(ShaderCachePath),
		m_share_path(SharePath)
	{
		// Sanity checks ...
		assert_warning(boost::filesystem::exists(m_shader_cache_path));
		assert_warning(boost::filesystem::exists(m_share_path));

		// Register ourselves as the root of the command tree ...
		m_command_tree.set_root(*this);

		// Register the user_interface object (if any) as our child in the command tree ...
		if(dynamic_cast<k3d::icommand_node*>(m_user_interface))
			m_command_tree.add_node(*dynamic_cast<k3d::icommand_node*>(m_user_interface), *this);
	}

	~implementation()
	{
		// Notify observers that we're going away ...
		m_close_signal.emit();

		// Delete any remaining documents ...
		for(document_list_t::iterator document = m_documents.begin(); document != m_documents.end(); document = m_documents.begin())
			close_document(**document);

		delete dynamic_cast<k3d::ideletable*>(m_user_interface);
	}

	bool execute_command(const std::string& Command, const std::string& Arguments)
	{
		return k3d::command_node::execute_command(Command, Arguments);
	}

	k3d::iuser_interface* user_interface()
	{
		return m_user_interface;
	}

	bool exit()
	{
		// Request shutdown from the environment ...
		return m_exit_signal.emit();
	}

	k3d::idocument* open_document(const boost::filesystem::path& DocumentFile)
	{
		// Sanity checks ...
		return_val_if_fail(boost::filesystem::exists(DocumentFile), 0);

		// Notify observers that we're about to create a new document ...
		m_pre_create_document_signal.emit();

		// Create a new doc ...
		k3d::idocument* const document = k3d::open_document(DocumentFile);
		return_val_if_fail(document, 0);

		// Add it to the doc list ...
		m_documents.push_back(document);

		// Notify observers that document initialization is complete ...
		m_new_document_ready_signal.emit(*document);

		return document;
	}

	k3d::idocument* create_document()
	{
		// Notify observers that we're about to create a new document ...
		m_pre_create_document_signal.emit();

		// Create a new doc ...
		k3d::idocument* const document = k3d::create_document();
		return_val_if_fail(document, 0);

		// Add it to the doc list ...
		m_documents.push_back(document);

		// Notify observers that document initialization is complete ...
		m_new_document_ready_signal.emit(*document);

		return document;
	}

	void close_document(k3d::idocument& Document)
	{
		// Find the document iterator ...
		const document_list_t::iterator document = std::find(m_documents.begin(), m_documents.end(), &Document);

		// Sanity checks ...
		return_if_fail(document != m_documents.end());

		// Notify observers that the document will be closed
		m_close_document_signal.emit(Document);

		// Remove it from the document list ...
		m_documents.erase(document);

		// Delete the document ...
		k3d::close_document(Document);
	}

	const k3d::iapplication::document_collection_t documents()
	{
		document_collection_t results;
		std::copy(m_documents.begin(), m_documents.end(), std::back_inserter(results));

		return results;
	}

	k3d::ioptions& options()
	{
		return m_options;
	}
	
	k3d::iapplication::startup_message_signal_t& startup_message_signal()
	{
		return m_startup_message_signal;
	}

	k3d::iapplication::safe_to_close_signal_t& safe_to_close_signal()
	{
		return m_safe_to_close_signal;
	}

	k3d::iapplication::close_signal_t& close_signal()
	{
		return m_close_signal;
	}

	k3d::iapplication::pre_create_document_signal_t& pre_create_document_signal()
	{
		return m_pre_create_document_signal;
	}

	k3d::iapplication::new_document_signal_t& new_document_signal()
	{
		return m_new_document_signal;
	}

	k3d::iapplication::new_document_ready_signal_t& new_document_ready_signal()
	{
		return m_new_document_ready_signal;
	}

	k3d::iapplication::close_document_signal_t& close_document_signal()
	{
		return m_close_document_signal;
	}

	k3d::iapplication::command_signal_t& command_signal()
	{
		return m_command_signal;
	}

	const boost::filesystem::path shader_cache_path()
	{
		return m_shader_cache_path;
	}

	const boost::filesystem::path share_path()
	{
		return m_share_path;
	}

	k3d::icommand_tree& command_tree()
	{
		return m_command_tree;
	}

	const k3d::iplugin_factory_collection::factories_t& plugins()
	{
		return m_plugins.factories();
	}

	const sdpsl::shaders_t& shaders()
	{
		return m_shaders.shaders();
	}

	k3d::irender_farm& render_farm()
	{
		return m_render_farm;
	}

	application_implementation::exit_signal_t& exit_signal()
	{
		return m_exit_signal;
	}

private:
	implementation(const implementation& RHS);
	implementation& operator=(const implementation& RHS);

	// "Environment" options specified at startup

	/// Stores a reference to the collection of available plugin factories
	k3d::iplugin_factory_collection& m_plugins;
	/// Stores a reference to the collection of available RenderMan shaders
	k3d::ishader_collection& m_shaders;
	/// Stores a reference to a renderfarm object for rendering jobs/frames
	k3d::irender_farm& m_render_farm;
	/// Stores a reference to the user options object, for storing global user options
	k3d::ioptions& m_options;
	/// Optional user interface object
	k3d::iuser_interface* const m_user_interface;

	/// Stores the shader cache path
	const boost::filesystem::path m_shader_cache_path;
	/// Stores the share path
	const boost::filesystem::path m_share_path;

	/// Stores the global, hierarchical tree of command nodes
	command_tree_implementation m_command_tree;
	/// Stores a collection of open documents
	typedef std::list<k3d::idocument*> document_list_t;
	/// Stores the collection of open documents
	document_list_t m_documents;
	/// Signal emitted to display progress during application startup
	startup_message_signal_t m_startup_message_signal;
	/// Signal emitted prior to closing the application
	safe_to_close_signal_t m_safe_to_close_signal;
	/// Signal emitted when the application is closing
	close_signal_t m_close_signal;
	/// Signal emitted immediately prior to creating / opening a document
	pre_create_document_signal_t m_pre_create_document_signal;
	/// Signal emitted when a new document is first created
	new_document_signal_t m_new_document_signal;
	/// Signal emitted when a new document is fully initialized (e.g. loaded from disk)
	new_document_ready_signal_t m_new_document_ready_signal;
	/// Signal emitted when an open document is closed
	close_document_signal_t m_close_document_signal;
	/// Signal used to distribute commands to recorders (for macro recording, tutorial recording, blackbox recorder, etc)
	command_signal_t m_command_signal;

	/// Signal emitted to request application close
	application_implementation::exit_signal_t m_exit_signal;
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////
// application_implementation

application_implementation::application_implementation(iplugin_factory_collection& PluginFactories, ishader_collection& Shaders, irender_farm& RenderFarm, ioptions& Options, iuser_interface* const UserInterface, const boost::filesystem::path& ShaderCachePath, const boost::filesystem::path& SharePath) :
	m_implementation(new implementation(PluginFactories, Shaders, RenderFarm, Options, UserInterface, ShaderCachePath, SharePath))
{
}

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

iapplication& application_implementation::interface()
{
	return *m_implementation;
}

application_implementation::exit_signal_t& application_implementation::exit_signal()
{
	return m_implementation->exit_signal();
}

} // namespace k3d


