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


		#include <stdio.h>
		#include "geometry/curve.h"
		#include "math/nmath.h"
		#include "sort/sort.h"
		#include "alloc/ialloc.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::NML;


//------------------------------------------------------------------------------
Reflection::Enum::Dict Curve::loop_mode_dict[] =
{
	{ Curve::Reset, "Reset" },
	{ Curve::Constant, "Constant" },
	{ Curve::Repeat, "Repeat" },
	{ Curve::Oscillate, "Oscillate" },
	{ Curve::OffsetAndRepeat, "OffsetAndRepeat" },
	{ 0, 0 }
};
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// ARM odd address read/write helper functions.
void	ARM_unaligned_read(float &out, const char *addr)
{
	char *p_out = (char *)&out;
	for (int n = 0; n < sizeof(float); ++n)
		p_out[n] = addr[n];
}
void	ARM_unaligned_write(char *addr, const float &in)
{
	const char *p_in = (const char *)&in;
	for (int n = 0; n < sizeof(float); ++n)
		addr[n] = p_in[n];
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Curve::FromMetaTag(Tag &tag)
{
	if	(tag.name != "Curve")
		__ERR__(__LOG_E__ << "Could not parse curve, incorrect root tag (" << tag.name << ").\n", false)

	Clear();

	// Parse root tags.
	NMLTagForeach(pt, tag)
	{
		if	(pt->name == "BinaryKnot")
		{
			Tag *count_tag = pt->GetTag("Count"), *data_tag = pt->GetTag("Data");

			if	(count_tag && data_tag)
			{
				char *data = (char *)data_tag->GetValue().GetBinaryBuffer(), *p_data = data;

				if	(data && AllocatePoint(count_tag->GetInteger()))
					for (uint n = 0; n < points.GetCount(); ++n)
					{
						CurvePoint *p = points[n];

						p->shape = CurvePoint::Shape(*p_data++);

						float t;
						ARM_unaligned_read(t, p_data + 0);
						p->t.setSec(t);
						ARM_unaligned_read(p->v, p_data + 4);

						if	(p->shape == CurvePoint::Shape_Linear)
							p_data += 2 * 4;

						else
						{
							ARM_unaligned_read(p->tension, p_data + 8);
							ARM_unaligned_read(p->continuity, p_data + 12);
							ARM_unaligned_read(p->bias, p_data + 16);

							for (int n = 0; n < 4; ++n)
								ARM_unaligned_read(p->param[n], p_data + 20 + n * 4);

							p_data += 9 * 4;
						}
					}
			}
		}
		else if (pt->name == "Knot")
		{
			Tag *st = pt->GetTags()[0];
			if	(!st || (st->name != "Count"))
				__ERR__(__LOG_E__ << "First sub-tag in <Curve> must be the knot <Count> tag.\n", false)

			if	(!AllocatePoint((uint)st->GetInteger()))
				return false;

			static String _count("Count"), _knot("Knot"), _knotex("KnotEx");

			uint n = 0;
			NMLTagForeach(st, *pt)
			{
				if	(st->name == _count)
				{}

				// Legacy knot definition.
				if	(st->name == _knot)
				{
					if	(n == points.GetCount())
					{
						__LOG_E__ << "Too many knot in <Curve>, " << points.GetCount() << " expected.\n";
						break;
					}

					if	(const char *p = st->GetString())
					{
						points[n]->t = Time::fromSec(String::atof(p));
						points[n]->shape = CurvePoint::Shape_Linear;

						p = String::strfindchar(p, ':');
						points[n]->v = p[0] ? String::atof(p + 1) : 0;

						n++;
					}
					else
						__LOG_W__ << "Invalid knot tag while parsing curve.\n";
				}

				/*
					Extended knot definition.
				*/
				else	if	(st->name == _knotex)
				{
					if	(n == points.GetCount())
					{
						__LOG_E__ << "Too many knot in <Knot>, " << points.GetCount() << " specified.\n";
						break;
					}

					if	(const char	*p = st->GetString())
					{
						CurvePoint	*_knot = points[n];
						_knot->t = Time::fromSec(String::atof(p));

						// Read shape.
						p = String::strfindchar(p, ':');
						int shape = p[0] ? String::atoi(p + 1) : 0;
						p++;

						switch (shape)
						{
							default:
							case 0:	_knot->shape = CurvePoint::Shape_None;		break;
							case 1:	_knot->shape = CurvePoint::Shape_Linear;	break;
							case 2:	_knot->shape = CurvePoint::Shape_Bezier;	break;
							case 3:	_knot->shape = CurvePoint::Shape_Bezier2;	break;
							case 4:	_knot->shape = CurvePoint::Shape_Hermite;	break;
							case 5:	_knot->shape = CurvePoint::Shape_TCB;		break;
							case 6:	_knot->shape = CurvePoint::Shape_Step;		break;
						}

						// Read knot parameters.
						//--------------------------------------------
						#define GetInputKnotParamEx(_PARM_)\
						{\
							p = String::strfindchar(p, ':');\
							(_PARM_) = p[0] ? String::atof(p + 1) : -1;\
							p++;\
						}
						//--------------------------------------------

						GetInputKnotParamEx(_knot->v);
						GetInputKnotParamEx(_knot->tension);
						GetInputKnotParamEx(_knot->continuity);
						GetInputKnotParamEx(_knot->bias);

						GetInputKnotParamEx(_knot->param[0]);
						GetInputKnotParamEx(_knot->param[1]);
						GetInputKnotParamEx(_knot->param[2]);
						GetInputKnotParamEx(_knot->param[3]);
						n++;
					}
					else
						__LOG_W__ << "Invalid extended knot tag while parsing curve.\n";
				}
				else
					__LOG_W__ << "Unsupported knot tag '" << st->name << "'.\n";
			}

			// Incomplete/erroneous definition.
			if	(n != points.GetCount())
			{
				Clear();
				__ERR__(__LOG_E__ << "<Curve> is corrupted, discarding.\n", false)
			}
		}
		else	__LOG_W__ << "Unknown tag '" << pt->name << "' in <Curve>.\n";
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Tag					*Curve::AsMetaTag() const
{
	Tag *root = new Tag("Curve");
	if	(!root)
		__ERR__(__LOG_E__ << "Could not create curve root tag to serialize.\n", NULL)

	// Binary knots.
	if	(points.GetCount())
		if	(Tag *binary_knot_tag = root->AddChild("BinaryKnot"))
		{
			binary_knot_tag->AddChild("Count", points.GetCount());

			// Get size.
			int size = 0;
			for	(uint n = 0; n < points.GetCount(); ++n)
			{
				CurvePoint *_knot = points[n];

				// Legacy definition.
				if	(_knot->shape == CurvePoint::Shape_Linear)
						size += 2 * 4;		// Knot size.
				else	size += 9 * 4;		// Extended knot size.
			}

			// Output binary.
			Array <char> knot_array(points.GetCount() + size);
			char *p_knot = knot_array;

			for	(uint n = 0; n < points.GetCount(); ++n)
			{
				CurvePoint *_knot = points[n];

				*p_knot++ = uchar(_knot->shape);

				float t = _knot->t.toSec();
				ARM_unaligned_write(p_knot + 0, t);
				ARM_unaligned_write(p_knot + 4, _knot->v);

				if	(_knot->shape == CurvePoint::Shape_Linear)
					p_knot += 2 * 4;

				else
				{
					ARM_unaligned_write(p_knot + 8, _knot->tension);
					ARM_unaligned_write(p_knot + 12, _knot->continuity);
					ARM_unaligned_write(p_knot + 16, _knot->bias);

					for (int n = 0; n < 4; ++n)
						ARM_unaligned_write(p_knot + 20 + n * 4, _knot->param[n]);

					p_knot += 9 * 4;
				}
			}

			binary_knot_tag->AddChild("Data", knot_array, points.GetCount() + size);
		}

	return root;
}
//------------------------------------------------------------------------------
