// 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 Timothy M. Shead (tshead@k-3d.com)
*/

#include <k3dsdk/classes.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/mesh_filter.h>
#include <k3dsdk/module.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/renderman.h>
#include <k3dsdk/selection.h>
#include <k3dsdk/string_cast.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/transform.h>
#include <k3dsdk/xml_utility.h>

#include <iterator>

namespace libk3dmesh
{

/////////////////////////////////////////////////////////////////////////////
// frozen_mesh_implementation

class frozen_mesh_implementation :
	public k3d::mesh_filter<k3d::persistent<k3d::object> >
{
	typedef k3d::mesh_filter<k3d::persistent<k3d::object> > base;

public:
	frozen_mesh_implementation(k3d::idocument& Document) :
		base(Document)
	{
		m_input_mesh.changed_signal().connect(SigC::slot(*this, &frozen_mesh_implementation::on_reset_geometry));
		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &frozen_mesh_implementation::on_create_geometry));
	}

	void on_reset_geometry()
	{
//		m_output_mesh.reset();
	}

	k3d::mesh* on_create_geometry()
	{
		// If we don't have any input data, we're done ...
		k3d::mesh* const input = m_input_mesh.property_value();
		if(!input)
			return 0;

		// Create mutable output geometry ...
		k3d::mesh* const output = new k3d::mesh();

		// Make a copy of the input geometry ...
		k3d::deep_copy(*input, *output);
		
		return output;
	}

	template<typename data_t>
	bool load_parameter(const std::string& XMLType, const std::string& Name, const std::string& Type, const std::string& Value, k3d::parameters_t& Parameters)
	{
		if(XMLType != Type)
			return false;

		Parameters[Name] = k3d::from_string<data_t>(Value, data_t());	
		return true;
	}

	void load_parameters(const sdpxml::Element& Element, const k3d::ri::storage_class_t StorageClass, k3d::parameters_t& Parameters)
	{
		for(sdpxml::ElementCollection::const_iterator xml_parameters = Element.Children().begin(); xml_parameters != Element.Children().end(); ++xml_parameters)
			{
				if(xml_parameters->Name() != "parameters")
					continue;
					
				for(sdpxml::ElementCollection::const_iterator xml_parameter = xml_parameters->Children().begin(); xml_parameter != xml_parameters->Children().end(); ++xml_parameter)
					{
						if(xml_parameter->Name() != "parameter")
							continue;
							
						const std::string name = sdpxml::GetAttribute(*xml_parameter, "name", std::string());
						const std::string type = sdpxml::GetAttribute(*xml_parameter, "type", std::string());
						const std::string value = sdpxml::GetAttribute(*xml_parameter, "value", std::string());
						
						if(name.empty())
							{
								std::cerr << error << k3d_file_reference() << " unnamed parameter will not be loaded" << std::endl;
								continue;
							}
							
						if(type.empty())
							{
								std::cerr << error << k3d_file_reference() << " parameter [" << name << "] with unknown type will not be loaded" << std::endl;
								continue;
							}
							
						if(load_parameter<k3d::ri::integer>("integer", name, type, value, Parameters)) continue; 
						if(load_parameter<k3d::ri::real>("real", name, type, value, Parameters)) continue; 
						if(load_parameter<k3d::ri::string>("string", name, type, value, Parameters)) continue; 
						if(load_parameter<k3d::ri::point>("point", name, type, value, Parameters)) continue; 
						if(load_parameter<k3d::ri::vector>("vector", name, type, value, Parameters)) continue; 
						if(load_parameter<k3d::ri::normal>("normal", name, type, value, Parameters)) continue; 
						if(load_parameter<k3d::ri::color>("color", name, type, value, Parameters)) continue; 
						if(load_parameter<k3d::ri::hpoint>("hpoint", name, type, value, Parameters)) continue; 
				//		if(load_parameter<k3d::ri::matrix>("matrix", name, type, value, Parameters)) continue;
				
						std::cerr << error << k3d_file_reference() << " parameter [" << name << "] with unsupported type [" << type << "] will not be loaded" << std::endl;
					}
			}
	}

	void load_legacy_data(sdpxml::Document& Document, sdpxml::Element& Element, k3d::mesh& Mesh)
	{
		// Load points ...
		std::map<unsigned long, k3d::point*> point_map;
		sdpxml::Element* const xml_points = sdpxml::FindElement(Element, sdpxml::SameName("points"));
		if(xml_points)
			{
				for(sdpxml::ElementCollection::const_iterator xml_point = xml_points->Children().begin(); xml_point != xml_points->Children().end(); ++xml_point)
					{
						if(xml_point->Name() != "point")
							continue;
						
						const unsigned long id = sdpxml::GetAttribute(*xml_point, "id", 0UL);
						return_if_fail(id);

						Mesh.points.push_back(new k3d::point(sdpxml::GetAttribute(*xml_point, "position", k3d::vector3(0, 0, 0))));
						point_map[id] = Mesh.points.back();
					}
			}
			
		// Load polygons into a single, non-solid polyhedron ...
		sdpxml::Element* const xml_paths = sdpxml::FindElement(Element, sdpxml::SameName("paths"));
		if(xml_paths)
			{
				k3d::polyhedron* const polyhedron = new k3d::polyhedron();
				Mesh.polyhedra.push_back(polyhedron);

				const std::string xml_rendermode = sdpxml::GetAttribute(k3d::xml::safe_element(k3d::xml::safe_element(Element, "variables"), sdpxml::Element("variable", "", sdpxml::Attribute("name", "rendermode"))), "value", std::string("normal"));
				polyhedron->type = ("subdivision" == xml_rendermode) ? k3d::polyhedron::CATMULL_CLARK_SUBDIVISION_MESH : k3d::polyhedron::POLYGONS;
												
				for(sdpxml::ElementCollection::iterator xml_path = xml_paths->Children().begin(); xml_path != xml_paths->Children().end(); ++xml_path)
					{
						if(xml_path->Name() != "path")
							continue;

						polyhedron->material = reinterpret_cast<k3d::imaterial*>(sdpxml::GetAttribute(*xml_path, "material", 0UL));
														
						sdpxml::Element* const xml_pathpoints = sdpxml::FindElement(*xml_path, sdpxml::SameName("pathpoints"));
						if(xml_pathpoints)
							{				
								k3d::polyhedron::edges_t new_edges;
								for(sdpxml::ElementCollection::iterator xml_pathpoint = xml_pathpoints->Children().begin(); xml_pathpoint != xml_pathpoints->Children().end(); ++xml_pathpoint)
									{
										if(xml_pathpoint->Name() != "pathpoint")
											continue;
											
										const unsigned long point_id = sdpxml::GetAttribute(*xml_pathpoint, "pointid", 0UL);
										return_if_fail(point_map.count(point_id));
						
										new_edges.push_back(new k3d::split_edge(point_map[point_id]));
									}
								k3d::loop_edges(new_edges.begin(), new_edges.end());
				
								polyhedron->edges.insert(polyhedron->edges.end(), new_edges.begin(), new_edges.end());
								polyhedron->faces.push_back(new k3d::face(new_edges.front()));
							}
					}
			}
	}

	void load(sdpxml::Document& Document, sdpxml::Element& Element)
	{
		base::load(Document, Element);

		// Create a new mesh ...
		k3d::mesh* const mesh = new k3d::mesh();
		m_output_mesh.reset(mesh);
						
		// Load legacy data from the original Mesh object
		load_legacy_data(Document, Element, *mesh);
		
		// Load the stored mesh data ...
		sdpxml::Element* const xml_mesh = sdpxml::FindElement(Element, sdpxml::SameName("mesh"));
		if(!xml_mesh)
			return;
		
		// Load points ...
		sdpxml::Element* const xml_points = sdpxml::FindElement(*xml_mesh, sdpxml::SameName("points"));
		if(xml_points)
			{
				for(sdpxml::ElementCollection::const_iterator xml_point = xml_points->Children().begin(); xml_point != xml_points->Children().end(); ++xml_point)
					{
						if(xml_point->Name() != "point")
							continue;
							
						mesh->points.push_back(new k3d::point(sdpxml::GetAttribute(*xml_point, "position", k3d::vector3(0, 0, 0))));
						load_parameters(*xml_point, k3d::ri::VERTEX, mesh->points.back()->vertex_data);
					}
			}
			
		// Load point groups ...
		sdpxml::Element* const xml_point_groups = sdpxml::FindElement(*xml_mesh, sdpxml::SameName("pointgroups"));
		if(xml_point_groups)
			{
				for(sdpxml::ElementCollection::iterator xml_group = xml_point_groups->Children().begin(); xml_group != xml_point_groups->Children().end(); ++xml_group)
					{
						if(xml_group->Name() != "group")
							continue;
						
						k3d::point_group* const group = new k3d::point_group();
						group->material = reinterpret_cast<k3d::imaterial*>(sdpxml::GetAttribute(*xml_group, "material", 0UL));
						mesh->point_groups.push_back(group);
							
						sdpxml::Element* const xml_points = sdpxml::FindElement(*xml_group, sdpxml::SameName("points"));
						if(xml_points)
							{
								std::istringstream points_buffer(xml_points->Text());
								for(std::istream_iterator<unsigned long> point(points_buffer); point != std::istream_iterator<unsigned long>(); ++point)
									{
										const unsigned long point_index = *point - 1;
										return_if_fail(point_index < mesh->points.size());
										group->points.push_back(mesh->points[point_index]);
									}
							}
					}
			}
	
		// Load polyhedra ...
		sdpxml::Element* const xml_polyhedra = sdpxml::FindElement(*xml_mesh, sdpxml::SameName("polyhedra"));
		if(xml_polyhedra)
			{
				for(sdpxml::ElementCollection::iterator xml_polyhedron = xml_polyhedra->Children().begin(); xml_polyhedron != xml_polyhedra->Children().end(); ++xml_polyhedron)
					{
						if(xml_polyhedron->Name() != "polyhedron")
							continue;

						k3d::polyhedron* const polyhedron = new k3d::polyhedron();
						
						polyhedron->type = k3d::polyhedron::POLYGONS;
						if(!sdpxml::ParseAttribute(*xml_polyhedron, "type", polyhedron->type))
							{
								std::cerr << warning << "Defaulting polyhedron type to POLYGONS" << std::endl;
							}

						polyhedron->material = reinterpret_cast<k3d::imaterial*>(sdpxml::GetAttribute(*xml_polyhedron, "material", 0UL));
						mesh->polyhedra.push_back(polyhedron);

						// Load edges ...
						sdpxml::Element* const xml_edges = sdpxml::FindElement(*xml_polyhedron, sdpxml::SameName("edges"));
						if(xml_edges)
							{
								const unsigned long edge_count = std::count_if(xml_edges->Children().begin(), xml_edges->Children().end(), sdpxml::SameName("edge"));
								k3d::polyhedron::edges_t edges(edge_count);
								for(k3d::polyhedron::edges_t::iterator edge = edges.begin(); edge != edges.end(); ++edge)
									*edge = new k3d::split_edge(0, 0, 0);
								

								k3d::polyhedron::edges_t::iterator edge = edges.begin();
								for(sdpxml::ElementCollection::iterator xml_edge = xml_edges->Children().begin(); xml_edge != xml_edges->Children().end(); ++xml_edge)
									{
										if(xml_edge->Name() != "edge")
											continue;

										unsigned long vertex_index = 0;
										return_if_fail(sdpxml::ParseAttribute(*xml_edge, "vertex", vertex_index));
										return_if_fail(vertex_index <= mesh->points.size());

										unsigned long face_clockwise_index = 0;
										return_if_fail(sdpxml::ParseAttribute(*xml_edge, "faceclockwise", face_clockwise_index));
										return_if_fail(face_clockwise_index <= edges.size());

										unsigned long companion_index = 0;
										return_if_fail(sdpxml::ParseAttribute(*xml_edge, "companion", companion_index));
										return_if_fail(companion_index <= edges.size());

										if(vertex_index)
											(*edge)->vertex = mesh->points[vertex_index-1];

										if(face_clockwise_index)
											(*edge)->face_clockwise = edges[face_clockwise_index-1];

										if(companion_index)
											(*edge)->companion = edges[companion_index-1];

										++edge;
									}

								polyhedron->edges.swap(edges);
							}

						// Load faces ...
						sdpxml::Element* const xml_faces = sdpxml::FindElement(*xml_polyhedron, sdpxml::SameName("faces"));
						if(xml_faces)
							{
								for(sdpxml::ElementCollection::iterator xml_face = xml_faces->Children().begin(); xml_face != xml_faces->Children().end(); ++xml_face)
									{
										if(xml_face->Name() != "face")
											continue;

										unsigned long first_edge_index = 0;
										return_if_fail(sdpxml::ParseAttribute(*xml_face, "firstedge", first_edge_index));
										return_if_fail(first_edge_index <= polyhedron->edges.size());

										k3d::face* const face = new k3d::face(first_edge_index ? polyhedron->edges[first_edge_index-1] : 0);
										polyhedron->faces.push_back(face);
										
										sdpxml::Element* const xml_holes = sdpxml::FindElement(*xml_face, sdpxml::SameName("holes"));
										if(xml_holes)
											{
												for(sdpxml::ElementCollection::iterator xml_hole = xml_holes->Children().begin(); xml_hole != xml_holes->Children().end(); ++xml_hole)
													{
														if(xml_hole->Name() != "hole")
															continue;

														unsigned long first_edge_index = 0;
														return_if_fail(sdpxml::ParseAttribute(*xml_hole, "firstedge", first_edge_index));
														return_if_fail(first_edge_index <= polyhedron->edges.size());
														
														face->holes.push_back(first_edge_index ? polyhedron->edges[first_edge_index-1] : 0);
														                                                                                                                                                                                                                                                                                                                                                      
/*															
														k3d::face::hole* const hole = new k3d::face::hole();
														face->holes.push_back(hole);
		
														sdpxml::Element* const xml_vertices = sdpxml::FindElement(*xml_hole, sdpxml::SameName("vertices"));
														if(xml_vertices)
															{												
																std::istringstream vertices_buffer(xml_vertices->Text());
																for(std::istream_iterator<unsigned long> vertex(vertices_buffer); vertex != std::istream_iterator<unsigned long>(); ++vertex)
																	{
																		const unsigned long vertex_index = *vertex - 1;
																		return_if_fail(vertex_index < mesh->points.size());
																		hole->vertices.push_back(mesh->points[vertex_index]);
																	}
															}
*/
													}
											}
									}
							}
					}
			}
			
		// Load linear curve groups ...
		sdpxml::Element* const xml_linear_curve_groups = sdpxml::FindElement(*xml_mesh, sdpxml::SameName("linearcurvegroups"));
		if(xml_linear_curve_groups)
			{
				for(sdpxml::ElementCollection::iterator xml_group = xml_linear_curve_groups->Children().begin(); xml_group != xml_linear_curve_groups->Children().end(); ++xml_group)
					{
						if(xml_group->Name() != "group")
							continue;
							
						k3d::linear_curve_group* const group = new k3d::linear_curve_group();
						group->material = reinterpret_cast<k3d::imaterial*>(sdpxml::GetAttribute(*xml_group, "material", 0UL));
						mesh->linear_curve_groups.push_back(group);
						
						sdpxml::Element* const xml_curves = sdpxml::FindElement(*xml_group, sdpxml::SameName("curves"));
						if(!xml_curves)
							continue;
							
						for(sdpxml::ElementCollection::iterator xml_curve = xml_curves->Children().begin(); xml_curve != xml_curves->Children().end(); ++xml_curve)
							{
								if(xml_curve->Name() != "curve")
									continue;
									
								k3d::linear_curve* const curve = new k3d::linear_curve();
								group->curves.push_back(curve);
								
								sdpxml::Element* const xml_control_points = sdpxml::FindElement(*xml_curve, sdpxml::SameName("controlpoints"));
								if(!xml_control_points)
									continue;

								std::istringstream points_buffer(xml_control_points->Text());
								for(std::istream_iterator<unsigned long> control_point(points_buffer); control_point != std::istream_iterator<unsigned long>(); ++control_point)
									{
										const unsigned long control_point_index = *control_point - 1;
										return_if_fail(control_point_index < mesh->points.size());
										curve->control_points.push_back(mesh->points[control_point_index]);
									}
							}
					}
			}

		// Load cubic curve groups ...
		sdpxml::Element* const xml_cubic_curve_groups = sdpxml::FindElement(*xml_mesh, sdpxml::SameName("cubiccurvegroups"));
		if(xml_cubic_curve_groups)
			{
				for(sdpxml::ElementCollection::iterator xml_group = xml_cubic_curve_groups->Children().begin(); xml_group != xml_cubic_curve_groups->Children().end(); ++xml_group)
					{
						if(xml_group->Name() != "group")
							continue;
							
						k3d::cubic_curve_group* const group = new k3d::cubic_curve_group();
						group->material = reinterpret_cast<k3d::imaterial*>(sdpxml::GetAttribute(*xml_group, "material", 0UL));
						mesh->cubic_curve_groups.push_back(group);
						
						sdpxml::Element* const xml_curves = sdpxml::FindElement(*xml_group, sdpxml::SameName("curves"));
						if(!xml_curves)
							continue;
							
						for(sdpxml::ElementCollection::iterator xml_curve = xml_curves->Children().begin(); xml_curve != xml_curves->Children().end(); ++xml_curve)
							{
								if(xml_curve->Name() != "curve")
									continue;
									
								k3d::cubic_curve* const curve = new k3d::cubic_curve();
								group->curves.push_back(curve);
								
								sdpxml::Element* const xml_control_points = sdpxml::FindElement(*xml_curve, sdpxml::SameName("controlpoints"));
								if(!xml_control_points)
									continue;

								std::istringstream points_buffer(xml_control_points->Text());
								for(std::istream_iterator<unsigned long> control_point(points_buffer); control_point != std::istream_iterator<unsigned long>(); ++control_point)
									{
										const unsigned long control_point_index = *control_point - 1;
										return_if_fail(control_point_index < mesh->points.size());
										curve->control_points.push_back(mesh->points[control_point_index]);
									}
							}
					}
			}

		// Load NURBS curve groups ...
		sdpxml::Element* const xml_nucurve_groups = sdpxml::FindElement(*xml_mesh, sdpxml::SameName("nucurvegroups"));
		if(xml_nucurve_groups)
			{
				for(sdpxml::ElementCollection::iterator xml_group = xml_nucurve_groups->Children().begin(); xml_group != xml_nucurve_groups->Children().end(); ++xml_group)
					{
						if(xml_group->Name() != "group")
							continue;
							
						k3d::nucurve_group* const group = new k3d::nucurve_group();
						group->material = reinterpret_cast<k3d::imaterial*>(sdpxml::GetAttribute(*xml_group, "material", 0UL));
						mesh->nucurve_groups.push_back(group);
						
						sdpxml::Element* const xml_curves = sdpxml::FindElement(*xml_group, sdpxml::SameName("curves"));
						if(!xml_curves)
							continue;
						
						for(sdpxml::ElementCollection::iterator xml_curve = xml_curves->Children().begin(); xml_curve != xml_curves->Children().end(); ++xml_curve)
							{
								if(xml_curve->Name() != "curve")
									continue;

								sdpxml::Element* const xml_knot_vector = sdpxml::FindElement(*xml_curve, sdpxml::SameName("knotvector"));
								sdpxml::Element* const xml_control_points = sdpxml::FindElement(*xml_curve, sdpxml::SameName("controlpoints"));
								sdpxml::Element* const xml_weights = sdpxml::FindElement(*xml_curve, sdpxml::SameName("weights"));

								if(xml_knot_vector && xml_control_points && xml_weights)
									{
										k3d::nucurve* const curve = new k3d::nucurve();
										curve->order = sdpxml::GetAttribute(*xml_curve, "order", 0);
										group->curves.push_back(curve);

										std::istringstream knots_buffer(xml_knot_vector->Text());
										std::copy(std::istream_iterator<double>(knots_buffer), std::istream_iterator<double>(), std::back_inserter(curve->knots));

										std::istringstream points_buffer(xml_control_points->Text());
										std::istringstream weights_buffer(xml_weights->Text());

										std::istream_iterator<unsigned long> control_point(points_buffer);
										std::istream_iterator<double> weight(weights_buffer);
										for(; control_point != std::istream_iterator<unsigned long>() && weight != std::istream_iterator<double>(); ++control_point, ++weight)
											{
												const unsigned long control_point_index = *control_point - 1;
												return_if_fail(control_point_index < mesh->points.size());
												curve->control_points.push_back(k3d::nucurve::control_point(mesh->points[control_point_index], *weight));
											}
									}
							}
					}
			}
	

		// Load bilinear patches ...
		sdpxml::Element* const xml_bilinear_patches = sdpxml::FindElement(*xml_mesh, sdpxml::SameName("bilinearpatches"));
		if(xml_bilinear_patches)
			{
				for(sdpxml::ElementCollection::iterator xml_patch = xml_bilinear_patches->Children().begin(); xml_patch != xml_bilinear_patches->Children().end(); ++xml_patch)
					{
						if(xml_patch->Name() != "patch")
							continue;
							
						k3d::bilinear_patch* const patch = new k3d::bilinear_patch();
						patch->material = reinterpret_cast<k3d::imaterial*>(sdpxml::GetAttribute(*xml_patch, "material", 0UL));
						mesh->bilinear_patches.push_back(patch);
						
						sdpxml::Element* const xml_control_points = sdpxml::FindElement(*xml_patch, sdpxml::SameName("controlpoints"));
						if(!xml_control_points)
							continue;

						unsigned long storage_index = 0;
						std::istringstream points_buffer(xml_control_points->Text());
						for(std::istream_iterator<unsigned long> control_point(points_buffer); control_point != std::istream_iterator<unsigned long>(); ++control_point)
							{
								return_if_fail(storage_index < 4);
								
								const unsigned long control_point_index = *control_point - 1;
								return_if_fail(control_point_index < mesh->points.size());
								patch->control_points[storage_index++] = mesh->points[control_point_index];
							}
					}
			}
		
		// Load bicubic patches ...
		sdpxml::Element* const xml_bicubic_patches = sdpxml::FindElement(*xml_mesh, sdpxml::SameName("bicubicpatches"));
		if(xml_bicubic_patches)
			{
				for(sdpxml::ElementCollection::iterator xml_patch = xml_bicubic_patches->Children().begin(); xml_patch != xml_bicubic_patches->Children().end(); ++xml_patch)
					{
						if(xml_patch->Name() != "patch")
							continue;
							
						k3d::bicubic_patch* const patch = new k3d::bicubic_patch();
						patch->material = reinterpret_cast<k3d::imaterial*>(sdpxml::GetAttribute(*xml_patch, "material", 0UL));
						mesh->bicubic_patches.push_back(patch);
						
						sdpxml::Element* const xml_control_points = sdpxml::FindElement(*xml_patch, sdpxml::SameName("controlpoints"));
						if(!xml_control_points)
							continue;

						unsigned long storage_index = 0;
						std::istringstream points_buffer(xml_control_points->Text());
						for(std::istream_iterator<unsigned long> control_point(points_buffer); control_point != std::istream_iterator<unsigned long>(); ++control_point)
							{
								return_if_fail(storage_index < 16);
								
								const unsigned long control_point_index = *control_point - 1;
								return_if_fail(control_point_index < mesh->points.size());
								patch->control_points[storage_index++] = mesh->points[control_point_index];
							}
					}
			}

		// Load NURBS patches ...
		sdpxml::Element* const xml_nupatches = sdpxml::FindElement(*xml_mesh, sdpxml::SameName("nupatches"));
		if(xml_nupatches)
			{
				for(sdpxml::ElementCollection::iterator xml_patch = xml_nupatches->Children().begin(); xml_patch != xml_nupatches->Children().end(); ++xml_patch)
					{
						if(xml_patch->Name() != "patch")
							continue;

						sdpxml::Element* const xml_u_knot_vector = sdpxml::FindElement(*xml_patch, sdpxml::SameName("uknotvector"));
						sdpxml::Element* const xml_v_knot_vector = sdpxml::FindElement(*xml_patch, sdpxml::SameName("vknotvector"));
						sdpxml::Element* const xml_control_points = sdpxml::FindElement(*xml_patch, sdpxml::SameName("controlpoints"));
						sdpxml::Element* const xml_weights = sdpxml::FindElement(*xml_patch, sdpxml::SameName("weights"));

						if(xml_u_knot_vector && xml_v_knot_vector && xml_control_points && xml_weights)
							{
								k3d::nupatch* const patch = new k3d::nupatch();
								patch->u_order = sdpxml::GetAttribute(*xml_patch, "uorder", 0);
								patch->v_order = sdpxml::GetAttribute(*xml_patch, "vorder", 0);
								patch->material = reinterpret_cast<k3d::imaterial*>(sdpxml::GetAttribute(*xml_patch, "material", 0UL));
								mesh->nupatches.push_back(patch);

								std::istringstream u_knots_buffer(xml_u_knot_vector->Text());
								std::copy(std::istream_iterator<double>(u_knots_buffer), std::istream_iterator<double>(), std::back_inserter(patch->u_knots));

								std::istringstream v_knots_buffer(xml_v_knot_vector->Text());
								std::copy(std::istream_iterator<double>(v_knots_buffer), std::istream_iterator<double>(), std::back_inserter(patch->v_knots));

								std::istringstream points_buffer(xml_control_points->Text());
								std::istringstream weights_buffer(xml_weights->Text());

								std::istream_iterator<unsigned long> control_point(points_buffer);
								std::istream_iterator<double> weight(weights_buffer);
								for(; control_point != std::istream_iterator<unsigned long>() && weight != std::istream_iterator<double>(); ++control_point, ++weight)
									{
										const unsigned long control_point_index = *control_point - 1;
										return_if_fail(control_point_index < mesh->points.size());
										patch->control_points.push_back(k3d::nupatch::control_point(mesh->points[control_point_index], *weight));
									}
							}
					}
			}
	}

	/// Once the entire document has been loaded, we use this bit of voodoo to convert material object IDs back into interface pointers
	class relink
	{
	public:
		relink(k3d::idocument& Document) : m_objects(Document.objects())
		{
		}
		
		template<typename T>
		void operator()(T* Object)
		{
			Object->material = dynamic_cast<k3d::imaterial*>(k3d::find_object(m_objects, reinterpret_cast<k3d::iobject::id_type>(Object->material)));
		}
		
	private:
		k3d::iobject_collection& m_objects;
	};

	void load_complete()
	{
		base::load_complete();
		
		// If we aren't storing any data, we're done ...
		if(m_output_mesh.empty())
			return;
		k3d::mesh& mesh = *m_output_mesh.value();
			
		// Relink references to materials ...
		std::for_each(mesh.point_groups.begin(), mesh.point_groups.end(), relink(document()));
		std::for_each(mesh.polyhedra.begin(), mesh.polyhedra.end(), relink(document()));
		std::for_each(mesh.linear_curve_groups.begin(), mesh.linear_curve_groups.end(), relink(document()));
		std::for_each(mesh.cubic_curve_groups.begin(), mesh.cubic_curve_groups.end(), relink(document()));
		std::for_each(mesh.nucurve_groups.begin(), mesh.nucurve_groups.end(), relink(document()));
		std::for_each(mesh.bilinear_patches.begin(), mesh.bilinear_patches.end(), relink(document()));
		std::for_each(mesh.bicubic_patches.begin(), mesh.bicubic_patches.end(), relink(document()));
		std::for_each(mesh.nupatches.begin(), mesh.nupatches.end(), relink(document()));
	}

	template<typename data_t, typename iterator_t>
	bool save_parameter(sdpxml::Element& XMLParameters, iterator_t Parameter, const std::string& Type)
	{
		if(typeid(data_t) != Parameter->second.type())
			return false;

		
		XMLParameters.Append(sdpxml::Element("parameter", "", sdpxml::Attribute("name", Parameter->first), sdpxml::Attribute("type", Type), sdpxml::Attribute("value", k3d::string_cast(boost::any_cast<data_t>(Parameter->second)))));

		return true;
	}

	void save_parameters(sdpxml::Element& Element, const k3d::parameters_t& Parameters, const k3d::ri::storage_class_t Type)
	{
		if(Parameters.empty())
			return;

		sdpxml::Element& xml_parameters = Element.Append(sdpxml::Element("parameters", "", sdpxml::Attribute("type", k3d::string_cast(Type))));

		for(k3d::parameters_t::const_iterator parameter = Parameters.begin(); parameter != Parameters.end(); ++parameter)
			{
				if(save_parameter<k3d::ri::integer>(xml_parameters, parameter, "integer")) continue; 
				if(save_parameter<k3d::ri::real>(xml_parameters, parameter, "real")) continue; 
				if(save_parameter<k3d::ri::string>(xml_parameters, parameter, "string")) continue; 
				if(save_parameter<k3d::ri::point>(xml_parameters, parameter, "point")) continue; 
				if(save_parameter<k3d::ri::vector>(xml_parameters, parameter, "vector")) continue; 
				if(save_parameter<k3d::ri::normal>(xml_parameters, parameter, "normal")) continue; 
				if(save_parameter<k3d::ri::color>(xml_parameters, parameter, "color")) continue; 
				if(save_parameter<k3d::ri::hpoint>(xml_parameters, parameter, "hpoint")) continue; 
		//		if(save_parameter<k3d::ri::matrix>(xml_parameters, parameter, "matrix")) continue;

				std::cerr << error << "Unknown parameter type - parameter [" << parameter->first << "] will not be serialized" << std::endl;
			}
	}

	void save(sdpxml::Element& Element, k3d::idependencies& Dependencies)
	{
		base::save(Element, Dependencies);

		// If we aren't storing any data, we're done ...
		if(m_output_mesh.empty())
			return;
			
		// Save our stored mesh data ...
		const k3d::mesh& mesh = *m_output_mesh.value();
		sdpxml::Element& xml_mesh = Element.Append(sdpxml::Element("mesh"));

		// Create a one-based index of points
		std::map<k3d::point*, unsigned long> point_map;
		point_map[0] = 0;
		for(k3d::mesh::points_t::const_iterator point = mesh.points.begin(); point != mesh.points.end(); ++point)
			point_map.insert(std::make_pair(*point, point_map.size()));

		// Save points ...
		if(mesh.points.size())
			{
				sdpxml::Element& xml_points = xml_mesh.Append(sdpxml::Element("points"));
				for(k3d::mesh::points_t::const_iterator point = mesh.points.begin(); point != mesh.points.end(); ++point)
					{
						sdpxml::Element& xml_point = xml_points.Append(sdpxml::Element("point", "", sdpxml::Attribute("position", k3d::string_cast((**point).position))));
						save_parameters(xml_point, (*point)->vertex_data, k3d::ri::VERTEX);
					}
			}

		// Save point groups ...
		if(mesh.point_groups.size())
			{
				sdpxml::Element& xml_point_groups = xml_mesh.Append(sdpxml::Element("pointgroups"));
				for(k3d::mesh::point_groups_t::const_iterator point_group = mesh.point_groups.begin(); point_group != mesh.point_groups.end(); ++point_group)
					{
						k3d::iobject* const material_object = dynamic_cast<k3d::iobject*>((*point_group)->material);
						sdpxml::Element& xml_point_group = xml_point_groups.Append(sdpxml::Element("group", "", sdpxml::Attribute("material", k3d::string_cast(material_object ? material_object->id() : 0))));

						std::ostringstream points_buffer;
						for(k3d::point_group::points_t::const_iterator point = (*point_group)->points.begin(); point != (*point_group)->points.end(); ++point)
							points_buffer << point_map[*point] << " ";
						xml_point_group.Append(sdpxml::Element("points", points_buffer.str()));

						save_parameters(xml_point_group, (*point_group)->constant_data, k3d::ri::CONSTANT);
					}
			}

		// Save polyhedra ...
		if(mesh.polyhedra.size())
			{
				// For each polyhedron ...
				sdpxml::Element& xml_polyhedra = xml_mesh.Append(sdpxml::Element("polyhedra"));
				for(k3d::mesh::polyhedra_t::const_iterator polyhedron = mesh.polyhedra.begin(); polyhedron != mesh.polyhedra.end(); ++polyhedron)
					{
						k3d::iobject* const material_object = dynamic_cast<k3d::iobject*>((*polyhedron)->material);
						sdpxml::Element& xml_polyhedron = xml_polyhedra.Append(sdpxml::Element("polyhedron", "", sdpxml::Attribute("type", k3d::string_cast((*polyhedron)->type)), sdpxml::Attribute("material", k3d::string_cast(material_object ? material_object->id() : 0))));

						// Create a one-based index of edges ...
						std::map<k3d::split_edge*, unsigned long> edge_map;
						edge_map[0] = 0;		
						for(k3d::polyhedron::edges_t::const_iterator edge = (*polyhedron)->edges.begin(); edge != (*polyhedron)->edges.end(); ++edge)
							edge_map.insert(std::make_pair(*edge, edge_map.size()));

						// Save edges ...
						if((*polyhedron)->edges.size())
							{
								sdpxml::Element& xml_edges = xml_polyhedron.Append(sdpxml::Element("edges"));
								for(k3d::polyhedron::edges_t::const_iterator edge = (*polyhedron)->edges.begin(); edge != (*polyhedron)->edges.end(); ++edge)
									{
										sdpxml::Element& xml_edge = xml_edges.Append(
											sdpxml::Element("edge", "",
												sdpxml::Attribute("vertex", k3d::string_cast(point_map[(*edge)->vertex])),
												sdpxml::Attribute("faceclockwise", k3d::string_cast(edge_map[(*edge)->face_clockwise])),
												sdpxml::Attribute("companion", k3d::string_cast(edge_map[(*edge)->companion]))));

										save_parameters(xml_edge, (*edge)->facevarying_data, k3d::ri::FACEVARYING);
									}
							}

						// Save faces ...
						if((*polyhedron)->faces.size())
							{
								sdpxml::Element& xml_faces = xml_polyhedron.Append(sdpxml::Element("faces"));
								for(k3d::polyhedron::faces_t::const_iterator face = (*polyhedron)->faces.begin(); face != (*polyhedron)->faces.end(); ++face)
									{
										sdpxml::Element& xml_face = xml_faces.Append(sdpxml::Element("face", "", sdpxml::Attribute("firstedge", k3d::string_cast(edge_map[(*face)->first_edge]))));
										if((*face)->holes.size())
											{
												sdpxml::Element& xml_holes = xml_face.Append(sdpxml::Element("holes"));
												for(k3d::face::holes_t::const_iterator hole = (*face)->holes.begin(); hole != (*face)->holes.end(); ++hole)
													{
														sdpxml::Element& xml_hole = xml_holes.Append(sdpxml::Element("hole", "", sdpxml::Attribute("firstedge", k3d::string_cast(edge_map[(*hole)]))));
/*
														std::ostringstream points_buffer;
														for(k3d::face::hole::vertices_t::const_iterator vertex = (*hole)->vertices.begin(); vertex != (*hole)->vertices.end(); ++vertex)
															points_buffer << point_map[*vertex] << " ";
														xml_hole.Append(sdpxml::Element("vertices", points_buffer.str()));
*/
													}
											}

										save_parameters(xml_face, (*face)->uniform_data, k3d::ri::UNIFORM);
									}
							}
					}
			}

		// Save linear curve groups ...
		if(mesh.linear_curve_groups.size())
			{
				sdpxml::Element& xml_linear_curve_groups = xml_mesh.Append(sdpxml::Element("linearcurvegroups"));
				for(k3d::mesh::linear_curve_groups_t::const_iterator group = mesh.linear_curve_groups.begin(); group != mesh.linear_curve_groups.end(); ++group)
					{
						k3d::iobject* const material_object = dynamic_cast<k3d::iobject*>((*group)->material);
						sdpxml::Element& xml_linear_curve_group = xml_linear_curve_groups.Append(sdpxml::Element("group", "", sdpxml::Attribute("wrap", k3d::string_cast((*group)->wrap)), sdpxml::Attribute("material", k3d::string_cast(material_object ? material_object->id() : 0))));

						sdpxml::Element& xml_curves = xml_linear_curve_group.Append(sdpxml::Element("curves"));
						for(k3d::linear_curve_group::curves_t::const_iterator curve = (*group)->curves.begin(); curve != (*group)->curves.end(); ++curve)
							{
								sdpxml::Element& xml_curve = xml_curves.Append(sdpxml::Element("curve", ""));
								
								std::ostringstream points_buffer;
								for(k3d::linear_curve::control_points_t::const_iterator control_point = (*curve)->control_points.begin(); control_point != (*curve)->control_points.end(); ++control_point)
									points_buffer << point_map[*control_point] << " ";
								xml_curve.Append(sdpxml::Element("controlpoints", points_buffer.str()));
							}
					}
			}

		// Save cubic curve groups ...
		if(mesh.cubic_curve_groups.size())
			{
				sdpxml::Element& xml_cubic_curve_groups = xml_mesh.Append(sdpxml::Element("cubiccurvegroups"));
				for(k3d::mesh::cubic_curve_groups_t::const_iterator group = mesh.cubic_curve_groups.begin(); group != mesh.cubic_curve_groups.end(); ++group)
					{
						k3d::iobject* const material_object = dynamic_cast<k3d::iobject*>((*group)->material);
						sdpxml::Element& xml_cubic_curve_group = xml_cubic_curve_groups.Append(sdpxml::Element("group", "", sdpxml::Attribute("wrap", k3d::string_cast((*group)->wrap)), sdpxml::Attribute("material", k3d::string_cast(material_object ? material_object->id() : 0))));

						sdpxml::Element& xml_curves = xml_cubic_curve_group.Append(sdpxml::Element("curves"));
						for(k3d::cubic_curve_group::curves_t::const_iterator curve = (*group)->curves.begin(); curve != (*group)->curves.end(); ++curve)
							{
								sdpxml::Element& xml_curve = xml_curves.Append(sdpxml::Element("curve", ""));
								
								std::ostringstream points_buffer;
								for(k3d::cubic_curve::control_points_t::const_iterator control_point = (*curve)->control_points.begin(); control_point != (*curve)->control_points.end(); ++control_point)
									points_buffer << point_map[*control_point] << " ";
								xml_curve.Append(sdpxml::Element("controlpoints", points_buffer.str()));
							}
					}
			}

		// Save NURBS curve groups ...
		if(mesh.nucurve_groups.size())
			{
				sdpxml::Element& xml_nucurve_groups = xml_mesh.Append(sdpxml::Element("nucurvegroups"));
				for(k3d::mesh::nucurve_groups_t::const_iterator group = mesh.nucurve_groups.begin(); group != mesh.nucurve_groups.end(); ++group)
					{
						k3d::iobject* const material_object = dynamic_cast<k3d::iobject*>((*group)->material);
						sdpxml::Element& xml_nucurve_group = xml_nucurve_groups.Append(sdpxml::Element("group", "", sdpxml::Attribute("material", k3d::string_cast(material_object ? material_object->id() : 0))));

						sdpxml::Element& xml_curves = xml_nucurve_group.Append(sdpxml::Element("curves"));
						for(k3d::nucurve_group::curves_t::const_iterator curve = (*group)->curves.begin(); curve != (*group)->curves.end(); ++curve)
							{
								sdpxml::Element& xml_curve = xml_curves.Append(sdpxml::Element("curve", "", sdpxml::Attribute("order", k3d::string_cast((*curve)->order))));

								std::ostringstream knots_buffer;
								std::copy((*curve)->knots.begin(), (*curve)->knots.end(), std::ostream_iterator<double>(knots_buffer, " "));
								xml_curve.Append(sdpxml::Element("knotvector", knots_buffer.str()));
								
								std::ostringstream points_buffer;
								std::ostringstream weights_buffer;
								for(k3d::nucurve::control_points_t::const_iterator control_point = (*curve)->control_points.begin(); control_point != (*curve)->control_points.end(); ++control_point)
									{
										points_buffer << point_map[control_point->position] << " ";
										weights_buffer << control_point->weight << " ";
									}
									
								xml_curve.Append(sdpxml::Element("controlpoints", points_buffer.str()));
								xml_curve.Append(sdpxml::Element("weights", weights_buffer.str()));
							}
					}
			}

		// Save bilinear patches ...
		if(mesh.bilinear_patches.size())
			{
				sdpxml::Element& xml_patches = xml_mesh.Append(sdpxml::Element("bilinearpatches"));
				for(k3d::mesh::bilinear_patches_t::const_iterator patch = mesh.bilinear_patches.begin(); patch != mesh.bilinear_patches.end(); ++patch)
					{
						k3d::iobject* const material_object = dynamic_cast<k3d::iobject*>((*patch)->material);
						sdpxml::Element& xml_patch = xml_patches.Append(sdpxml::Element("patch", "", sdpxml::Attribute("material", k3d::string_cast(material_object ? material_object->id() : 0))));

						std::ostringstream points_buffer;
						for(k3d::bilinear_patch::control_points_t::const_iterator control_point = (*patch)->control_points.begin(); control_point != (*patch)->control_points.end(); ++control_point)
							points_buffer << point_map[*control_point] << " ";
						xml_patch.Append(sdpxml::Element("controlpoints", points_buffer.str()));
					}
			}

		// Save bicubic patches ...
		if(mesh.bicubic_patches.size())
			{
				sdpxml::Element& xml_patches = xml_mesh.Append(sdpxml::Element("bicubicpatches"));
				for(k3d::mesh::bicubic_patches_t::const_iterator patch = mesh.bicubic_patches.begin(); patch != mesh.bicubic_patches.end(); ++patch)
					{
						k3d::iobject* const material_object = dynamic_cast<k3d::iobject*>((*patch)->material);
						sdpxml::Element& xml_patch = xml_patches.Append(sdpxml::Element("patch", "", sdpxml::Attribute("material", k3d::string_cast(material_object ? material_object->id() : 0))));

						std::ostringstream points_buffer;
						for(k3d::bicubic_patch::control_points_t::const_iterator control_point = (*patch)->control_points.begin(); control_point != (*patch)->control_points.end(); ++control_point)
							points_buffer << point_map[*control_point] << " ";
						xml_patch.Append(sdpxml::Element("controlpoints", points_buffer.str()));
					}
			}

		// Save NURBS patches ...
		if(mesh.nupatches.size())
			{
				sdpxml::Element& xml_patches = xml_mesh.Append(sdpxml::Element("nupatches"));
				for(k3d::mesh::nupatches_t::const_iterator patch = mesh.nupatches.begin(); patch != mesh.nupatches.end(); ++patch)
					{
						k3d::iobject* const material_object = dynamic_cast<k3d::iobject*>((*patch)->material);
						sdpxml::Element& xml_patch = xml_patches.Append(sdpxml::Element("patch", "", sdpxml::Attribute("uorder", k3d::string_cast((*patch)->u_order)), sdpxml::Attribute("vorder", k3d::string_cast((*patch)->v_order)), sdpxml::Attribute("material", k3d::string_cast(material_object ? material_object->id() : 0 ))));

						std::ostringstream u_knots_buffer;
						std::copy((*patch)->u_knots.begin(), (*patch)->u_knots.end(), std::ostream_iterator<double>(u_knots_buffer, " "));
						xml_patch.Append(sdpxml::Element("uknotvector", u_knots_buffer.str()));
						
						std::ostringstream v_knots_buffer;
						std::copy((*patch)->v_knots.begin(), (*patch)->v_knots.end(), std::ostream_iterator<double>(v_knots_buffer, " "));
						xml_patch.Append(sdpxml::Element("vknotvector", v_knots_buffer.str()));
						
						std::ostringstream points_buffer;
						std::ostringstream weights_buffer;
						for(k3d::nupatch::control_points_t::const_iterator control_point = (*patch)->control_points.begin(); control_point != (*patch)->control_points.end(); ++control_point)
							{
								points_buffer << point_map[control_point->position] << " ";
								weights_buffer << control_point->weight << " ";
							}
							
						xml_patch.Append(sdpxml::Element("controlpoints", points_buffer.str()));
						xml_patch.Append(sdpxml::Element("weights", weights_buffer.str()));
					}
			}
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<frozen_mesh_implementation>,
			k3d::interface_list<k3d::imesh_source,
			k3d::interface_list<k3d::imesh_sink > > > factory(
			k3d::classes::FrozenMesh(),
			"FrozenMesh",
			"Freezes its input for manual editing",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}
};

/////////////////////////////////////////////////////////////////////////////
// frozen_mesh_factory

k3d::iplugin_factory& frozen_mesh_factory()
{
	return frozen_mesh_implementation::get_factory();
}

} // namespace libk3dmesh

