#ifndef K3DSDK_TRANSFORMABLE_H
#define K3DSDK_TRANSFORMABLE_H

// 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 Declares the k3dTransform class, which provides a pluggable implementation of k3dITransform
		\author Tim Shead (tshead@k-3d.com)
*/

#include "computed_property.h"
#include "data.h"
#include "itransform_sink.h"
#include "itransform_source.h"
#include "isnap_target.h"
#include "persistence.h"
#include "property_collection.h"
#include "transform.h"
#include "vectors.h"

#include <sdpxml/sdpxml.h>

namespace k3d
{

/// Provides a boilerplate implementation of an object that has position, scale, and orientation
template<typename base_t>
class transformable :
	public base_t,
	public itransform_source,
	public itransform_sink,
	public isnap_target
{
public:
	transformable(idocument& Document) :
		base_t(Document),
		m_position(init_name("position") + init_description("Position [vector3]") + init_value(vector3(0.0, 0.0, 0.0)) + init_document(Document)),
		m_orientation(init_name("orientation") + init_description("Orientation [angle_axis]") + init_value(angle_axis(0.0, vector3(0.0, 0.0, 1.0))) + init_document(Document)),
		m_scale(init_name("scale") + init_description("Scale Matrix [vector3]") + init_value(vector3(1.0, 1.0, 1.0)) + init_document(Document)),
		m_input_matrix(init_name("input_matrix") + init_description("Input matrix [matrix4]") + init_value(identity3D()) + init_document(Document)),
		m_output_matrix("output_matrix", "Output matrix [matrix4]", method_call(*this, &transformable<base_t>::matrix))
	{
		base_t::enable_serialization(persistence::proxy(m_position));
		base_t::enable_serialization(persistence::proxy(m_orientation));
		base_t::enable_serialization(persistence::proxy(m_scale));

		base_t::register_property(m_input_matrix);
		base_t::register_property(m_position);
		base_t::register_property(m_orientation);
		base_t::register_property(m_scale);
		base_t::register_property(m_output_matrix);

		m_input_matrix.changed_signal().connect(m_output_matrix.changed_signal().slot());
		m_position.changed_signal().connect(m_output_matrix.changed_signal().slot());
		m_orientation.changed_signal().connect(m_output_matrix.changed_signal().slot());
		m_scale.changed_signal().connect(m_output_matrix.changed_signal().slot());
	}

	iproperty& transform_source_output()
	{
		return m_output_matrix;
	}

	iproperty& transform_sink_input()
	{
		return m_input_matrix;
	}

	void snap(const vector3& InputCoordinates, vector3& SnapCoordinates, std::string& SnapDescription)
	{
		SnapCoordinates = matrix() * vector3(0.0, 0.0, 0.0);
		SnapDescription = "Object Center Point";
	}

protected:
	matrix4 matrix()
	{
		return m_input_matrix.property_value() * translation3D(m_position.property_value()) * rotation3D(m_orientation.property_value()) * scaling3D(m_scale.property_value());
	}

	k3d_data_property(vector3, immutable_name, change_signal, with_undo, local_storage, no_constraint) m_position;
	k3d_data_property(angle_axis, immutable_name, change_signal, with_undo, local_storage, no_constraint) m_orientation;
	k3d_data_property(vector3, immutable_name, change_signal, with_undo, local_storage, no_constraint) m_scale;
	k3d_data_property(matrix4, immutable_name, change_signal, no_undo, local_storage, no_constraint) m_input_matrix;
	computed_property<k3d::matrix4, method_call_t<transformable<base_t>, matrix4> > m_output_matrix;

private:
	void load(sdpxml::Document& Document, sdpxml::Element& Element)
	{
		base_t::load(Document, Element);

		// Look for legacy data from older versions of the program ...
		for(sdpxml::ElementCollection::const_iterator xml_transformation = Element.Children().begin(); xml_transformation != Element.Children().end(); ++xml_transformation)
			{
				if(xml_transformation->Name() != "transformation")
					continue;

				const std::string frame = sdpxml::GetAttribute(*xml_transformation, "frame", std::string());
				if(frame != "self")
					continue;

				if(!sdpxml::ParseAttribute(*xml_transformation, "position", m_position.value()))
					continue;
				if(!sdpxml::ParseAttribute(*xml_transformation, "orientation", m_orientation.value()))
					continue;
				if(!sdpxml::ParseAttribute(*xml_transformation, "scale", m_scale.value()))
					continue;

				break;
			}
	}
};

} // namespace k3d

#endif // K3DSDK_TRANSFORMABLE_H


