/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


#ifndef	__NCURVE__
#define	__NCURVE__


		#include "math/nmath.h"
		#include "metafile/nml.h"
		#include "reflection/nenum_string.h"
		#include "container/narray_list.h"
		#include "time/ntime_range.h"
		#include "time/ntime.h"


namespace GS {

static const float DefaultCurveOptimizationThreshold = 0.05f;

/*
	@short	Curve control point.
	@author	Emmanuel Julien (ejulien@nworks.fr)
*/
struct	CurvePoint
{
		NPLACEMENT_NEW(Curve)

/// Curve control point shape.
enum	Shape
{
	Shape_None = 0,
	Shape_Linear,
	Shape_TCB,
	Shape_Hermite,
	Shape_Bezier,
	Shape_Bezier2,
	Shape_Step
};

		Shape		shape;

		Time		t;
		float		v,
					tension, continuity, bias,
					param[4];	//< TCB parameters.

		CurvePoint(const Time &time, float value, Shape shp = Shape_Linear) : t(time), v(value), shape(shp)
		{
			tension = 0;
			continuity = 0;
			bias = 0;

			param[0] = param[1] = param[2] = param[3] = 0;
		}
		CurvePoint() : t(0), v(0), shape(Shape_Linear)
		{
			tension = 0;
			continuity = 0;
			bias = 0;

			param[0] = param[1] = param[2] = param[3] = 0;
		}
};

/*!
	@short	Curve object.
	@author Emmanuel Julien (ejulien@nworks.fr)
*/
class	Curve
{
public:

		NPLACEMENT_NEW(Curve)

enum	LoopMode
{
	Reset = 0,			///< Returns 0.
	Constant,			///< Returns first/last knot value.
	Repeat,				///< Warp evaluation time to a value inside curve range and evaluate.
	Oscillate,			///< Ping-pong style evaluation.
	OffsetAndRepeat		///< Same as repeat but the whole curve is offset with the last knot value.
};

static	Reflection::Enum::Dict loop_mode_dict[];

protected:

		ArrayList <CurvePoint *> points;	///< Curve knots.

		uint			Optimize(uint point_count, const CurvePoint *src, CurvePoint *dst, float threshold);

public:

		/// Evaluate curve at a given time.
		void			Evaluate(Time t, float *value, LoopMode loop_mode = Constant, Time loop_start = Time::Inf, Time loop_end = Time::Inf) const;

		/// Evaluate incoming tangent to the curve.
		float			Incoming(const CurvePoint *kf0, const CurvePoint *kf1, const CurvePoint *kf2) const;
		/// Evaluate outgoing tangent to the curve.
		float			Outgoing(const CurvePoint *kf0, const CurvePoint *kf1, const CurvePoint *kfp) const;

		/*!
			@short Update a control point.
			@note The time epsilon is considered on both side of the control
				  points time. If no point to update is found a new point is
				  inserted.
		*/
		void			Update(const CurvePoint &, const Time &t_epsilon);
		/// Insert a control point.
		void			Insert(const CurvePoint &);
		/// Append a control point to the control point list.
		void			Append(const CurvePoint &);

		/// Delete a curve control point from the channel.
		void			Delete(CurvePoint *);

		/// Get curve time range, very fast.
		TimeRange		GetTimeRange() const;
		/// Get curve value range, requires a full parsing of the knot list.
		Range <float>	GetValueRange() const;

		/// Get curve range length in time.
		Time			GetDuration() const { return GetTimeRange().valueRange(); }

		/// Optimize curve removing the less influential knots.
		uint			Optimize(float threshold = DefaultCurveOptimizationThreshold);

		/// Return the curve control points as an array.
		ArrayList <CurvePoint *> &GetPoints() { return points; }
		/// Return the number of control point in curve.
		uint			GetPointCount() const { return points.GetCount(); }

		/// Sort curve point array by time.
		void			Sort();
		/*!
			@short	Set a control point content.

			@Note	This function does modify the internal ordering of the curve
					keys in order to keep them time ordered.
					Please use the Sort() function to make sure the curve can
					still be correctly evaluated after a key time modification.
		*/
		void			SetPoint(uint index, const CurvePoint &content) const;

		/// Allocate a set of control points.
		bool			AllocatePoint(uint n);

		/*!
			@short Return the closest point on curve to a given time position.
			The default behavior is to return the first point whose time is
			greater than the query time.
		*/
		int				GetPointIndex(const Time &time, bool t_greater_than = true) const;

		void			Clear();

virtual	size_t			MemoryFootPrint() const { return size_t(points.GetCount()) * sizeof(CurvePoint) + sizeof(Curve); }

		bool			FromMetaTag(NML::Tag &);
		NML::Tag		*AsMetaTag() const;

virtual	~Curve();
};

} // GS


#endif	// __NCURVE__
