#include "idag.h"
#include "idocument.h"
#include "igeometry_read_format.h"
#include "igeometry_write_format.h"
#include "iobject_collection.h"
#include "iproperty.h"
#include "iproperty_collection.h"
#include "iobject.h"
#include "property.h"
#include "result.h"
#include "serialization.h"

#include <sdpxml/sdpxml.h>
#include <boost/lexical_cast.hpp>

namespace
{

class save_dependencies
{
public:
	typedef std::map<k3d::iproperty*, k3d::iobject*> map_t;

	save_dependencies(map_t& Map, sdpxml::Element& DAGXML) :
		m_map(Map),
		m_dag_xml(DAGXML)
	{
	}
	
	void operator()(const k3d::idag::dependency_t& Dependency)
	{
		// Sanity checks ...
		k3d::iproperty* const from_property = Dependency.second;
		if(!from_property)
			return;
		k3d::iobject* const from_object = m_map[from_property];
		return_if_fail(from_object);
			
		k3d::iproperty* const to_property = Dependency.first;
		return_if_fail(to_property);
		k3d::iobject* const to_object = m_map[to_property];
		return_if_fail(to_object);
		
		m_dag_xml.Append(
			sdpxml::Element("dependency", "",
				sdpxml::Attribute("from_object", boost::lexical_cast<sdpString>(from_object->id())),
				sdpxml::Attribute("from_property", from_property->name()),
				sdpxml::Attribute("to_object", boost::lexical_cast<sdpString>(to_object->id())),
				sdpxml::Attribute("to_property", to_property->name())));
	}	

private:
	map_t& m_map;
	sdpxml::Element& m_dag_xml;
};

class load_dependencies
{
public:
	typedef std::map<k3d::iobject::id_type, k3d::iobject*> map_t;

	load_dependencies(map_t& Map, k3d::idag::dependencies_t& Dependencies) :
		m_map(Map),
		m_dependencies(Dependencies)
	{
	}

	void operator()(const sdpxml::Element& Dependency)
	{
		if("dependency" != Dependency.Name())
			return;
			
		const k3d::iobject::id_type from_object_id = sdpxml::GetAttribute(Dependency, "from_object", 0);
		return_if_fail(from_object_id);
		k3d::iobject* const from_object = m_map[from_object_id];
		if(!from_object)
			{
				std::cerr << error << "Missing dependency source object [" << from_object_id << "]" << std::endl;
				return;
			}
			
		sdpString from_property_name;
		return_if_fail(sdpxml::ParseAttribute(Dependency, "from_property", from_property_name));
		k3d::iproperty* const from_property = k3d::get_property(*from_object, from_property_name);
		if(!from_property)
			{
				std::cerr << error << "Missing dependency source property [" << from_object->name() << "." << from_property_name << "]" << std::endl;
				return;
			}
		
		const k3d::iobject::id_type to_object_id = sdpxml::GetAttribute(Dependency, "to_object", 0);
		return_if_fail(to_object_id);
		k3d::iobject* const to_object = m_map[to_object_id];
		if(!to_object)
			{
				std::cerr << error << "Missing dependency target object [" << to_object_id << "]" << std::endl;
				return;
			}
		
		sdpString to_property_name;
		return_if_fail(sdpxml::ParseAttribute(Dependency, "to_property", to_property_name));
		k3d::iproperty* const to_property = k3d::get_property(*to_object, to_property_name);
		if(!to_property)
			{
				std::cerr << error << "Missing dependency target property [" << to_object->name() << "." << to_property_name << "]" << std::endl;
				return;
			}
		
		m_dependencies[to_property] = from_property;		
	}

private:
	map_t& m_map;
	k3d::idag::dependencies_t& m_dependencies;
};

} // namespace

namespace k3d
{

void save_dag(idocument& Document, sdpxml::Element& XML)
{
	// Create a mapping of properties to objects ...
	save_dependencies::map_t object_map;
	const iobject_collection::objects_t& objects = Document.objects().collection();
	for(iobject_collection::objects_t::const_iterator object = objects.begin(); object != objects.end(); ++object)
		{
			iproperty_collection* const property_collection = dynamic_cast<iproperty_collection*>(*object);
			if(!property_collection)
				continue;

			const iproperty_collection::properties_t properties(property_collection->properties());
			for(iproperty_collection::properties_t::const_iterator property = properties.begin(); property != properties.end(); ++property)
				object_map[*property] = *object;
		}
	
	// Save all dependencies
	sdpxml::Element& dag_xml = XML.Append(sdpxml::Element("dag"));
	std::for_each(Document.dag().dependencies().begin(), Document.dag().dependencies().end(), save_dependencies(object_map, dag_xml));
}

void load_dag(idocument& Document, sdpxml::Element& XML)
{
	// If we don't have any DAG information, we're done ...
	sdpxml::Element* const dag_xml = sdpxml::FindElement(XML, sdpxml::SameName("dag"));
	if(!dag_xml)
		return;

	// Create a mapping of IDs to objects ...
	load_dependencies::map_t object_map;
	const iobject_collection::objects_t& objects = Document.objects().collection();
	for(iobject_collection::objects_t::const_iterator object = objects.begin(); object != objects.end(); ++object)
		object_map[(*object)->id()] = *object;
	
	// Load data and update the DAG ...
	idag::dependencies_t dependencies;
	std::for_each(dag_xml->Children().begin(), dag_xml->Children().end(), load_dependencies(object_map, dependencies));
	Document.dag().set_dependencies(dependencies);
}

bool import_file(idocument& Document, igeometry_read_format& FormatFilter, const boost::filesystem::path& File)
{
	// Give the format a chance to look at the file, first ...
	if(!FormatFilter.pre_read(Document, File))
		return false;

	// Prompt the user for any options before reading ...
	if(!FormatFilter.read_options(Document, File))
		return false;

	return FormatFilter.read_file(Document, File);
}

bool export_file(idocument& Document, igeometry_write_format& FormatFilter, const boost::filesystem::path& File)
{
	// Give the format a chance to look at the file, first ...
	if(!FormatFilter.pre_write(Document, File))
		return false;

	// Prompt the user for any options before writing ...
	if(!FormatFilter.write_options(Document, File))
		return false;

	// Write the file ...
	return FormatFilter.write_file(Document, File);
}

} // namespace k3d

