// 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 Tim Shead (tshead@k-3d.com)
*/

#include <k3dsdk/application.h>
#include <k3dsdk/basic_math.h>
#include <k3dsdk/bezier.h>
#include <k3dsdk/classes.h>
#include <k3dsdk/color.h>
#include <k3dsdk/computed_property.h>
#include <k3dsdk/geometry.h>
#include <k3dsdk/ibezier_channel.h>
#include <k3dsdk/ichannel.h>
#include <k3dsdk/itime_sink.h>
#include <k3dsdk/iuser_interface.h>
#include <k3dsdk/persistence.h>
#include <k3dsdk/module.h>
#include <k3dsdk/mouse_event_observer.h>
#include <k3dsdk/object.h>
#include <k3dsdk/ioptions.h>
#include <k3dsdk/property.h>
#include <k3dsdk/state_change_set.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/time_source.h>
#include <k3dsdk/user_interface.h>
#include <k3dsdk/vectors.h>

#include <iterator>
#include <set>

// We have an unfortunate clash with X
#ifdef RootWindow
#undef RootWindow
#endif // RootWindow

namespace
{

/////////////////////////////////////////////////////////////////////////////
// color_bezier_channel_implementation

/// Encapsulates a data value that can change over time, controlled by a Bezier curve
class color_bezier_channel_implementation :
	public k3d::persistent<k3d::object> ,
	public k3d::ichannel<k3d::color>,
	public k3d::ibezier_channel<k3d::color>,
	public k3d::itime_sink
{
	typedef k3d::persistent<k3d::object>  base;
	typedef k3d::ibezier_channel<k3d::color>::control_points_t control_points_t;
	typedef k3d::ibezier_channel<k3d::color>::values_t values_t;

public:
	color_bezier_channel_implementation(k3d::idocument& Document) :
		base(Document),
		m_input(k3d::init_name("input") + k3d::init_description("Output Value [double]") + k3d::init_value(0.0) + k3d::init_document(Document)),
		m_control_points(k3d::init_value(control_points_t(1, k3d::vector2(0.0, 0.0))) + k3d::init_document(Document)),
		m_values(k3d::init_value(values_t(1, k3d::color(1, 1, 1))) + k3d::init_document(Document)),
		m_output("output", "Output Value [double]", k3d::method_call(*this, &color_bezier_channel_implementation::get_value))
	{
		register_property(m_input);
		register_property(m_output);

		m_input.changed_signal().connect(m_output.changed_signal().slot());
		m_control_points.changed_signal().connect(m_output.changed_signal().slot());
		m_values.changed_signal().connect(m_output.changed_signal().slot());
	}

	k3d::iproperty& time_sink_input()
	{
		return m_input;
	}

	k3d::color get_value()
	{
		return value();
	}

	const k3d::color value()
	{
		return value(m_input.property_value(), 0.001);
	}

	const k3d::color value(const double X, const double MaxError)
	{
		// Special-case the easiest configuration ...
		if(1 == m_control_points.value().size())
			return m_values.value().front();

		// Convert document time to our own local time ...
		const double time = X;

		// We're before the first control point, so that's the value ...
		if(time <= m_control_points.value().front()[0])
			return m_values.value().front();

		values_t::iterator value = m_values.value().begin();
		for(control_points_t::iterator control_point = m_control_points.value().begin(); control_point != m_control_points.value().end() - 1; std::advance(control_point, 3), std::advance(value, 1))
			{
				if(time > (*(control_point + 3))[0])
					continue;

				double mix = 0.0;
				const double dx = (*(control_point+3))[0] - (*control_point)[0];
				if(dx)
					{
						double error = 0.0;
						unsigned long iterations = 0;
						double mix = k3d::bezier_function<3, k3d::vector2>(control_point, control_point + 4, time, (time - (*control_point)[0]) / dx, MaxError * dx, 1000, error, iterations);

						// Adjust the mix for our "sawtooth" pattern ...
						if((*control_point)[1] > (*(control_point+3))[1])
							mix = 1.0 - mix;

						// Linear interpolate the final value ...
//						return k3d::mix<k3d::color>(*value, *(value+1), mix);
						return k3d::color(k3d::mix(value->red, (value+1)->red, mix), k3d::mix(value->green, (value+1)->green, mix), k3d::mix(value->blue, (value+1)->blue, mix));
					}
				else
					{
						mix = (*control_point)[1];
					}

			}

		// We're after the last control point, so that's the value ...
		return m_values.value().back();
	}

	changed_signal_t& changed_signal()
	{
		return m_output.changed_signal();
	}

	void set_curve(const control_points_t ControlPoints, const values_t Values)
	{
		// Sanity checks ...
		return_if_fail(ControlPoints.size());
		return_if_fail(ControlPoints.size() == ((((ControlPoints.size() - 1) / 3) * 3) + 1));
		return_if_fail(Values.size() == (((ControlPoints.size() - 1) / 3) + 1));

		m_control_points.set_value(ControlPoints);
		m_values.set_value(Values);
	}

	void get_curve(control_points_t& ControlPoints, values_t& Values)
	{
		ControlPoints = m_control_points.value();
		Values = m_values.value();
	}

	void save(sdpxml::Element& Element, k3d::idependencies& Dependencies)
	{
		base::save(Element, Dependencies);

		sdpxml::Element& nodes = Element.Append(sdpxml::Element("nodes"));

		control_points_t::iterator control_point = m_control_points.value().begin();
		values_t::iterator value = m_values.value().begin();
		for(unsigned int i = 0; i < m_control_points.value().size(); ++i)
			{
				sdpxml::Element& node = nodes.Append(sdpxml::Element("node", "", sdpxml::Attribute("coords", sdpToString(*control_point))));

				if(0 == i % 3)
					{
						node.Attributes().push_back(sdpxml::Attribute("value", sdpToString(*value)));
						++value;
					}

				++control_point;
			}
	}

	void load(sdpxml::Document& Document, sdpxml::Element& Element)
	{
		// Sanity checks ...
		base::load(Document, Element);

		sdpxml::ElementPointer nodes = sdpxml::FindElement(Element, sdpxml::SameName("nodes"));
		return_if_fail(nodes);

		// Because we already have control points by default, we need to start from scratch ...
		m_control_points.value().clear();
		m_values.value().clear();

		for(sdpxml::ElementCollection::iterator element = nodes->Children().begin(); element != nodes->Children().end(); element++)
			{
				if(element->Name() == "node" || element->Name() == "valuenode")
					{
						k3d::vector2 coords;
						sdpxml::ParseAttribute(*element, "coords", coords);
						m_control_points.value().push_back(coords);

						k3d::color value;
						if(sdpxml::ParseAttribute(*element, "value", value))
							m_values.value().push_back(value);
					}
				else
					{
						std::cerr << __PRETTY_FUNCTION__ << ": unknown element \"" << element->Name() << "\" will be ignored ... " << std::endl;
					}
			}
	}

	void load_complete()
	{
		// If for any reason we don't have the correct number of control points or values, reset ourselves to a sensible fallback position ...
		if(
			(m_control_points.value().size()) &&
			(m_control_points.value().size() == ((((m_control_points.value().size() - 1) / 3) * 3) + 1)) &&
			(m_values.value().size() == (((m_control_points.value().size() - 1) / 3) + 1)))
		{
			return;
		}

		std::cerr << __PRETTY_FUNCTION__ << ": inconsistent node count [" << m_control_points.value().size() << ", " << m_values.value().size() << "] resetting to default" << std::endl;
		m_control_points.value() = control_points_t(1, k3d::vector2(0.0, 0.0));
		m_values.value() = values_t(1, k3d::color(0.0, 0.0, 0.0));
	}

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

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<k3d::document_plugin<color_bezier_channel_implementation>, k3d::interface_list<k3d::ichannel<k3d::color>, k3d::interface_list<k3d::ibezier_channel<k3d::color> > > > factory(
			k3d::classes::ColorBezierChannel(),
			"ColorBezierChannel",
			"",
			"Objects",
			k3d::iplugin_factory::STABLE);

		return factory;
	}

private:
	friend class bezier_channel_properties;

	k3d_data_property(double, k3d::immutable_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_input;
	k3d_data(control_points_t, k3d::no_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_control_points;
	k3d_data(values_t, k3d::no_name, k3d::change_signal, k3d::with_undo, k3d::local_storage, k3d::no_constraint) m_values;
	k3d::computed_property<k3d::color, k3d::method_call_t<color_bezier_channel_implementation, k3d::color> > m_output;
};

} // namespace

namespace libk3dchannel
{

/////////////////////////////////////////////////////////////////////////////
// color_bezier_channel_factory

k3d::iplugin_factory& color_bezier_channel_factory()
{
	return color_bezier_channel_implementation::get_factory();
}

} // namespace libk3dchannel


