#include "../../Scene/Controller/RuController_Hierarchy.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

ruRTTI_DEFINE_SUBCLASS(CRuController_Hierarchy, IRuEntity_Controller)
ruHEAP_DEFINE_SUBCLASS(CRuController_Hierarchy, "CRuController_Hierarchy", "IRuEntity_Controller")
ruCLASSGUID_DEFINE(CRuController_Hierarchy, 0x69F1B94E, 0x486B4bf4, 0x8D0BF5FD, 0xC28A6791)

// ************************************************************************************************************************************************************

CRuController_Hierarchy::CRuController_Hierarchy()
{
	m_targetEntity = NULL;
	m_targetInterpolator = NULL;

	m_applied = FALSE;
	m_animation = NULL;

	m_embedAnimation = FALSE;
	m_animationName = NULL;
	m_blendInTime = 0.0f;
	m_blendOutTime = 0.0f;
	m_loopTime = -1.0f;

	// Register as listener for self in order to handle the ruENTITYEVENT_ONDESTROY event.
	this->RegisterListener(this, NULL);
}

CRuController_Hierarchy::~CRuController_Hierarchy()
{
	// NOTE: Destruction of resources is done during the ruENTITYEVENT_ONDESTROY event.
}

IRuObject *CRuController_Hierarchy::Clone(IRuObject *clonedObject)
{
	// Create a new cloned entity if necessary
	clonedObject = clonedObject ? clonedObject : ruNEW CRuController_Hierarchy();
	CRuController_Hierarchy *controller = static_cast<CRuController_Hierarchy *>(clonedObject);

	// Chain cloning call to parent class
	IRuEntity_Controller::Clone(clonedObject);

	controller->m_embedAnimation = m_embedAnimation;

	if(m_animationName)
	{
		controller->m_animationName = ruNEW char [strlen(m_animationName) + 1];
		memcpy(controller->m_animationName, m_animationName, strlen(m_animationName) + 1);
	}

	if(m_animation)
	{
		controller->m_animation = m_animation;
		controller->m_animation->AddRef();
	}

	controller->m_blendInTime = m_blendInTime;
	controller->m_blendOutTime = m_blendOutTime;

	return clonedObject;
}

BOOL CRuController_Hierarchy::SerializeFrom(IRuStream *stream)
{
	CRuGUID classGUID;

	// Read class GUID
	if(RuReadGUID(stream, classGUID) == FALSE)
	{
		return FALSE;
	}

	// Read version
	INT32 version = 1;
	stream->Read(&version, sizeof(INT32));

	// Chain serialization to base class
	IRuEntity_Controller::SerializeFrom(stream);

	switch(version)
	{
		case 1:
			{
				// Read embedding setting
				stream->Read(&m_embedAnimation, sizeof(BOOL));

				if(m_embedAnimation)
				{
					m_animation = LoadRuAnimation(stream);

					// Read blend in time
					stream->Read(&m_blendInTime, sizeof(m_blendInTime));

					// Read blend out time
					stream->Read(&m_blendOutTime, sizeof(m_blendOutTime));
				}
				else
				{
					UINT32 nameLength;
					stream->Read(&nameLength, sizeof(nameLength));
					m_animationName = ruNEW char [nameLength];
					stream->Read(m_animationName, nameLength);

					// Read blend in time
					stream->Read(&m_blendInTime, sizeof(m_blendInTime));

					// Read blend out time
					stream->Read(&m_blendOutTime, sizeof(m_blendOutTime));
				}
			}

			break;
	}

	return TRUE;
}

BOOL CRuController_Hierarchy::SerializeTo(IRuStream *stream)
{
	// Write class GUID
	stream->Write(GetClassGUID().Data(), GetClassGUID().Size());

	// Write version
	INT32 version = 1;
	stream->Write(&version, sizeof(INT32));

	// Chain serialization to base class
	IRuEntity_Controller::SerializeTo(stream);

	// Write embedding setting
	stream->Write(&m_embedAnimation, sizeof(BOOL));

	if(m_embedAnimation && m_animation)
	{
		// Write animation
		SaveRuAnimation(stream, m_animation);

		// Write blend in time
		stream->Write(&m_blendInTime, sizeof(m_blendInTime));

		// Write blend out time
		stream->Write(&m_blendOutTime, sizeof(m_blendOutTime));
	}
	else
	{
		// Write animation name length including NULL followed by the name
		if(m_animationName)
		{
			UINT32 nameLength = strlen(m_animationName) + 1;
			stream->Write(&nameLength, sizeof(nameLength));
			stream->Write(m_animationName, nameLength);
		}
		else
		{
			UINT32 nameLength = strlen("") + 1;
			stream->Write(&nameLength, sizeof(nameLength));
			stream->Write("", nameLength);
		}

		// Write blend in time
		stream->Write(&m_blendInTime, sizeof(m_blendInTime));

		// Write blend out time
		stream->Write(&m_blendOutTime, sizeof(m_blendOutTime));
	}

	return TRUE;
}

BOOL CRuController_Hierarchy::Update(REAL elapsedTime)
{
	// The idea is the controller goes to sleep after it starts the animation, hence it sets t to -1
	if(m_enabled && m_targetInterpolator && (m_animation || InstanceAnimation()))
	{
		if(!m_applied)
		{
			m_targetInterpolator->PlayAnimation(m_animation, m_startTime, m_blendInTime, m_blendOutTime, m_loopTime);
			m_applied = TRUE;
		}
	}

	// Chain update call to base controller class
	return IRuEntity_Controller::Update(elapsedTime);
}

REAL CRuController_Hierarchy::GetDuration()
{
	// If animation is not instanced, instance it
	if(m_animation == NULL)
	{
		InstanceAnimation();
	}

	return IRuEntity_Controller::GetDuration();
}

IRuObject *CRuController_Hierarchy::GetTarget()
{
	return m_targetInterpolator;
}

BOOL CRuController_Hierarchy::SetTarget(IRuObject *target)
{
	if(target->GetType() == CRuFrameHierarchy::Type)
	{
		// If we had a previous target, unregister it
		if(m_targetEntity)
		{
			m_targetEntity->UnregisterListener(this);
		}

		// Store target reference and setup event listener
		m_targetEntity = static_cast<CRuEntity *>(target);
		m_targetEntity->RegisterListener(this, NULL);

		// Store target interpolator reference
		m_targetInterpolator = static_cast<CRuFrameHierarchy *>(target)->GetInterpolator();

		return TRUE;
	}

	if(target->GetType() == CRuAnimationInterpolator::Type)
	{
		m_targetInterpolator = static_cast<CRuAnimationInterpolator *>(target);

		return TRUE;
	}

	return FALSE;
}

const char *CRuController_Hierarchy::GetAnimationName()
{
	if(m_animationName == NULL)
	{
		return "";
	}

	return m_animationName;
}

REAL CRuController_Hierarchy::GetBlendInTime()
{
	return m_blendInTime;
}

REAL CRuController_Hierarchy::GetBlendOutTime()
{
	return m_blendOutTime;
}

void CRuController_Hierarchy::SetAnimation(const char *animationName)
{
	if(animationName)
	{
		// Release reference to existing animation
		ruSAFE_RELEASE(m_animation);

		// Release animation name
		delete[] m_animationName;
		m_animationName = NULL;

		// Copy animation name
		m_animationName = ruNEW char [strlen(animationName) + 1];
		memcpy(m_animationName, animationName, strlen(animationName) + 1);

		// Reset duration
		IRuEntity_Controller::SetDuration(0.0f);
	}
}

void CRuController_Hierarchy::SetAnimation(CRuAnimation *animation)
{
	if(animation)
	{
		// Release reference to existing animation
		ruSAFE_RELEASE(m_animation);

		// Release animation name
		delete[] m_animationName;
		m_animationName = NULL;

		// Force animation embedding
		m_embedAnimation = TRUE;
		m_animation = animation;

		// Update animation duration
		IRuEntity_Controller::SetDuration(m_animation->GetDuration());
	}
}

void CRuController_Hierarchy::SetBlendInTime(REAL blendInTime)
{
	m_blendInTime = blendInTime;
}

void CRuController_Hierarchy::SetBlendOutTime(REAL blendOutTime)
{
	m_blendOutTime = blendOutTime;
}

BOOL CRuController_Hierarchy::HandleEvent(CRuEntity *entity, INT32 eventID, INT32 eventParameter0, INT32 eventParameter1, const char *eventString, REAL eventDuration, void *userData)
{
	// Chain event handler to base class
	if(IRuEntity_Controller::HandleEvent(entity, eventID, eventParameter0, eventParameter1, eventString, eventDuration, userData) == TRUE)
	{
		return TRUE;
	}

	// Ensure that the broadcasting entity is the target entity for this controller or the controller itself
	if(entity != m_targetEntity && entity != this)
	{
		return FALSE;
	}

	// Handle event
	switch(eventID)
	{
		case ruENTITYEVENT_ONDESTROY:		// Target entity is being destroyed, unregister the controller
			{
				if(m_targetEntity)
				{
					m_targetEntity->UnregisterListener(this);
					m_targetEntity = NULL;
				}

				// Reset target interpolator to null
				m_targetInterpolator = NULL;

				// Release reference to existing animation
				ruSAFE_RELEASE(m_animation);

				// Release animation name string
				delete[] m_animationName;
			}

			break;
	}

	return FALSE;
}

BOOL CRuController_Hierarchy::InstanceAnimation()
{
	if(m_animationName)
	{
		ruSAFE_RELEASE(m_animation);

		// Load animation using resource manager
		m_animation = g_ruResourceManager->LoadAnimation(m_animationName);

		if(m_animation)
		{
			IRuEntity_Controller::SetDuration(m_animation->GetDuration());
			return TRUE;
		}
	}

	return FALSE;
}

// ************************************************************************************************************************************************************

#pragma managed
