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

#include "../../Engine/Base/RuShader_Composite.h"

#pragma unmanaged

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

ruRTTI_DEFINE_SUBCLASS(CRuController_Highlighter, IRuEntity_Controller)
ruHEAP_DEFINE_SUBCLASS(CRuController_Highlighter, "CRuController_Highlighter", "IRuEntity_Controller")
ruCLASSGUID_DEFINE(CRuController_Highlighter, 0x7ED1695A, 0x4B0243fb, 0xA435A04A, 0x66D32EB3)

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

CRuController_Highlighter::CRuController_Highlighter()
:	m_targetEntity(NULL),
	m_pulsePeriod(1.0f),
	m_highlightColor_0(0xFFFFFFFF),
	m_highlightColor_1(0xFFFFFFFF)
{
	// Override parameters
	m_startTime = 0.0f;
	m_duration = 0.0f;
	m_loopTime = 0.0f;

	// Get reference to the highlighter shaders
	m_highlighterShader_Static = g_ruResourceManager->GetShader("ruSHADER_STATIC_HIGHLIGHT");

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

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

void CRuController_Highlighter::SetPulsePeriod(REAL pulsePeriod)
{
	m_pulsePeriod = pulsePeriod;
}

void CRuController_Highlighter::SetHighlightColor0(const CRuColor4 &highlightColor0)
{
	m_highlightColor_0 = highlightColor0;
}

void CRuController_Highlighter::SetHighlightColor1(const CRuColor4 &highlightColor1)
{
	m_highlightColor_1 = highlightColor1;
}

REAL CRuController_Highlighter::GetPulsePeriod() const
{
	return m_pulsePeriod;
}

const CRuColor4 &CRuController_Highlighter::GetHighlightColor0() const
{
	return m_highlightColor_0;
}

const CRuColor4 &CRuController_Highlighter::GetHighlightColor1() const
{
	return m_highlightColor_1;
}

BOOL CRuController_Highlighter::Update(REAL elapsedTime)
{
	// Override parameters
	m_startTime = 0.0f;
	m_duration = 0.0f;
	m_loopTime = 0.0f;

	if(m_enabled)
	{
		RUCOLOR currentColor;
		if(m_pulsePeriod > ruEPSILON)
		{
			REAL relativeT = fmod(m_age, m_pulsePeriod) / m_pulsePeriod;

			if(relativeT <= 0.5f)
			{
				relativeT = relativeT * 2.0f;
			}
			else
			{
				relativeT = 1.0f - (relativeT - 0.5f) * 2.0f;
			}

			currentColor = (m_highlightColor_1 * relativeT  + m_highlightColor_0 * (1.0f - relativeT)).GetARGB();
		}

		// Iterate through all target materials
		for(INT32 i = 0; i < m_targetMaterials.Count(); ++i)
		{
			CRuParameterBlock *parameterBlock = m_targetMaterials[i].m_targetMaterial->GetParameterBlock();
			parameterBlock->SetParameter(ruPARAMTYPE_DWORD_0, currentColor);
		}
	}

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

IRuObject *CRuController_Highlighter::GetTarget()
{
	return m_targetEntity;
}

BOOL CRuController_Highlighter::SetTarget(IRuObject *target)
{
	// If the target has not changed, ignore
	if(target == m_targetEntity)
	{
		return TRUE;
	}

	// If we had a previous target, release it
	ReleaseTarget();

	if(target && target->GetType().IsSubClassOf(CRuEntity::Type))
	{
		// Store target reference and setup event listener
		m_targetEntity = static_cast<CRuEntity *>(target);
		m_targetEntity->RegisterListener(this, NULL);

		// Iterate through target entity's renderables
		CRuStack<CRuEntity *> entityStack;
		entityStack.Push(static_cast<CRuEntity *>(target));

		while(!entityStack.IsEmpty())
		{
			CRuEntity *curEntity = entityStack.Pop();

			if(curEntity->GetType().IsSubClassOf(IRuEntity_Container::Type))
			{
				IRuEntity_Container *container = static_cast<IRuEntity_Container *>(curEntity);

				// Push all entities held by the container onto the entity stack
				for(INT32 i = 0, numEntities = container->GetNumEntities(); i < numEntities; ++i)
				{
					entityStack.Push(container->GetEntity(i));
				}
			}
			else if(curEntity->GetType().IsSubClassOf(IRuEntity_Renderable::Type))
			{
				IRuEntity_Renderable *model = static_cast<IRuEntity_Renderable *>(curEntity);

				for(INT32 i = 0, numMeshes = model->GetNumMeshes(); i < numMeshes; ++i)
				{
					IRuMesh *mesh = NULL;
					IRuMaterial *material = NULL;

					if(model->GetMesh(i, &mesh, &material))
					{
						TargetMaterialDesc tMatDesc;
						tMatDesc.m_targetMaterial = material;
						tMatDesc.m_targetMaterial->AddRef();
						tMatDesc.m_oldShader = material->GetShader();

						if(m_highlighterShader_Static->GetVertexDeclaration() == tMatDesc.m_oldShader->GetVertexDeclaration())
						{
							tMatDesc.m_highlighterShader = ruNEW CRuShader_Composite();

							static_cast<CRuShader_Composite *>(tMatDesc.m_highlighterShader)->AppendShader(tMatDesc.m_oldShader);
							static_cast<CRuShader_Composite *>(tMatDesc.m_highlighterShader)->AppendShader(m_highlighterShader_Static);
						}
						else
						{
							tMatDesc.m_highlighterShader = NULL;
						}

						if(tMatDesc.m_highlighterShader)
						{
							tMatDesc.m_targetMaterial->SetShader(tMatDesc.m_highlighterShader);
							m_targetMaterials.Add(tMatDesc);
						}
					}
				}
			}
		}

		return TRUE;
	}

	return FALSE;
}

BOOL CRuController_Highlighter::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
			{
				ReleaseTarget();
			}

			break;
	}

	return FALSE;
}

void CRuController_Highlighter::ReleaseTarget()
{
	if(m_targetEntity)
	{
		// Unregister target entity
		m_targetEntity->UnregisterListener(this);
		m_targetEntity = NULL;

		// Iterate through all target materials
		for(INT32 i = 0; i < m_targetMaterials.Count(); ++i)
		{
			// Clear global diffuse parameter
			CRuParameterBlock *parameterBlock = m_targetMaterials[i].m_targetMaterial->GetParameterBlock();
			parameterBlock->ClearParameter(ruPARAMTYPE_DWORD_0);

			// Replace old shader on the target material
			m_targetMaterials[i].m_targetMaterial->SetShader(m_targetMaterials[i].m_oldShader);

			// Release reference on target material
			ruSAFE_RELEASE(m_targetMaterials[i].m_targetMaterial);

			// Release highlighter shader
			ruSAFE_RELEASE(m_targetMaterials[i].m_highlighterShader);
		}

		m_targetMaterials.Clear();
	}
}

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

#pragma managed
