// 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/algebra.h>
#include <k3dsdk/axis.h>
#include <k3dsdk/bounding_box.h>
#include <k3dsdk/color.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/mesh_filter.h>
#include <k3dsdk/module.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/selection.h>

#include <iterator>

namespace libk3dmesh
{

/////////////////////////////////////////////////////////////////////////////
// planar_map_implementation

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

public:
	planar_map_implementation(k3d::idocument& Document) :
		base(Document),
		m_axis(k3d::init_name("axis") + k3d::init_description("Projection Axis [enumeration]") + k3d::init_value(k3d::PZ) + k3d::init_document(Document) + k3d::init_enumeration(k3d::signed_axis_values())),
		m_s0(k3d::init_name("s0") + k3d::init_description("S0 [number]") + k3d::init_value(0) + k3d::init_document(Document)),
		m_s1(k3d::init_name("s1") + k3d::init_description("S1 [number]") + k3d::init_value(1) + k3d::init_document(Document)),
		m_t0(k3d::init_name("t0") + k3d::init_description("T0 [number]") + k3d::init_value(0) + k3d::init_document(Document)),
		m_t1(k3d::init_name("t1") + k3d::init_description("T1 [number]") + k3d::init_value(1) + k3d::init_document(Document)),
		m_default_s(k3d::init_name("default_s") + k3d::init_description("Default S [number]") + k3d::init_value(0) + k3d::init_document(Document)),
		m_default_t(k3d::init_name("default_t") + k3d::init_description("Default T [number]") + k3d::init_value(0) + k3d::init_document(Document)),
		m_tag_points(k3d::init_name("tag_points") + k3d::init_description("Tag Points [boolean]") + k3d::init_value(false) + k3d::init_document(Document)),
		m_tag_edges(k3d::init_name("tag_edges") + k3d::init_description("Tag Edges [boolean]") + k3d::init_value(false) + k3d::init_document(Document))
	{
		register_property(m_axis);
		register_property(m_s0);
		register_property(m_s1);
		register_property(m_t0);
		register_property(m_t1);
		register_property(m_default_s);
		register_property(m_default_t);
		register_property(m_tag_points);
		register_property(m_tag_edges);
		
		enable_serialization(k3d::persistence::proxy(m_axis));
		enable_serialization(k3d::persistence::proxy(m_s0));
		enable_serialization(k3d::persistence::proxy(m_s1));
		enable_serialization(k3d::persistence::proxy(m_t0));
		enable_serialization(k3d::persistence::proxy(m_t1));
		enable_serialization(k3d::persistence::proxy(m_default_s));
		enable_serialization(k3d::persistence::proxy(m_default_t));
		enable_serialization(k3d::persistence::proxy(m_tag_points));
		enable_serialization(k3d::persistence::proxy(m_tag_edges));

		m_input_mesh.changed_signal().connect(SigC::slot(*this, &planar_map_implementation::on_reset_geometry));
		m_axis.changed_signal().connect(SigC::slot(*this, &planar_map_implementation::on_reset_geometry));
		m_s0.changed_signal().connect(SigC::slot(*this, &planar_map_implementation::on_reset_geometry));
		m_s1.changed_signal().connect(SigC::slot(*this, &planar_map_implementation::on_reset_geometry));
		m_t0.changed_signal().connect(SigC::slot(*this, &planar_map_implementation::on_reset_geometry));
		m_t1.changed_signal().connect(SigC::slot(*this, &planar_map_implementation::on_reset_geometry));
		m_default_s.changed_signal().connect(SigC::slot(*this, &planar_map_implementation::on_reset_geometry));
		m_default_t.changed_signal().connect(SigC::slot(*this, &planar_map_implementation::on_reset_geometry));
		m_tag_points.changed_signal().connect(SigC::slot(*this, &planar_map_implementation::on_reset_geometry));
		m_tag_edges.changed_signal().connect(SigC::slot(*this, &planar_map_implementation::on_reset_geometry));

		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &planar_map_implementation::on_create_geometry));
	}
	
	void on_reset_geometry()
	{
		m_output_mesh.reset();
	}
	
	k3d::mesh* on_create_geometry()
	{
		// If we don't have any input mesh, we're done ...
		const k3d::mesh* const input = m_input_mesh.property_value();
		if(!input)
			return 0;

		// Otherwise, we make a copy of the input mesh and modify the copy ...
		k3d::mesh* const output = new k3d::mesh();
		k3d::deep_copy(*input, *output);
		modify_geometry(*input, *output);

		return output;
	}

	void update_bounds(const bool Selected, const bool IgnoreSelection, const k3d::matrix4& Transformation, const k3d::vector3& Position, k3d::bounding_box& Bounds)
	{
		if(IgnoreSelection || Selected)
			Bounds.insert(Transformation * Position);
	}

	void set_coordinates(const bool Selected, const bool IgnoreSelection, const k3d::matrix4& Transformation, const k3d::vector3& Position, const k3d::bounding_box& Bounds, const double S0, const double S1, const double T0, const double T1, const double DefaultS, const double DefaultT, k3d::parameters_t& Parameters)
	{
		if(IgnoreSelection || Selected)
			{
				const k3d::vector3 position = Transformation * Position;

				Parameters["s"] = Bounds.width() ? k3d::mix(S0, S1, (position[0] - Bounds.nx) / (Bounds.width())) : DefaultS;
				Parameters["t"] = Bounds.height() ? k3d::mix(T0, T1, (Bounds.py - position[1]) / (Bounds.height())) : DefaultT;
			}
		else
			{
				if(0 == Parameters.count("s"))
					Parameters["s"] = DefaultS;
				if(0 == Parameters.count("t"))
					Parameters["t"] = DefaultT;
			}
	}

	void modify_geometry(const k3d::mesh& Source, k3d::mesh& Target)
	{
		const bool ignore_selection = !k3d::contains_selection(Source);
		const double s0 = m_s0.property_value();
		const double s1 = m_s1.property_value();
		const double t0 = m_t0.property_value();
		const double t1 = m_t1.property_value();
		const double default_s = m_default_s.property_value();
		const double default_t = m_default_t.property_value();
		
		k3d::matrix4 transformation = k3d::identity3D();
		switch(m_axis.property_value())
			{
				case k3d::NX:
					transformation = k3d::rotation3D(k3d::radians(90.0), k3d::vector3(0, 1, 0));
					break;
					
				case k3d::PX:
					transformation = k3d::rotation3D(k3d::radians(-90.0), k3d::vector3(0, 1, 0));
					break;
					
				case k3d::NY:
					transformation = k3d::rotation3D(k3d::radians(-90.0), k3d::vector3(1, 0, 0));
					break;
					
				case k3d::PY:
					transformation = k3d::rotation3D(k3d::radians(90.0), k3d::vector3(1, 0, 0));
					break;
					
				case k3d::NZ:
					transformation = k3d::rotation3D(k3d::radians(180.0), k3d::vector3(0, 1, 0));
					break;
					
				case k3d::PZ:
					break;
					
				default:
					std::cerr << warning << __PRETTY_FUNCTION__ << ": unknown axis enumeration" << std::endl;
					break;
			}

		// Compute a bounding-box that contains all selected points ...
		k3d::bounding_box bounds;
		if(m_tag_points.property_value())
			{
				for(k3d::mesh::points_t::iterator point = Target.points.begin(); point != Target.points.end(); ++point)
					update_bounds((*point)->selected, ignore_selection, transformation, (*point)->position, bounds);
			}

		for(k3d::mesh::polyhedra_t::iterator polyhedron = Target.polyhedra.begin(); polyhedron != Target.polyhedra.end(); ++polyhedron)
			{
				if(m_tag_edges.property_value())
					{
						for(k3d::polyhedron::edges_t::const_iterator edge = (*polyhedron)->edges.begin(); edge != (*polyhedron)->edges.end(); ++edge)
							{
								if((*edge)->vertex)
									update_bounds((*edge)->selected, ignore_selection, transformation, (*edge)->vertex->position, bounds);
							}
					}
			}

		// If we didn't have any input to process, we're done ...
		if(bounds.empty())
			return;

		// Calculate s,t coordinates using linear interpolation
		if(m_tag_points.property_value())
			{
				for(k3d::mesh::points_t::iterator point = Target.points.begin(); point != Target.points.end(); ++point)
					set_coordinates((*point)->selected, ignore_selection, transformation, (*point)->position, bounds, s0, s1, t0, t1, default_s, default_t, (*point)->vertex_data);
			}

		for(k3d::mesh::polyhedra_t::iterator polyhedron = Target.polyhedra.begin(); polyhedron != Target.polyhedra.end(); ++polyhedron)
			{
				if(m_tag_edges.property_value())
					{
						for(k3d::polyhedron::edges_t::const_iterator edge = (*polyhedron)->edges.begin(); edge != (*polyhedron)->edges.end(); ++edge)
							{
								if((*edge)->vertex)
									set_coordinates((*edge)->selected, ignore_selection, transformation, (*edge)->vertex->position, bounds, s0, s1, t0, t1, default_s, default_t, (*edge)->facevarying_data);
							}
					}
			}
	}
	
	k3d::iplugin_factory& factory()
	{
		return get_factory();
	}

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<planar_map_implementation>,
			k3d::interface_list<k3d::imesh_source,
			k3d::interface_list<k3d::imesh_sink > > > factory(
				k3d::uuid(0x9e25bcdd, 0x621d4536, 0xa622a63f, 0xa59e874b),
				"PlanarMap",
				"Maps values to geometry using a planar projection map",
				"Objects",
				k3d::iplugin_factory::EXPERIMENTAL);

		return factory;
	}

private:
	k3d_enumeration_property(k3d::signed_axis, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_axis;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_s0;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_s1;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_t0;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_t1;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_default_s;
	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_default_t;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_tag_points;
	k3d_data_property(bool, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_tag_edges;
};

/////////////////////////////////////////////////////////////////////////////
// planar_map_factory

k3d::iplugin_factory& planar_map_factory()
{
	return planar_map_implementation::get_factory();
}

} // namespace libk3dmesh

