// 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/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/mesh_filter.h>
#include <k3dsdk/module.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/utility.h>

#include <boost/multi_array.hpp>
#include <iomanip>
#include <iterator>

namespace libk3dmesh
{

/////////////////////////////////////////////////////////////////////////////
// delete_selected_implementation

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

public:
	delete_selected_implementation(k3d::idocument& Document) :
		base(Document)
	{
		m_input_mesh.changed_signal().connect(SigC::slot(*this, &delete_selected_implementation::on_reset_geometry));
		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &delete_selected_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::point_group*> point_groups;
		std::set<k3d::polyhedron*> polyhedra;
		std::set<k3d::face*> faces;
		std::set<k3d::split_edge*> edges;
		std::set<k3d::linear_curve_group*> linear_curve_groups;
		std::set<k3d::linear_curve*> linear_curves;
		std::set<k3d::cubic_curve_group*> cubic_curve_groups;
		std::set<k3d::cubic_curve*> cubic_curves;
		std::set<k3d::nucurve_group*> nucurve_groups;
		std::set<k3d::nucurve*> nucurves;
		std::set<k3d::bilinear_patch*> bilinear_patches;
		std::set<k3d::bicubic_patch*> bicubic_patches;
		std::set<k3d::nupatch*> nupatches;

		// Delete the big, easy stuff that's selected ...
		delete_geometry(Mesh.point_groups, point_groups, is_selected());
		delete_geometry(Mesh.polyhedra, polyhedra, is_selected());
		delete_geometry(Mesh.linear_curve_groups, linear_curve_groups, is_selected());
		delete_geometry(Mesh.cubic_curve_groups, cubic_curve_groups, is_selected());
		delete_geometry(Mesh.nucurve_groups, nucurve_groups, is_selected());
		delete_geometry(Mesh.bilinear_patches, bilinear_patches, is_selected());
		delete_geometry(Mesh.bicubic_patches, bicubic_patches, is_selected());
		delete_geometry(Mesh.nupatches, nupatches, is_selected());
	
		// 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(); )
					{
						k3d::face* face = *f;
						
						if(!face->selected)
							{
								++f;
								continue;
							}
							
						for(k3d::split_edge* edge = face->first_edge; 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 == face->first_edge)
									break;
							}

						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;
									}
							}
																					
						f = polyhedron.faces.erase(f);
						faces.insert(face);
					}
			}
			
		// Delete selected edges, updating their owning faces and adjacent edges as needed.
		// We kill two birds with one stone here by also deleting edges whose vertices are selected.
		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 || (edge->vertex && edge->vertex->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 || (edge->vertex && edge->vertex->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 individual curves that are selected, and curves that contain points that will be deleted ...
		for(k3d::mesh::linear_curve_groups_t::iterator group = Mesh.linear_curve_groups.begin(); group != Mesh.linear_curve_groups.end(); ++group)
			{
				delete_geometry((*group)->curves, linear_curves, is_selected());
				delete_geometry((*group)->curves, linear_curves, contains_selected_points());
			}

		for(k3d::mesh::cubic_curve_groups_t::iterator group = Mesh.cubic_curve_groups.begin(); group != Mesh.cubic_curve_groups.end(); ++group)
			{
				delete_geometry((*group)->curves, cubic_curves, is_selected());
				delete_geometry((*group)->curves, cubic_curves, contains_selected_points());
			}

		for(k3d::mesh::nucurve_groups_t::iterator group = Mesh.nucurve_groups.begin(); group != Mesh.nucurve_groups.end(); ++group)
			{
				delete_geometry((*group)->curves, nucurves, is_selected());
				delete_geometry((*group)->curves, nucurves, contains_selected_points());
			}

		// Remove selected points from point groups ...
		for(k3d::mesh::point_groups_t::iterator g = Mesh.point_groups.begin(); g != Mesh.point_groups.end(); ++g)
			{
				k3d::point_group& group = **g;
				group.points.erase(std::remove_if(group.points.begin(), group.points.end(), is_selected()), group.points.end());
			}

		// Delete geometry that contains points that will be deleted ...
		delete_geometry(Mesh.bilinear_patches, bilinear_patches, contains_selected_points());
		delete_geometry(Mesh.bicubic_patches, bicubic_patches, contains_selected_points());
		delete_geometry(Mesh.nupatches, nupatches, contains_selected_points());
	
		// Delete points ...
		delete_geometry(Mesh.points, points, is_selected());
		
		// Make it happen ...
		std::for_each(points.begin(), points.end(), k3d::delete_object());
		std::for_each(point_groups.begin(), point_groups.end(), k3d::delete_object());
		std::for_each(polyhedra.begin(), polyhedra.end(), k3d::delete_object());
		std::for_each(faces.begin(), faces.end(), k3d::delete_object());
		std::for_each(edges.begin(), edges.end(), k3d::delete_object());
		std::for_each(linear_curve_groups.begin(), linear_curve_groups.end(), k3d::delete_object());
		std::for_each(linear_curves.begin(), linear_curves.end(), k3d::delete_object());
		std::for_each(cubic_curve_groups.begin(), cubic_curve_groups.end(), k3d::delete_object());
		std::for_each(cubic_curves.begin(), cubic_curves.end(), k3d::delete_object());
		std::for_each(nucurve_groups.begin(), nucurve_groups.end(), k3d::delete_object());
		std::for_each(nucurves.begin(), nucurves.end(), k3d::delete_object());
		std::for_each(bilinear_patches.begin(), bilinear_patches.end(), k3d::delete_object());
		std::for_each(bicubic_patches.begin(), bicubic_patches.end(), k3d::delete_object());
		std::for_each(nupatches.begin(), nupatches.end(), k3d::delete_object());
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<delete_selected_implementation>,
				k3d::interface_list<k3d::imesh_source,
				k3d::interface_list<k3d::imesh_sink > > > factory(
				k3d::uuid(0x001c2ac5, 0x05ce42b5, 0x8232dfeb, 0xf0480802),
				"DeleteSelected",
				"Deletes selected geometry",
				"Objects",
				k3d::iplugin_factory::STABLE);

		return factory;
	}
};

/////////////////////////////////////////////////////////////////////////////
// delete_selected_factory

k3d::iplugin_factory& delete_selected_factory()
{
	return delete_selected_implementation::get_factory();
}

} // namespace libk3dmesh

