// 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/measurement.h>
#include <k3dsdk/mesh_filter.h>
#include <k3dsdk/module.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/transform.h>

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

namespace libk3dmesh
{

/////////////////////////////////////////////////////////////////////////////
// array_1d_implementation

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

public:
	array_1d_implementation(k3d::idocument& Document) :
		base(Document),
		m_count(k3d::init_name("count") + k3d::init_description("Count [integer]") + k3d::init_value(5) + k3d::init_document(Document) + k3d::init_precision(0) + k3d::init_step_increment(1) + k3d::init_units(typeid(k3d::measurement::scalar)) + k3d::init_constraint(k3d::constraint::minimum(0UL))),
		m_offsetx(k3d::init_name("offsetx") + k3d::init_description("X Offset [number]") + k3d::init_value(0.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_offsety(k3d::init_name("offsety") + k3d::init_description("Y Offset [number]") + k3d::init_value(0.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_offsetz(k3d::init_name("offsetz") + k3d::init_description("Z Offset [number]") + k3d::init_value(5.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(0.1) + k3d::init_units(typeid(k3d::measurement::distance))),
		m_rotatex(k3d::init_name("rotatex") + k3d::init_description("X Rotation [number]") + k3d::init_value(0.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(k3d::radians(1.)) + k3d::init_units(typeid(k3d::measurement::angle))),
		m_rotatey(k3d::init_name("rotatey") + k3d::init_description("Y Rotation [number]") + k3d::init_value(0.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(k3d::radians(1.0)) + k3d::init_units(typeid(k3d::measurement::angle))),
		m_rotatez(k3d::init_name("rotatez") + k3d::init_description("Z Rotation [number]") + k3d::init_value(0.0) + k3d::init_document(Document) + k3d::init_precision(2) + k3d::init_step_increment(k3d::radians(1.0)) + k3d::init_units(typeid(k3d::measurement::angle)))
	{
		enable_serialization(k3d::persistence::proxy(m_count));
		enable_serialization(k3d::persistence::proxy(m_offsetx));
		enable_serialization(k3d::persistence::proxy(m_offsety));
		enable_serialization(k3d::persistence::proxy(m_offsetz));
		enable_serialization(k3d::persistence::proxy(m_rotatex));
		enable_serialization(k3d::persistence::proxy(m_rotatey));
		enable_serialization(k3d::persistence::proxy(m_rotatez));

		register_property(m_count);
		register_property(m_offsetx);
		register_property(m_offsety);
		register_property(m_offsetz);
		register_property(m_rotatex);
		register_property(m_rotatey);
		register_property(m_rotatez);
		
		m_input_mesh.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::on_reset_geometry));
				
		m_count.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::on_reset_geometry));
		m_offsetx.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::on_reset_geometry));
		m_offsety.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::on_reset_geometry));
		m_offsetz.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::on_reset_geometry));
		m_rotatex.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::on_reset_geometry));
		m_rotatey.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::on_reset_geometry));
		m_rotatez.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::on_reset_geometry));
	
		m_output_mesh.need_data_signal().connect(SigC::slot(*this, &array_1d_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();

		create_geometry(*input, *output);

		return output;
	}

	void create_geometry(k3d::mesh& Input, k3d::mesh& Output)
	{
		const unsigned long count = m_count.property_value();
		const double offsetx = m_offsetx.property_value();
		const double offsety = m_offsety.property_value();
		const double offsetz = m_offsetz.property_value();
		const double rotatex = m_rotatex.property_value();
		const double rotatey = m_rotatey.property_value();
		const double rotatez = m_rotatez.property_value();
		
		for(unsigned long i = 0; i != count; ++i)
			{
				// Make a copy of the input geometry ...
				const unsigned long first_new_point = Output.points.size();
				k3d::deep_copy(Input, Output);

				// Apply offsets ...
				const k3d::matrix4 matrix = k3d::translation3D(k3d::vector3(i * offsetx, i * offsety, i * offsetz)) * k3d::rotation3D(k3d::vector3(i * rotatex, i * rotatey, i * rotatez));
				for(unsigned long i = first_new_point; i != Output.points.size(); ++i)
					Output.points[i]->position = matrix * Output.points[i]->position;
			}
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<array_1d_implementation>,
				k3d::interface_list<k3d::imesh_source,
				k3d::interface_list<k3d::imesh_sink > > > factory(
				k3d::uuid(0x15e5e4e3, 0x80144246, 0xb8b43558, 0xcd361180),
				"Array1D",
				"Makes copies of a mesh along one dimension",
				"Objects",
				k3d::iplugin_factory::EXPERIMENTAL);

		return factory;
	}

private:
	k3d_measurement_property(unsigned long, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::with_constraint) m_count;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_offsetx;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_offsety;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_offsetz;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_rotatex;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_rotatey;
	k3d_measurement_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_rotatez;
};

/////////////////////////////////////////////////////////////////////////////
// array_1d_factory

k3d::iplugin_factory& array_1d_factory()
{
	return array_1d_implementation::get_factory();
}

} // namespace libk3dmesh

