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


		#include "core/item.h"
		#include "geometry/bounding_box.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Core;


//------------------------------------------------------------------------------
Matrix4				Item::GetMatrixNoPivot()
{
	Matrix4 p = GetPivot();
	SetPivot(Matrix4::IdentityMatrix());
	Matrix4 m = GetMatrix();
	SetPivot(p);
	return m;
}
void				Item::ComputeLocalMatrix()
{
	if	(!item_flags.IsSet(ItemFlagLocalMatrixDirty))
		return;

	if	(!item_flags.IsSet(ItemFlagHasTarget) && item_flags.IsSet(ItemFlagRotationMatrixDirty))
	{
		rotation_matrix = Matrix3::FromEuler(rotation.x, rotation.y, rotation.z, rorder);
		item_flags.Remove(ItemFlagRotationMatrixDirty);
	}

	local_matrix =
					(
						Matrix4::TranslationMatrix(position) *
						Matrix4::FromMatrix3(rotation_matrix) *
						Matrix4::ScaleMatrix(scale)
					)
					*
					pivot_matrix;

	item_flags.Remove(ItemFlagLocalMatrixDirty);
}
void				Item::ComputeMatrix()
{
	bool update_local = item_flags.IsSet(ItemFlagLocalMatrixDirty),
		 update_world = item_flags.IsSet(ItemFlagWorldMatrixDirty);

	if	(update_local)
		ComputeLocalMatrix();

	if	(!parent)
	{
		if	(update_world)
			matrix = local_matrix;
	}
	else	if	(update_world)
	{
		matrix = parent->GetMatrix() * local_matrix;

		if	(item_flags.IsSet(ItemFlagInheritPositionOnly))
		{
			Matrix4 m(Matrix4::FromMatrix3(rotation_matrix));
			m.SetRow(3, matrix.GetRow(3));
			matrix = m;
		}
	}

	///	Handle item target and scale.
	if	(update_local || update_world)
		if	(item_flags.IsSet(ItemFlagHasTarget))
		{
			matrix.Decompose(NULL, NULL, &rotation_matrix);
			Matrix3 tgtm = Matrix3::FromOrthonormalBasis(target - matrix.GetRow(3));
			matrix = matrix * Matrix4::FromMatrix3(rotation_matrix.Transposed() * tgtm);
			rotation_matrix = tgtm;
		}

	item_flags.Remove(ItemFlagWorldMatrixDirty);
}
void				Item::ComputeInverseMatrix()
{
	if	(item_flags.IsSet(ItemFlagInverseWorldMatrixDirty))
	{
		imatrix = GetMatrix().InversedFast();
		item_flags.Remove(ItemFlagInverseWorldMatrixDirty);
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Item::MarkWorldTransformationDirty()
{
	item_flags.Set(ItemFlagWorldMatrixDirty | ItemFlagInverseWorldMatrixDirty);
	ListForeachPtr(Item *, i, children)
		i->MarkWorldTransformationDirty();
}
void				Item::MarkTransformationDirty(bool child_only)
{
	if	(!child_only)
		item_flags.Set(ItemFlagLocalMatrixDirty | ItemFlagWorldMatrixDirty | ItemFlagInverseWorldMatrixDirty);

	ListForeachPtr(Item *, i, children)
		i->MarkWorldTransformationDirty();
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Item::SetPosition(const Vector4 &p)
{	
	position = p;
	MarkTransformationDirty();
}
const Vector4		&Item::GetPosition() const
{	return position;	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Item::SetRotation(const Vector4 &r)
{
	rotation = r;
	item_flags.Set(ItemFlagRotationMatrixDirty);
	MarkTransformationDirty();
}
void				Item::SetRotation(const Quaternion &q)
{
	rotation_matrix = q.AsMatrix3();
	item_flags.Remove(ItemFlagRotationMatrixDirty);
	rotation = rotation_matrix.AsEuler(GetRotationOrder());
	MarkTransformationDirty();
}
void				Item::SetRotation(const Matrix3 &m)
{
	rotation_matrix = m;
	item_flags.Remove(ItemFlagRotationMatrixDirty);
	rotation = rotation_matrix.AsEuler(GetRotationOrder());
	MarkTransformationDirty();
}
const Vector4		&Item::GetRotation() const
{	return rotation;	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Item::SetScale(const Vector4 &s)
{
	scale = s;
	MarkTransformationDirty();
}
const Vector4		&Item::GetScale() const
{	return scale;	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Item::SetPivot(const Matrix4 &m)
{
	pivot_matrix = m;
	MarkTransformationDirty();
}
const Matrix4		&Item::GetPivot() const
{	return pivot_matrix;	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
const Matrix4		&Item::GetPreviousMatrix() const
{	return prv_matrix;	}
void				Item::SetPreviousMatrix(const Matrix4 &mtx)
{	prv_matrix = mtx;	}
const Matrix4		&Item::GetLocalMatrix()
{
	ComputeMatrix();
	return local_matrix;
}
const Matrix4		&Item::GetMatrix()
{
	ComputeMatrix();
	return matrix;
}
const Matrix4		&Item::GetInverseMatrix()
{
	ComputeInverseMatrix();
	return imatrix;
}
const Matrix3		&Item::GetRotationMatrix()
{
	ComputeMatrix();
	return rotation_matrix;
}
//------------------------------------------------------------------------------

//-------------------------------------------------------------------
void				Item::OffsetWorldPosition(const Vector4 &offset)
//-------------------------------------------------------------------
{
	if	(parent)
			position += (matrix.GetRow(3) + offset) * parent->imatrix - matrix.GetRow(3) * parent->imatrix;
	else	position += offset;

	MarkTransformationDirty();
}

//------------------------------------------------
bool				Item::IsLinkedTo(Item *item)
//------------------------------------------------
{
	for	(Item *c = this; c; )
	{
		if	(c == item)
			return true;
		c = c->GetParent();
	}
	return false;
}

//------------------------------------------------------------------
void				Item::ComputeLocalMinMax(MinMax &minmax) const
//------------------------------------------------------------------
{
	Vector4	w_position = matrix.GetRow(3);
	minmax.Set(w_position, w_position);
}

//------------------------------------------------------------------------------------------------
void				Item::SnapshotTransformation(const Matrix4 &m, bool local, bool only_matrix)
//------------------------------------------------------------------------------------------------
{
	local_matrix = (parent && !local) ? parent->GetInverseMatrix() * m : m;
	item_flags.Remove(ItemFlagLocalMatrixDirty);

	if	(!only_matrix)
	{
		// Remove pivot before extracting transformation components.
		(local_matrix * pivot_matrix.InversedFast()).Decompose(&position, &scale, &rotation_matrix);
		rotation = rotation_matrix.AsEuler(rorder);
		item_flags.Remove(ItemFlagRotationMatrixDirty);
	}

	MarkWorldTransformationDirty();
}

//--------------------------------------------
void				Item::SetParent(Item *p)
//--------------------------------------------
{
	if	(p == this)
		__ERRRAW__(__LOG_E__ << "Cannot parent item to itself.\n")

	// Already linked.
	if	(p && p->children.Find(this))	// FIXME why not if (parent == i)???
		return;

	// Catch and correct deadlock.
	for	(Item *c = p; c; c = c->GetParent())
		if	(c->GetParent() && (c->GetParent() == this))
		{
			c->SetParent(GetParent());		// Extract 'this' from the parent chain.
			__LOG_W__ << "Deadlock detected, link chain has been corrected.\n";
			break;
		}

	// Remove from current parent children list.
	if	(parent)
		parent->children.Remove(this);

	// Set parent.
	parent = p;

	// Insert as a new parent child.
	if	(parent)
		parent->children.Add(this);

	MarkTransformationDirty();
}

//----------------------------------------------------
void				Item::SetTarget(const Vector4 *p)
//----------------------------------------------------
{
	if	(p)
	{
		item_flags.Set(ItemFlagHasTarget);
		target = *p;
	}
	else
		item_flags.Remove(ItemFlagHasTarget);

	MarkTransformationDirty();
}

//-----------------------------------------------------
void				Item::SetMatrix(const Matrix4 &m)
//-----------------------------------------------------
{
	SnapshotTransformation(m, false);

	matrix = m;
	imatrix = m.InversedFast();
	item_flags.Remove(ItemFlagWorldMatrixDirty | ItemFlagInverseWorldMatrixDirty);

	MarkTransformationDirty(true);
}

//--------------------------------------------------------------
void				Item::SetRotationOrder(Math::rOrder order)
//--------------------------------------------------------------
{
	rorder = order;
	MarkTransformationDirty();
}

//------------------------------------------------------------------------------
Item::Item() : position(0, 0, 0), rotation(0, 0, 0), scale(1, 1, 1)
{
	local_matrix = Matrix4::IdentityMatrix();
	prv_matrix = Matrix4::IdentityMatrix();
	matrix = Matrix4::IdentityMatrix();
	imatrix = Matrix4::IdentityMatrix();
	pivot_matrix = Matrix4::IdentityMatrix();

	parent = NULL;

	rorder = Math::rOrder_Default;

	scale.Set(1,1,1,1);

	mitem = NULL;
	target.Set(0, 0, 1);

	opacity = 1;

	MarkTransformationDirty();
	item_flags.Set(ItemFlagRotationMatrixDirty);
}
Item::~Item()
{
	ListForeachPtr(Item *, child, children)
		child->SetParent(NULL);
	SetParent(NULL);
}
//------------------------------------------------------------------------------
