// 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/classes.h>
#include <k3dsdk/ibounded.h>
#include <k3dsdk/imaterial.h>
#include <k3dsdk/object.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/material_collection.h>
#include <k3dsdk/measurement.h>
#include <k3dsdk/module.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/renderman.h>
#include <k3dsdk/transformable.h>
#include <k3dsdk/viewport.h>

namespace libk3drenderman
{

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

class array_1d_implementation :
	public k3d::material_collection<k3d::viewport::drawable<k3d::ri::renderable<k3d::transformable<k3d::persistent<k3d::object> > > > >
{
	typedef k3d::material_collection<k3d::viewport::drawable<k3d::ri::renderable<k3d::transformable<k3d::persistent<k3d::object> > > > > base;

public:
	array_1d_implementation(k3d::idocument& Document) :
		base(Document),
		m_instance(k3d::init_name("instance") + k3d::init_description("Instance [object]") + k3d::init_object_value(0) + k3d::init_document(Document)),
		m_count(k3d::init_name("count") + k3d::init_description("Count [integer]") + k3d::init_value(5) + k3d::init_document(Document) + k3d::init_constraint(k3d::constraint::minimum(0UL)) + k3d::init_precision(0) + k3d::init_step_increment(1.0) + k3d::init_units(typeid(k3d::measurement::scalar))),
		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.0)) + 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::object_proxy(m_instance));
		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_instance);
		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_instance.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::async_redraw_all));		
		m_count.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::async_redraw_all));		
		m_offsetx.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::async_redraw_all));		
		m_offsety.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::async_redraw_all));		
		m_offsetz.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::async_redraw_all));		
		m_rotatex.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::async_redraw_all));		
		m_rotatey.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::async_redraw_all));		
		m_rotatez.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::async_redraw_all));		
		
		m_position.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::async_redraw_all));
		m_orientation.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::async_redraw_all));
		m_scale.changed_signal().connect(SigC::slot(*this, &array_1d_implementation::async_redraw_all));
	}

	~array_1d_implementation()
	{
	}

	void draw(const k3d::viewport::render_state& State)
	{
		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();

		glDisable(GL_LIGHTING);
		glColor3d(0, 1, 1);

		glBegin(GL_LINES);
		glVertex3dv(k3d::vector3(0, 0, 0));
		glVertex3dv(k3d::vector3((count-1) * offsetx, (count-1) * offsety, (count-1) * offsetz));
		glEnd();

		glBegin(GL_POINTS);
		for(unsigned long i = 0; i != count; ++i)
			{
				glVertex3dv(k3d::vector3(i * offsetx, i * offsety, i * offsetz));
			}
		glEnd();
	
		k3d::ibounded* const bounded = dynamic_cast<k3d::ibounded*>(m_instance.interface());
		if(!bounded)
			return;

		const k3d::bounding_box bounds = bounded->extents();
		if(bounds.empty())
			return;
		
		const double rotatex = m_rotatex.property_value();
		const double rotatey = m_rotatey.property_value();
		const double rotatez = m_rotatez.property_value();
						
		glMatrixMode(GL_MODELVIEW);
		for(unsigned long i = 0; i != count; ++i)
			{
				glPushMatrix();
				k3d::push_opengl_matrix(k3d::translation3D(k3d::vector3(i * offsetx, i * offsety, i * offsetz)) * k3d::rotation3D(k3d::vector3(i * rotatex, i * rotatey, i * rotatez)));
				k3d::viewport::draw_bounding_box(bounds);
				glPopMatrix();
			}
	}

	void on_viewport_draw(const k3d::viewport::render_state& State)
	{
		draw(State);
	}

	void on_viewport_select(const k3d::viewport::render_state& State)
	{
		draw(State);
	}

	void on_renderman_render(const k3d::ri::render_state& State)
	{
		k3d::ri::irenderable* const renderable = m_instance.interface();
		if(!renderable)
			return;

		// Make sure we don't enter an infinite loop trying to render ourself ...
		if(renderable == this)
			{
				std::cerr << error << factory().name() << " [" << name() << "] cannot instance itself" << std::endl;
				return;
			}

		const k3d::ri::object_handle handle = State.engine.RiObjectBegin();
			k3d::ri::render_state state(State);
			state.render_context = k3d::ri::OBJECT_INSTANCE;
			renderable->renderman_render(state);
		State.engine.RiObjectEnd();

		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();

		k3d::ri::setup_material(m_material.interface(), State);

		for(unsigned long i = 0; i != count; ++i)
			{
				State.engine.RiAttributeBegin();
				State.engine.RiConcatTransform(k3d::ri::convert(k3d::translation3D(k3d::vector3(i * offsetx, i * offsety, i * offsetz)) * k3d::rotation3D(k3d::vector3(i * rotatex, i * rotatey, i * rotatez))));
				State.engine.RiObjectInstance(handle);
				State.engine.RiAttributeEnd();
			}
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::document_plugin<array_1d_implementation> > factory(
				k3d::uuid(0x714f0863, 0x6af8447f, 0x9d5a556c, 0x5e2f6940),
				"RenderManArray1D",
				"Renders a one-dimensional array of geometric object instances",
				"Objects",
				k3d::iplugin_factory::EXPERIMENTAL);

		return factory;
	}

private:
	k3d_object_property(k3d::ri::irenderable, k3d::immutable_name, k3d::with_undo, k3d::local_storage) m_instance;
	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 libk3drenderman


