// 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 Bart Janssens <bart.janssens@lid.kviv.be>
*/

#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/mesh_filter.h>
#include <k3dsdk/module.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/utility.h>

namespace libk3dmesh
{

/////////////////////////////////////////////////////////////////////////////
// join_points_implementation

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

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

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

	k3d::mesh* on_create_geometry()
	{
		// Get the input geometry ...
		k3d::mesh* const input = m_input_mesh.property_value();
		if(!input)
			return 0;

		// Create output geometry ...
		k3d::mesh* const output = new k3d::mesh();
		k3d::deep_copy(*input, *output);

		update_geometry(*output);

		return output;
	}

	/// Functor object that returns true iff the given geometry is selected
	struct is_selected
	{
		template<typename T>
		bool operator()(const T* const Geometry)
		{
			return Geometry->selected;
		}

		bool operator()(const k3d::nucurve::control_point& ControlPoint)
		{
			return ControlPoint.position->selected;
		}

		bool operator()(const k3d::nupatch::control_point& ControlPoint)
		{
			return ControlPoint.position->selected;
		}
	};

	/// Functor object that returns true iff the given geometry links to a selected point
	struct contains_selected_points
	{
		bool operator()(const k3d::linear_curve* const Curve)
		{
			return std::find_if(Curve->control_points.begin(), Curve->control_points.end(), is_selected()) != Curve->control_points.end();
		}

		bool operator()(const k3d::cubic_curve* const Curve)
		{
			return std::find_if(Curve->control_points.begin(), Curve->control_points.end(), is_selected()) != Curve->control_points.end();
		}

		bool operator()(const k3d::nucurve* const Curve)
		{
			return std::find_if(Curve->control_points.begin(), Curve->control_points.end(), is_selected()) != Curve->control_points.end();
		}

		bool operator()(const k3d::bilinear_patch* const Patch)
		{
			return std::find_if(Patch->control_points.begin(), Patch->control_points.end(), is_selected()) != Patch->control_points.end();
		}

		bool operator()(const k3d::bicubic_patch* const Patch)
		{
			return std::find_if(Patch->control_points.begin(), Patch->control_points.end(), is_selected()) != Patch->control_points.end();
		}

		bool operator()(const k3d::nupatch* const Patch)
		{
			return std::find_if(Patch->control_points.begin(), Patch->control_points.end(), is_selected()) != Patch->control_points.end();
		}
	};

	/// "Deletes" geometry by moving it from the Source container to the Disposal container, if the Functor tests true
	template<typename S, typename D, typename F>
	void delete_geometry(S& Source, D& Disposal, F Functor)
	{
		k3d::copy_if(Source.begin(), Source.end(), std::inserter(Disposal, Disposal.end()), Functor);
		Source.erase(std::remove_if(Source.begin(), Source.end(), Functor), Source.end());
	}

	void update_geometry(k3d::mesh& Mesh)
	{
		// We keep track of the geometry we're going to delete ...
		std::set<k3d::point*> points;
		std::set<k3d::face*> faces;
		std::set<k3d::split_edge*> edges;

		// Delete selected faces, taking their edges along with them ...
		for(k3d::mesh::polyhedra_t::iterator p = Mesh.polyhedra.begin(); p != Mesh.polyhedra.end(); ++p)
			{
				k3d::polyhedron& polyhedron = **p;

				for(k3d::polyhedron::faces_t::iterator f = polyhedron.faces.begin(); f != polyhedron.faces.end(); ++f)
					{
						k3d::face* face = *f;
						face->selected = false;
						for(k3d::split_edge* edge = face->first_edge; edge; edge = edge->face_clockwise)
							{
								edge->selected = false;
								if(edge->vertex->selected)
									edges.insert(edge);
								if(edge->face_clockwise == face->first_edge)
									break;
							}
					}
			}

		k3d::point* point = 0;
		for(k3d::mesh::points_t::iterator p = Mesh.points.begin(); p != Mesh.points.end(); ++p)
			{
				if((*p)->selected)
					{
						if(!point)
							point = *p;
						else
							points.insert(*p);
					}
			}

		point->selected = false;

		std::set<k3d::split_edge*> deleted_edges;

		for(std::set<k3d::split_edge*>::iterator edge = edges.begin(); edge != edges.end(); ++edge)
			(*edge)->vertex = point;

		for(std::set<k3d::split_edge*>::iterator e = edges.begin(); e != edges.end(); ++e)
		{
			k3d::split_edge* edge = *e;
			if(edge->vertex == edge->face_clockwise->vertex)
			{
				edge->selected = true;
			}
		}

		edges.clear();

		// Delete selected edges, updating their owning faces and adjacent edges as needed.
		for(k3d::mesh::polyhedra_t::iterator p = Mesh.polyhedra.begin(); p != Mesh.polyhedra.end(); ++p)
			{
				k3d::polyhedron& polyhedron = **p;

				for(k3d::polyhedron::faces_t::iterator f = polyhedron.faces.begin(); f != polyhedron.faces.end(); )
					{
						k3d::face* face = *f;

						std::vector<k3d::split_edge*> remaining_edges;
						for(k3d::split_edge* edge = face->first_edge; edge; edge = edge->face_clockwise)
							{
								if(edge->selected)
									{
										if(edge->companion)
											edge->companion->companion = 0;

										polyhedron.edges.erase(std::remove(polyhedron.edges.begin(), polyhedron.edges.end(), edge), polyhedron.edges.end());
										edges.insert(edge);
									}
								else
									{
										remaining_edges.push_back(edge);
									}

								if(edge->face_clockwise == face->first_edge)
									break;
							}

						for(k3d::face::holes_t::iterator hole = face->holes.begin(); hole != face->holes.end(); )
							{
								std::vector<k3d::split_edge*> remaining_hole_edges;
								for(k3d::split_edge* edge = *hole; edge; edge = edge->face_clockwise)
									{
										if(edge->selected)
											{
												if(edge->companion)
													edge->companion->companion = 0;

												polyhedron.edges.erase(std::remove(polyhedron.edges.begin(), polyhedron.edges.end(), edge), polyhedron.edges.end());
												edges.insert(edge);
											}
										else
											{
												remaining_hole_edges.push_back(edge);
											}

										if(edge->face_clockwise == *hole)
											break;
									}

								// If there aren't any edges left over for this hole, get rid of it ...
								if(remaining_hole_edges.empty())
									{
										hole = face->holes.erase(hole);
									}
								else
									{
										*hole = remaining_hole_edges.front();
										k3d::loop_edges(remaining_hole_edges.begin(), remaining_hole_edges.end());
										++hole;
									}
							}

						// If there aren't any edges left over for this face zap it, and its little holes too ...
						if(remaining_edges.empty())
							{
								f = polyhedron.faces.erase(f);
								faces.insert(face);

								for(k3d::face::holes_t::iterator hole = face->holes.begin(); hole != face->holes.end(); ++hole)
									{
										for(k3d::split_edge* edge = *hole; edge; edge = edge->face_clockwise)
											{
												if(edge->companion)
													edge->companion->companion = 0;

												polyhedron.edges.erase(std::remove(polyhedron.edges.begin(), polyhedron.edges.end(), edge), polyhedron.edges.end());
												edges.insert(edge);

												if(edge->face_clockwise == *hole)
													break;
											}
									}
							}
						else
							{
								face->first_edge = remaining_edges.front();
								k3d::loop_edges(remaining_edges.begin(), remaining_edges.end());

								++f;
							}
					}
			}

		// Delete points ...
		delete_geometry(Mesh.points, points, is_selected());

		// Make it happen ...
		std::for_each(faces.begin(), faces.end(), k3d::delete_object());
		std::for_each(edges.begin(), edges.end(), k3d::delete_object());

		// Leave the only remaining point selected
		point->selected = true;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<join_points_implementation>,
				k3d::interface_list<k3d::imesh_source,
				k3d::interface_list<k3d::imesh_sink > > > factory(
					k3d::uuid(0x915ba4d4, 0xd4154a12, 0x938bec97, 0x60f819f3),
				"JoinPoints",
				"Joins points at the position of the first point selected",
				"Objects",
				k3d::iplugin_factory::STABLE);

		return factory;
	}
};

/////////////////////////////////////////////////////////////////////////////
// join_points_factory

k3d::iplugin_factory& join_points_factory()
{
	return join_points_implementation::get_factory();
}

} // namespace libk3dmesh


