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


		#include <cmath>
		#include "geometry/curve.h"
		#include "sort/sort.h"

		using namespace GS;


//------------------------------------------------------------------------------
void				Curve::Update(const CurvePoint &p, const Time &t_epsilon)
{
	for (uint n = 0; n < points.GetCount(); ++n)
	{
		CurvePoint *point = points[n];
		if	((p.t >= (point->t - t_epsilon)) && (p.t <= (point->t + t_epsilon)))
		{
			point->v = p.v;
			return;
		}
	}
	Insert(p);
}
void				Curve::Insert(const CurvePoint &k)
{
	int idx = GetPointIndex(k.t);
	points.Insert(new CurvePoint(k), (idx == -1) ? points.GetCount() : idx);
}
void				Curve::Append(const CurvePoint &k)
{
	points.Insert(new CurvePoint(k), points.GetCount());
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Curve::Delete(CurvePoint *k)
{
	points.Remove(k);
	delete k;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
int					Curve::GetPointIndex(const Time &t, bool t_greater) const
{
	if	(points.GetCount() == 0)
		return -1;

	if	(t_greater)
	{
		for (uint n = 0; n < points.GetCount(); ++n)
			if	(points[n]->t > t)
				return n;
	}
	else
	{
		for (int n = points.GetCount() - 1; n >= 0; --n)
			if	(points[n]->t <= t)
				return n;
	}
	return -1;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
TimeRange			Curve::GetTimeRange() const
{
	return points.GetCount() == 0 ? TimeRange() : TimeRange(points[0]->t, points[points.GetCount() - 1]->t);
}
Range <float>		Curve::GetValueRange() const
{
	if	(points.GetCount() == 0)
		return Range <float> ();

	Range <float> range(points[0]->v, points[0]->v);
	for (uint n = 1; n < points.GetCount(); ++n)
	{
		range.start = Types::Min(range.start, points[n]->v);
		range.end = Types::Max(range.end, points[n]->v);
	}
	return range;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
uint				Curve::Optimize(uint point_count, const CurvePoint *skf, CurvePoint *dkf, float threshold)
{
	if	(point_count < 3)
		return 0;

	uint ckf = 0, n;
	for	(n = 1; n < (point_count - 1); n += 2)
	{
		float k = (skf[n].t - skf[n - 1].t).toSec() / (skf[n + 1].t - skf[n - 1].t).toSec();
		float iv = (skf[n - 1].v * k) + (skf[n + 1].v * (1.f - k));

		dkf[ckf++] = skf[n - 1];
		if	(fabs(skf[n].v - iv) > threshold)
			dkf[ckf++] = skf[n];
	}
	if	(n == (point_count - 1))
		dkf[ckf++] = skf[point_count - 2];
	dkf[ckf++] = skf[point_count - 1];
	return point_count - ckf;
}
uint				Curve::Optimize(float threshold)
{
	if	(!points.GetCount())
		return 0;

	Array <CurvePoint> skf(points.GetCount(), Alloc::Curve), dkf(points.GetCount(), Alloc::Curve);
	if	(skf.IsNull() || dkf.IsNull())
		__ERR__(__LOG__ << "Not enough memory.\n", 0);

	// Freeze array.
	for (uint n = 0; n < points.GetCount(); ++n)
		skf[n] = *points[n];

	// Optimize curve.
	uint gain = Optimize(points.GetCount(), skf.c_ptr(), dkf.c_ptr(), threshold), out = points.GetCount() - gain;

	if	(gain)
	{
		// Send back to curve.
		if	(!AllocatePoint(out))
			__ERR__(__LOG__ << "Failed to reallocate optimized array.\n", 0);
		for (uint n = 0; n < points.GetCount(); ++n)
			SetPoint(n, dkf[n]);
	}
	return gain;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Curve::AllocatePoint(uint n)
{
	ArrayListDeleteAllPtr(CurvePoint *, points)
	while (n--)
		if	(!points.Add(new CurvePoint))
			return false;

	return true;
}
void				Curve::SetPoint(uint i, const CurvePoint &p) const
{	*points[i] = p;	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Curve::Sort()
{
	// Make sure there is work to do.
	bool need_sorting = false;
	for (uint n = 1; n < points.GetCount(); ++n)
		if	(points[n - 1]->t > points[n]->t)
		{
			need_sorting = true;
			break;
		}
	if	(!need_sorting)
		return;

	// Sort keys.
	uint count = points.GetCount();

	Array <GS::Sort<Time, CurvePoint *>::Entry> entries(count);
	for (uint n = 0; n < count; ++n)
	{
		entries[n].v = points[n]->t;
		entries[n].o = points[n];
	}
	GS::Sort<Time, CurvePoint *>::QuickSort(count, entries);

	// Drop current array and rewrite ordered one.
	points.Clear(false);
	for (uint n = 0; n < count; ++n)
		points.Add(entries[n].o);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static float		range(float v, float lo, float hi, int *i)
{
	float r = hi - lo;

	if	(r == 0.f)
	{
		if	(i)
			*i = 0;
		return lo;
	}

	float v2 = v - lo;
	if	(v2 >= 0.f)
		v2 = lo + v2 - r * floor(v2 / r);
	else
		v2 = hi + v2 - r * ceil(v2 / r);

	if	(i)
		*i = -(int)((v2 - v) / r + (v2 > v ? 0.5f : -0.5f));

	return Types::Clamp(v2, lo, hi);
}
static void			hermite(float t, float *h1, float *h2, float *h3, float *h4)
{
	float t2 = t * t, t3 = t * t2;

	*h2 = 3.f * t2 - t3 - t3;
	*h1 = 1.f - *h2;
	*h4 = t3 - t2;
	*h3 = *h4 - t2 + t;
}
static float		bezier(float x0, float x1, float x2, float x3, float t)
{
	float a, b, c, t2 = t * t, t3 = t * t2;

	c = 3.f * (x1 - x0);
	b = 3.f * (x2 - x1) - c;
	a = x3 - x0 - c - b;

	return a * t3 + b * t2 + c * t + x0;
}
static float		bez2_time(float x0, float x1, float x2, float x3, float time, float *t0, float *t1)
{
	float t = *t0 + (*t1 - *t0) * 0.5f, v = bezier(x0, x1, x2, x3, t);

	if	((fabs(*t1 - *t0) > .0001f) && (fabs(time - v) > .0001f))
	{
		if	(v > time)
			*t1 = t;
		else
			*t0 = t;

		return bez2_time(x0, x1, x2, x3, time, t0, t1);
	}
	return t;
}
static float		bez2(const CurvePoint *key0, const CurvePoint *key1, float time)
{
	float x, y, t, t0 = 0.f, t1 = 1.f;

	if	(key0->shape == CurvePoint::Shape_Bezier2)
		x = key0->t.toSec() + key0->param[2];
	else
		x = key0->t.toSec() + (key1->t - key0->t).toSec() / 3.f;

	t = bez2_time(key0->t.toSec(), x, key1->t.toSec() + key1->param[0], key1->t.toSec(), time, &t0, &t1);

	if	(key0->shape == CurvePoint::Shape_Bezier2)
		y = key0->v + key0->param[3];
	else
		y = key0->v + key0->param[1] / 3.f;

	return bezier(key0->v, y, key1->param[1] + key1->v, key1->v, t);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
float				Curve::Outgoing(const CurvePoint *key0, const CurvePoint *key1, const CurvePoint *keyp) const
{
	float a, b, d, t, out;

	switch (key1->shape)
	{
		case CurvePoint::Shape_Linear:
			d = key1->v - key0->v;
			if	(keyp)
			{
				t = (key1->t - key0->t).toSec() / (key1->t - keyp->t).toSec();
				out = t * ((key0->v - keyp->v) + d);
			}
			else
				out = d;
			break;

		case CurvePoint::Shape_TCB:
			a = (1 - key0->tension)
			  * (1 + key0->continuity)
			  * (1 + key0->bias);
			b =	(1 - key0->tension)
			  * (1 - key0->continuity)
			  * (1 - key0->bias);
			d = key1->v - key0->v;

			if	(keyp)
			{
				t = (key1->t - key0->t).toSec() / (key1->t - keyp->t).toSec();
				out = t * (a * (key0->v - keyp->v) + b * d);
			}
			else
				out = b * d;
			break;

		case CurvePoint::Shape_Bezier:
		case CurvePoint::Shape_Hermite:
			out = key0->param[0];
			if	(keyp)
				out *= (key1->t - key0->t).toSec() / (key1->t - keyp->t).toSec();
			break;

		case CurvePoint::Shape_Bezier2:
			out = key0->param[3] * (key1->t - key0->t).toSec();
			if	(fabs(key0->param[2]) > 1e-5f)
				out /= key0->param[2];
			else
				out *= 1e5f;
			break;

		case CurvePoint::Shape_Step:
		default:
			out = 0;
			break;
	}
	return out;
}
float				Curve::Incoming(const CurvePoint *key0, const CurvePoint *key1, const CurvePoint *key2) const
{
	float a, b, d, t, in;

	switch (key1->shape)
	{
		case CurvePoint::Shape_Linear:
			d = key1->v - key0->v;
			if	(key2)
			{
				t = (key1->t - key0->t).toSec() / (key2->t - key0->t).toSec();
				in = t * ((key2->v - key1->v) + d);
			}
			else
				in = d;
			break;

		case CurvePoint::Shape_TCB:
			a = (1 - key1->tension)
			  * (1 - key1->continuity)
			  * (1 + key1->bias);
			b =	(1 - key1->tension)
			  * (1 + key1->continuity)
			  * (1 - key1->bias);
			d = key1->v - key0->v;

			if	(key2)
			{
				t = (key1->t - key0->t).toSec() / (key2->t - key0->t).toSec();
				in = t * (b * (key2->v - key1->v) + a * d);
			}
			else
				in = a * d;
			break;

		case CurvePoint::Shape_Bezier:
		case CurvePoint::Shape_Hermite:
			in = key1->param[0];
			if	(key2)
				in *= (key1->t - key0->t).toSec() / (key2->t - key0->t).toSec();
			break;

		case CurvePoint::Shape_Bezier2:
			in = key1->param[1] * (key1->t - key0->t).toSec();
			if	(fabs(key1->param[0]) > 1e-5f)
					in /= key1->param[0];
			else	in *= 1e5f;
			break;

		case CurvePoint::Shape_Step:
		default:
			in = 0;
			break;
	}
	return in;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Curve::Evaluate(Time t, float *p, LoopMode loop, Time loop_start, Time loop_end) const
{
	int point_count = points.GetCount();

	if	(point_count == 0)
	{
		*p = 0;
		return;
	}
	if	(point_count == 1)
	{
		*p = points[0]->v;
		return;
	}

	// Loop mode.
	CurvePoint *skey = points[0], *ekey = points[point_count - 1];

	loop_start = (loop_start == Time::Inf) ? skey->t : Types::Clamp(loop_start, skey->t, ekey->t);
	loop_end = (loop_end == Time::Inf) ? ekey->t : Types::Clamp(loop_end, skey->t, ekey->t);

	int noff = 0;
	float offset = 0;
	if	(t < loop_start)
	{
		switch (loop)
		{
			case Reset:
				*p = 0.f;
				return;

			default:
			case Constant:
				Evaluate(loop_start, p, loop, loop_start, loop_end);
				return;
			case Repeat:
				t.setSec(range(t.toSec(), loop_start.toSec(), loop_end.toSec(), NULL));
				break;
			case Oscillate:
				t.setSec(range(t.toSec(), loop_start.toSec(), loop_end.toSec(), &noff));
				if	(noff % 2)
					t = loop_end + loop_start - t;
				break;
			case OffsetAndRepeat:
				t.setSec(range(t.toSec(), loop_start.toSec(), loop_end.toSec(), &noff));
				offset = noff * (ekey->v - skey->v);		// Broken on custom loop point.
				break;
		}
	}
    else if (t > loop_end)
	{
		switch (loop)
		{
			case Reset:
				*p = 0.f;
				return;

			default:
			case Constant:
				Evaluate(loop_end, p, loop, loop_start, loop_end);
				return;
			case Repeat:
				t.setSec(range(t.toSec(), loop_start.toSec(), loop_end.toSec(), NULL));
				break;
			case Oscillate:
				t.setSec(range(t.toSec(), loop_start.toSec(), loop_end.toSec(), &noff));
				if	(noff % 2)
					t = loop_end + loop_start - t;
				break;
			case OffsetAndRepeat:
				t.setSec(range(t.toSec(), loop_start.toSec(), loop_end.toSec(), &noff));
				offset = noff * (ekey->v - skey->v);
				break;
		}
	}

	// Seek to current key.
	int ikey0;
#if	1
	{
		uint lo = 0, hi = points.GetCount() - 1;

		forever
		{
			uint mid = (lo + hi) / 2;

			if	(points[mid]->t > t)
				hi = mid;
			else
			{
				if	(lo == mid)
				{
					ikey0 = lo;
					break;
				}
				else
					lo = mid;
			}
		}
	}
#else
	ikey0 = 0;
	while (((ikey0 + 1) < point_count) && (t > points[ikey0 + 1]->t))
		ikey0++;
#endif

	CurvePoint *pkey0 = points[ikey0];

	if	(pkey0 == NULL)
		return;

	// Sample curve.
	CurvePoint *pkeyp = ikey0 > 0 ? points[ikey0 - 1] : NULL;

	int ikey1 = ikey0 + 1;
	CurvePoint *pkey1 = points[ikey1], *pkey2 = ikey1 < (point_count - 1) ? points[ikey1 + 1] : NULL;

	if	(t == pkey0->t)
		*p = pkey0->v + offset;

	else if (t == pkey1->t)
		*p = pkey1->v + offset;

	else
	{
		const float	k_t = (t - pkey0->t).toSec() / (pkey1->t - pkey0->t).toSec();

		switch (pkey0->shape)
		{
			case CurvePoint::Shape_TCB:
			case CurvePoint::Shape_Bezier:
			case CurvePoint::Shape_Hermite:
			{
				float out = Outgoing(pkey0, pkey1, pkeyp), in = Incoming(pkey0, pkey1, pkey2);

				float h1, h2, h3, h4;
				hermite(k_t, &h1, &h2, &h3, &h4);
				*p = h1 * pkey0->v + h2 * pkey1->v + h3 * out + h4 * in + offset;
			}
			break;

			case CurvePoint::Shape_Bezier2:
				*p = bez2(pkey0, pkey1, k_t) + offset;
				break;

			case CurvePoint::Shape_Linear:
				*p = pkey0->v + k_t * (pkey1->v - pkey0->v) + offset;
				break;

			case CurvePoint::Shape_Step:
				*p = pkey0->v + offset;
				break;

			default:
				*p = offset;
				break;
		}
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Curve::Clear()
{
	ArrayListDeleteAllPtr(CurvePoint *, points)
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Curve::~Curve()
{	Clear();	}
//------------------------------------------------------------------------------
