// 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
		\brief Performs boolean operations
		\author Timothy M. Shead (tshead@k-3d.com)
*/

#include <k3dsdk/basic_math.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/material_collection.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/mesh_source.h>
#include <k3dsdk/module.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/utility.h>

#include "gts_interface.h"

namespace libk3dgts
{

namespace detail
{

template<typename T>
class wrapper
{
public:
	wrapper(T* const Object) :
		m_object(Object)
	{
	}

	~wrapper()
	{
		if(m_object)
			gts_object_destroy(GTS_OBJECT(m_object));
	}

	operator T*()
	{
		return m_object;
	}

	T* operator->()
	{
		return m_object;
	}

private:
	T* const m_object;
};

template<>
class wrapper<GNode>
{
public:
	wrapper(GNode* const Object) :
		m_object(Object)
	{
	}

	~wrapper()
	{
		gts_bb_tree_destroy(m_object, TRUE);
	}

	operator GNode*()
	{
		return m_object;
	}

private:
	GNode* const m_object;
};

} // namespace detail

/////////////////////////////////////////////////////////////////////////////
// boolean_implementation

class boolean_implementation :
	public k3d::material_collection<k3d::mesh_source<k3d::persistent<k3d::object> > >
{
	typedef k3d::material_collection<k3d::mesh_source<k3d::persistent<k3d::object> > > base;

public:
	boolean_implementation(k3d::idocument& Document) :
		base(Document),
		m_type(k3d::init_name("type") + k3d::init_description("Operation [enumeration]") + k3d::init_enumeration(boolean_values()) + k3d::init_value(BOOLEAN_INTERSECTION) + k3d::init_document(Document)),
		m_input1(k3d::init_name("input1") + k3d::init_description("Input mesh 1") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document)),
		m_input2(k3d::init_name("input2") + k3d::init_description("Input mesh 2") + k3d::init_value<k3d::mesh*>(0) + k3d::init_document(Document))
	{
		register_property(m_type);
		register_property(m_input1);
		register_property(m_input2);

		enable_serialization(k3d::persistence::proxy(m_type));

		m_type.changed_signal().connect(SigC::slot(*this, &boolean_implementation::on_reset_geometry));
		m_input1.changed_signal().connect(SigC::slot(*this, &boolean_implementation::on_reset_geometry));
		m_input2.changed_signal().connect(SigC::slot(*this, &boolean_implementation::on_reset_geometry));

		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &boolean_implementation::on_create_geometry));
	}

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

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

		const k3d::mesh* const input2 = m_input2.property_value();
		if(!input2)
			return 0;

		unsigned long polyhedron_index = 0;
		detail::wrapper<GtsSurface> surface1 = gts_polygonal_surface(*input1, polyhedron_index);
		return_val_if_fail(surface1, 0);
		if(!gts_surface_is_orientable(surface1))
			{
				std::cerr << error << "input1 is not an orientable manifold" << std::endl;
				return 0;
			}
		detail::wrapper<GtsSurface> self_intersection1 = gts_surface_is_self_intersecting(surface1);
		if(self_intersection1)
			{
				std::cerr << error << "input1 self-intersects" << std::endl;
				return 0;
			}

		detail::wrapper<GtsSurface> surface2 = gts_polygonal_surface(*input2, polyhedron_index);
		return_val_if_fail(surface2, 0);
		if(!gts_surface_is_orientable(surface2))
			{
				std::cerr << error << "input2 is not an orientable manifold" << std::endl;
				return 0;
			}
		detail::wrapper<GtsSurface> self_intersection2 = gts_surface_is_self_intersecting(surface2);
		if(self_intersection2)
			{
				std::cerr << error << "input2 self-intersects" << std::endl;
				return 0;
			}

		detail::wrapper<GNode> tree1 = gts_bb_tree_surface(surface1);
		const bool open1 = gts_surface_volume(surface1) < 0;

		detail::wrapper<GNode> tree2 = gts_bb_tree_surface(surface2);
		const bool open2 = gts_surface_volume(surface2) < 0;


		detail::wrapper<GtsSurfaceInter> si = gts_surface_inter_new(gts_surface_inter_class(), surface1, surface2, tree1, tree2, open1, open2);
		return_val_if_fail(si, 0);

		detail::wrapper<GtsSurface> surface = gts_surface_new(gts_surface_class(), gts_face_class(), gts_edge_class(), gts_vertex_class());
		return_val_if_fail(surface, 0);

		switch(m_type.property_value())
			{
				case BOOLEAN_INTERSECTION:
					gts_surface_inter_boolean(si, surface, GTS_1_IN_2);
					gts_surface_inter_boolean(si, surface, GTS_2_IN_1);
					break;

				case BOOLEAN_UNION:
					gts_surface_inter_boolean(si, surface, GTS_1_OUT_2);
					gts_surface_inter_boolean(si, surface, GTS_2_OUT_1);
					break;

				case BOOLEAN_DIFFERENCE:
					gts_surface_inter_boolean(si, surface, GTS_1_OUT_2);
					gts_surface_inter_boolean(si, surface, GTS_2_IN_1);
					gts_surface_foreach_face(si->s2, (GtsFunc)gts_triangle_revert, NULL);
					gts_surface_foreach_face(surface2, (GtsFunc)gts_triangle_revert, NULL);
					break;

				case BOOLEAN_REVERSE_DIFFERENCE:
					gts_surface_inter_boolean(si, surface, GTS_2_OUT_1);
					gts_surface_inter_boolean(si, surface, GTS_1_IN_2);
					gts_surface_foreach_face(si->s1, (GtsFunc)gts_triangle_revert, NULL);
					gts_surface_foreach_face(surface1, (GtsFunc)gts_triangle_revert, NULL);
					break;
			}

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

		k3d::imaterial* const material = m_material.interface();

		for(k3d::mesh::polyhedra_t::iterator polyhedron = output->polyhedra.begin(); polyhedron != output->polyhedra.end(); ++polyhedron)
			(*polyhedron)->material = material;

		return output;
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<boolean_implementation> > factory(
				k3d::uuid(0xbb8e6fcc, 0x573245c3, 0xb6166c7e, 0xe4f06b86),
				"GTSBoolean",
				"Merges polygonal surfaces using boolean operations",
				"Objects",
				k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	typedef enum
	{
		BOOLEAN_INTERSECTION,
		BOOLEAN_UNION,
		BOOLEAN_DIFFERENCE,
		BOOLEAN_REVERSE_DIFFERENCE
	} boolean_t;

	friend const k3d::ienumeration_property::values_t& boolean_values()
	{
		static k3d::ienumeration_property::values_t values;
		if(values.empty())
			{
				values.push_back(k3d::ienumeration_property::value_t("Intersection", "intersection", "Render intersecting volumes"));
				values.push_back(k3d::ienumeration_property::value_t("Union", "union", "Render the union of two volumnes"));
				values.push_back(k3d::ienumeration_property::value_t("Difference", "difference", "Render the difference of two volumes"));
				values.push_back(k3d::ienumeration_property::value_t("Reverse Difference", "reverse_difference", "Render the difference of two volumes"));
			}

		return values;
	}

	friend std::ostream& operator<<(std::ostream& Stream, const boolean_t& Value)
	{
		switch(Value)
			{
				case BOOLEAN_UNION:
					Stream << "union";
					break;
				case BOOLEAN_INTERSECTION:
					Stream << "intersection";
					break;
				case BOOLEAN_DIFFERENCE:
					Stream << "difference";
					break;
				case BOOLEAN_REVERSE_DIFFERENCE:
					Stream << "reverse_difference";
					break;
			}

		return Stream;
	}

	friend std::istream& operator>>(std::istream& Stream, boolean_t& Value)
	{
		std::string text;
		Stream >> text;

		if(text == "union")
			Value = BOOLEAN_UNION;
		else if(text == "intersection")
			Value = BOOLEAN_INTERSECTION;
		else if(text == "difference")
			Value = BOOLEAN_DIFFERENCE;
		else if(text == "reverse_difference")
			Value = BOOLEAN_REVERSE_DIFFERENCE;
		else
			std::cerr << __PRETTY_FUNCTION__ << ": unknown enumeration [" << text << "]"<< std::endl;

		return Stream;
	}

	k3d_enumeration_property(boolean_t, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_type;
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input1;
	k3d_data_property(k3d::mesh*, k3d::immutable_name, k3d::change_signal, k3d::no_undo, k3d::local_storage, k3d::no_constraint) m_input2;
};

/////////////////////////////////////////////////////////////////////////////
// boolean_factory

k3d::iplugin_factory& boolean_factory()
{
	return boolean_implementation::get_factory();
}

} // namespace libk3dgts


