// 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
		\author Tim Shead <tshead@k-3d.com>
*/

#include <k3dsdk/ioptions.h>
#include <k3dsdk/irender_farm.h>
#include <k3dsdk/istill_render_engine.h>
#include <k3dsdk/module.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>

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

#include <fstream>
#include <iomanip>
#include <iterator>

namespace libk3dgraphviz
{

/////////////////////////////////////////////////////////////////////////////
// render_engine

class render_engine :
	public k3d::persistent<k3d::object>,
	public k3d::istill_render_engine
{
	typedef k3d::persistent<k3d::object> base;

public:
	render_engine(k3d::idocument& Document) :
		base(Document),
		m_render_engine(k3d::init_name("render_engine") + k3d::init_description("Render Engine [string]") + k3d::init_value(std::string("dot")) + k3d::init_document(Document) + k3d::init_values(render_engine_values()))
	{
		enable_serialization(k3d::persistence::proxy(m_render_engine));
		register_property(m_render_engine);
	}

	bool render_preview()
	{
		// Start a new render job ...
		k3d::irender_job& job = k3d::application().render_farm().create_job("k3d-preview");

		// Add a single render frame to the job ...
		k3d::irender_frame& frame = job.create_frame("frame");

		// Create an output image path ...
		const boost::filesystem::path outputimagepath = frame.add_output_file("world.ps");
		return_val_if_fail(!outputimagepath.empty(), false);

		// View the output image when it's done ...
		frame.add_view_operation(outputimagepath);

		// Render it ...
		return_val_if_fail(render(frame, outputimagepath), false);

		// Start the job running ...
		k3d::application().render_farm().start_job(job);

		return true;
	}

	bool render_frame(const boost::filesystem::path& OutputImage, const bool ViewImage)
	{
		// Sanity checks ...
		return_val_if_fail(!OutputImage.empty(), false);

		// Start a new render job ...
		k3d::irender_job& job = k3d::application().render_farm().create_job("k3d-render-frame");

		// Add a single render frame to the job ...
		k3d::irender_frame& frame = job.create_frame("frame");

		// Create an output image path ...
		const boost::filesystem::path outputimagepath = frame.add_output_file("world.ps");
		return_val_if_fail(!outputimagepath.empty(), false);

		// Copy the output image to its requested destination ...
		frame.add_copy_operation(outputimagepath, OutputImage);

		// View the output image when it's done ...
		if(ViewImage)
			frame.add_view_operation(OutputImage);

		// Render it ...
		return_val_if_fail(render(frame, outputimagepath), false);

		// Start the job running ...
		k3d::application().render_farm().start_job(job);

		return true;
	}

	k3d::iplugin_factory& factory()
	{
		return get_factory();
	}

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<render_engine>,
			k3d::interface_list<k3d::istill_render_engine> > factory(
			k3d::uuid(0xbe72cb50, 0x011f41d8, 0x90449ae0, 0x4c24ace5),
			"GraphVizEngine",
			"GraphViz Render Engine",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	template<typename T>
	static unsigned long to_integer(const T RHS)
	{
		// If this fails, it means that a pointer can't fit in a long int on your platform - you need to adjust the return type of this function
		BOOST_STATIC_ASSERT(sizeof(T) <= sizeof(unsigned long));
		
		return reinterpret_cast<unsigned long>(RHS);
	}
	
	static const std::string escaped_string(const std::string& Source)
	{
		std::string result(Source);
		
		for(std::string::size_type i = result.find('\"'); i != std::string::npos; i = result.find('\"', i+2))
			result.replace(i, 1, "\\\"");
		
		return result;
	}
	
	bool render(k3d::irender_frame& Frame, const boost::filesystem::path& OutputImagePath)
	{
		// Sanity checks ...
		return_val_if_fail(!OutputImagePath.empty(), false);

		// Start our GraphViz DOT file ...
		const boost::filesystem::path filepath = Frame.add_input_file("world.dot");
		return_val_if_fail(!filepath.empty(), false);

		// Open the DOT file stream ...
		boost::filesystem::ofstream stream(filepath);
		return_val_if_fail(stream.good(), false);

		// Setup the frame for GraphViz rendering ...
		Frame.add_render_operation("graphviz", m_render_engine.property_value(), filepath, false);

		stream << "digraph \"" << document().title() << "\"\n";
		stream << "{\n\n";
		
		stream << "rankdir=TD\n\n";
		
		stream << "node [shape=box,style=filled,width=0,height=0]\n\n";
		
		// Make a mapping of properties-to-objects as we go ...
		std::map<k3d::iproperty*, k3d::iobject*> object_map;
		
		// Create nodes for every document object ...
		const k3d::objects_t objects = document().objects().collection();
		for(k3d::objects_t::iterator object = objects.begin(); object != objects.end(); ++object)
			{
				stream << to_integer(*object) << " [label=\"" << escaped_string((*object)->name()) << "\"]\n";
				
				k3d::iproperty_collection* const property_collection = dynamic_cast<k3d::iproperty_collection*>(*object);
				if(property_collection)
					{
						const k3d::iproperty_collection::properties_t properties = property_collection->properties();
						for(k3d::iproperty_collection::properties_t::const_iterator property = properties.begin(); property != properties.end(); ++property)
							{
								object_map.insert(std::make_pair(*property, *object));
								
								// Show references between objects ...
								if(typeid(k3d::iobject*) == (*property)->type())
									{
										k3d::iobject* const referenced_object = boost::any_cast<k3d::iobject*>((*property)->value());
										if(referenced_object)
											{
												stream << to_integer(*object) << " -> " << to_integer(referenced_object);
												stream << " [style=dotted,label=\"" << escaped_string((*property)->name()) << "\"]\n";
											}
									}
							}
					}
			}

		// Show property dependencies ...			
		stream << "\n";	
		const k3d::idag::dependencies_t dependencies = document().dag().dependencies();
		for(k3d::idag::dependencies_t::const_iterator dependency = dependencies.begin(); dependency != dependencies.end(); ++dependency)
			{
				if(dependency->first && dependency->second)
					{
						stream << to_integer(object_map[dependency->second]) << " -> " << to_integer(object_map[dependency->first]);
						stream << " [headlabel=\"" << escaped_string(dependency->first->name()) << "\" taillabel=\"" << escaped_string(dependency->second->name()) << "\"]\n";
					}
			}
		
		stream << "\n}\n";

		return true;
	}

	k3d_list_property(std::string, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_render_engine;
	
	const k3d::ilist_property<std::string>::values_t& render_engine_values()
	{
		static k3d::ilist_property<std::string>::values_t values;
		if(values.empty())
			{
				const k3d::ioptions::render_engines_t engines = k3d::application().options().render_engines();
				for(k3d::ioptions::render_engines_t::const_iterator engine = engines.begin(); engine != engines.end(); ++engine)
					{
						if(engine->type == "graphviz")
							values.push_back(engine->engine);
					}
			}
		return values;
	}
};

} // namespace libk3dgraphviz


K3D_MODULE_START(k3d::uuid(0x11643e3b, 0xf8ca4564, 0xba9a7358, 0x096291c1), Registry)
	Registry.register_factory(libk3dgraphviz::render_engine::get_factory());
K3D_MODULE_END

