///////////////////////////////////////////////////////////////////////////////
//
// File: AVP_ClientEntity_Container.cpp
//
// Created by Martin G.Harris (20/8/2008)
// 
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
// Project-specific client entity that manages a container instance.
//
///////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"
#include "Entity/Container/AVP_ClientEntity_Container.h"

#ifdef ASURA_USE_CONTAINER_SYSTEM

// Includes
// Asura
#include "Core/Asura_LOS.h"
#include "Core/Asura_MaterialResponse_System.h"
#include "Entity/Container/Asura_ContainerRegistry.h"
#include "Light/Asura_Photon.h"
#include "Network/Asura_Message.h"
#include "Physics/Asura_Physics_Object.h"
#include "Projectors/Asura_Projector.h"
#include "Sound/Asura_Sound_Event_Storage.h"
#include "Sound/Asura_Sound_Event_System.h"

// AvP
#include "Core/AVP_SurfaceEffects.h"
#include "Entity/AVP_Messages.h"
#include "Entity/Damage/AVP_EntityDamage_Messages.h"
#include "Mesh/AVP_PolygonFlags.h"
#include "Network/AVP_Client.h"

// Register the client entity.
Asura_ContainerRegisterClientEntity<AVP_ClientEntity_Container> xRegisterAVPClientEntity;

///////////////////////////////////////////////////////////////////////////////
// AVP_ClientEntity_Container
///////////////////////////////////////////////////////////////////////////////
AVP_ClientEntity_Container::AVP_ClientEntity_Container()
: m_bIgnoreForSurfaceReorientation(true)
{
}

AVP_ClientEntity_Container::~AVP_ClientEntity_Container()
{
}

// Fill out the specified number of bytes of project-specific data.
void AVP_ClientEntity_Container::ProcessProjectCreateMessageData(u_int uNumBytes, const void* pData)
{	
	const char* pcData = reinterpret_cast<const char*>(pData);
	const bool bIgnoreForSurfaceReorientation = ((*pcData & 1) != 0);
	
	SetIgnoredForSurfaceReorientation(bIgnoreForSurfaceReorientation);
}

//////////////////////////////
// PostChangeInitialisation //
//////////////////////////////
// Called after the creation of the entity, and after a state change, activation, or deactivation.
// Gives projects a chance to update any project-specific stuff.
void AVP_ClientEntity_Container::PostChangeInitialisation()
{
	// Refresh the polygon flags.
	UpdateIgnoredForSurfaceReorientation();
}

///////////////////////////////////////
// SetIgnoredForSurfaceReorientation //
///////////////////////////////////////
void AVP_ClientEntity_Container::SetIgnoredForSurfaceReorientation(bool bIgnore)
{	
	m_bIgnoreForSurfaceReorientation = bIgnore;

	UpdateIgnoredForSurfaceReorientation();
}

//////////////////////////////////////////
// UpdateIgnoredForSurfaceReorientation //
//////////////////////////////////////////
// Traverse the client instance setting the polygon flags.
void AVP_ClientEntity_Container::UpdateIgnoredForSurfaceReorientation()
{	
	if (m_pxInstance)
	{
		AVP_Container_Functor_SetSurfaceReorientationFlag xFunctor(m_bIgnoreForSurfaceReorientation);
		m_pxInstance->Traverse(xFunctor);
	}
}

///////////////////
// HandleMessage //
///////////////////
bool AVP_ClientEntity_Container::HandleMessage(const Asura_Message* pxMessage)
{
	if (!pxMessage) return false;

	switch (pxMessage->GetMsgID())
	{
	default:
		break;

	case AVP_MSGID_ENTITYDAMAGE_APPLY_TO_ENTITY:
		{
			const AVP_Message_EntityDamage_ApplyToEntity* pxData = static_cast<const AVP_Message_EntityDamage_ApplyToEntity*>(pxMessage->GetData());

			AVP_EntityDamage_ApplyToEntity xDamage;
			pxData->GetDamage(xDamage);

			// Note: currently m_fAmount is set to the actual reduction in the health.
			const float fNewHealth = xDamage.GetHealth();
			if (xDamage.GetAmount() > 0.0f && fNewHealth <= 0.0f)
			{
				OnDeath(xDamage);
			}
			else
			{
				OnApplyDamage(xDamage);
			}

			return true;
		}
	}

	return PARENT::HandleMessage(pxMessage);
}

/////////////
// OnDeath //
/////////////
// Damage message received from server.
void AVP_ClientEntity_Container::OnDeath(const AVP_EntityDamage_ApplyToEntity& xDamage)
{
	OnApplyDamage(xDamage);

	// TODO put any death effects here.
}

///////////////////
// OnApplyDamage //
///////////////////
void AVP_ClientEntity_Container::OnApplyDamage(const AVP_EntityDamage_ApplyToEntity& xDamage)
{	
	// Look up the animation. Apply any impulses.
	Asura_Animation* pxAnimation = 0;
	if (m_pxInstance)
	{
		const u_int uUniqueID = static_cast<u_int>(xDamage.GetAttachmentID());
		Asura_ClientContainerInstance* pxInstance = m_pxInstance->FindFromUniqueID(uUniqueID);
		if (pxInstance)
		{
			pxAnimation = pxInstance->GetAnimation();

			if (Asura_Network::IsServer() &&
				pxInstance->GetClassification() == AsuraContainerClass_Mesh)
			{
				// Apply damage impulse.
				Asura_ClientContainerInstance_Mesh* pxMesh = static_cast<Asura_ClientContainerInstance_Mesh*>(pxInstance);
				
				Asura_Vector_3 xDirection;
				Asura_Vector_3 xPointOfImpact;
				xDamage.GetDirection(xDirection);
				xDamage.GetPointOfImpact(xPointOfImpact);

				const Asura_Vector_3 xImpulse(xDirection * xDamage.GetImpulseStrength());
				pxMesh->ApplyImpulse(xImpulse, xPointOfImpact, false);
			}
		}
	}

	// Look up the material type.
	const Asura_Hash_ID uMaterialResponseHashID = xDamage.GetMaterialResponseHashID();
	const Asura_MaterialResponse* const pxResponse = Asura_MaterialResponse_System::GetMaterialByHashID(uMaterialResponseHashID, true);

	Asura_Hash_ID uMaterialType = ASURA_HASH_ID_UNSET;
	if (pxResponse)
	{
		uMaterialType = pxResponse->GetSurfaceTypeHash();
	}
	
	if (uMaterialType == ASURA_HASH_ID_UNSET)
	{
		// Nothing to do.
		return;
	}

	AVP_SurfaceEffect_System::PlayDamageSurfaceEffect(uMaterialType, xDamage, GetGuid());
}

///////////////////////////////////////////////////////////////////////////////
// AVP_Container_Functor_GetLuminance
///////////////////////////////////////////////////////////////////////////////
// Get rough luminance value at waypoint position. Called from AVP_Waypoint_Manager.

/////////////
// Process //
/////////////
void AVP_Container_Functor_GetLuminance::Process(const Asura_ClientContainerInstance* pxEntry)
{
	if (pxEntry->GetFlag(ASURA_CONTAINER_INSTANCE_FLAG_DISABLED))
	{
		return;
	}

	const u_int uClass = pxEntry->GetClassification();

	if (uClass == AsuraContainerClass_Projector)
	{
		ProcessProjector(static_cast<const Asura_ClientContainerInstance_Projector*>(pxEntry));
	}
	else if (uClass == AsuraContainerClass_Photon)
	{
		ProcessPhoton(static_cast<const Asura_ClientContainerInstance_Photon*>(pxEntry));
	}
}

//////////////////////
// ProcessProjector //
//////////////////////
void AVP_Container_Functor_GetLuminance::ProcessProjector(const Asura_ClientContainerInstance_Projector* pxProjectorInstance)
{	
	const Asura_Projector* const pxProjector = pxProjectorInstance->GetProjector();
	const Asura_Container_Projector_Param& xParams = pxProjectorInstance->GetParam();
	if (!pxProjector) return;

	// Intensity is from 0 to 1 (or greater than 1 for HDR).
	const float* const pfRGB = pxProjector->GetRGB();
	float fIntensity = (pfRGB[0] +pfRGB[1] + pfRGB[2]) / 3.0f;
	if (fIntensity <= 0.0f)
	{
		// Totally black or negative light.
		return;
	}

	const Asura_Vector_3& xProjectorPos = pxProjector->GetPosition();
	Asura_Vector_3 xToLight = ( xProjectorPos - m_xPosition );
	const float    fToLight = xToLight.Normalise();
	float fDist = (fToLight - xParams.m_fMinRange)/(xParams.m_fMaxRange - xParams.m_fMinRange);
	if (fDist < 0.0f || fDist > 1.0f)
	{
		return;
	}
	
	if ( !pxProjector->PointInFrustum( m_xPosition ) )
	{
		// Waypoint is not in the frustum.
		return;
	}

//	bool bIsSpotlight = false;
//	if ( pxProjectorInstance->GetParam().IsSpotLight() )
//	{
//		bIsSpotlight = true;
//	}

	// Do a rough calculation to determine the luminance.
	const Asura_Vector_3& xWPNormal = m_xNormal;

	float fAngleScale = xToLight * xWPNormal;
	if ( fAngleScale <= 0.0f )
	{
		const Asura_Vector_3 xTestPoint = m_xPosition + xWPNormal * 1.5f;
		Asura_Vector_3 xTestToLight = xProjectorPos - xTestPoint;
		float fTestToLight = xTestToLight.Normalise();
		const Asura_LOS_InputData	xLOSInput(xTestPoint, xTestToLight, ASURA_GUID_UNREGISTERED, fTestToLight);
		const Asura_LOS_OutputData*	pxLOSOutput	= Asura_LOS::TestAgainstEverything(&xLOSInput);

		if (pxLOSOutput->m_bHitEnvironment)
		{
			return;
		}
	}

	// ignore the angle scale since an object on the point will probably reflect more light
	fAngleScale = 1.0f;

	float fScale;
	if (xParams.m_fFalloffInnerRatio >= 0.99f)
	{
		fScale = 100.0f;
	}
	else
	{
		fScale = (1.0f / (1.0f - xParams.m_fFalloffInnerRatio));
	}

	float fFallOff = Asura_Maths::Min(1.0f, Asura_Maths::Power(Asura_Maths::Abs((1.0f - fDist) * fScale), xParams.m_fFalloffPower )); 

	if (xParams.IsSpotLight() && xParams.m_fInnerFoV <  xParams.m_fFoV )
	{
		const Asura_Vector_3 xLightForward(pxProjector->GetOrient().e13, pxProjector->GetOrient().e23, pxProjector->GetOrient().e33); 
		const float fInner = Asura_Maths::Cosine(xParams.m_fInnerFoV/2.0f);
		const float fOuter = Asura_Maths::Cosine(xParams.m_fFoV/2.0f);
		const float fDot = (-xToLight * xLightForward);
		const float fDirFalloff = Asura_Maths::ClampToRange((fOuter - fDot) / (fOuter - fInner), 0.0f, 1.0f);
		fFallOff *= fDirFalloff;
	}
	
	
	// Scale for angle (cosine) and range (linear) falloffs.
	fIntensity *= (fAngleScale * fFallOff);

	// Add to the accumulated intensity.
	m_fLuminance += fIntensity;
}

///////////////////
// ProcessPhoton //
///////////////////
void AVP_Container_Functor_GetLuminance::ProcessPhoton(const Asura_ClientContainerInstance_Photon* pxPhotonInstance)
{
	const Asura_Photon* const pxPhoton = pxPhotonInstance->GetPhoton();
	const Asura_Container_Photon_Param& xParams = pxPhotonInstance->GetParam();
	if (!pxPhoton) return;

	// Do a rough calculation to determine the luminance.

	// Intensity is from 0 to 1 (or greater than 1 for HDR).
	float fIntensity = (pxPhoton->GetRedComponent() + pxPhoton->GetGreenComponent() + pxPhoton->GetBlueComponent())/3.0f;
	if (fIntensity <= 0.0f)
	{
		// Totally black or negative light.
		return;
	}

	const Asura_Vector_3& xPhotonPos = pxPhoton->GetPosition();

	Asura_Vector_3 xToLight = ( xPhotonPos - m_xPosition );
	const float    fToLight = xToLight.Normalise();
	const Asura_Vector_3& xWPNormal = m_xNormal;

	float fDist = (fToLight)/(xParams.m_fPhotonRange);
	if (fDist < 0.0f || fDist > 1.0f)
	{
		return;
	}

	float fAngleScale = xToLight * xWPNormal;
	if ( fAngleScale <= 0.0f )
	{
		const Asura_Vector_3 xTestPoint = m_xPosition + xWPNormal * 1.5f;
		Asura_Vector_3 xTestToLight = xPhotonPos - xTestPoint;
		float fTestToLight = xTestToLight.Normalise();
		const Asura_LOS_InputData	xLOSInput(xTestPoint, xTestToLight, ASURA_GUID_UNREGISTERED, fTestToLight);
		const Asura_LOS_OutputData*	pxLOSOutput	= Asura_LOS::TestAgainstEverything(&xLOSInput);

		if (pxLOSOutput->m_bHitEnvironment)
		{
			return;
		}
	}
	// ignore the angle scale since an object on the point will probably reflect more light
	fAngleScale = 1.0f;

	float fScale;
	if (xParams.m_fFalloffInnerRatio >= 0.99f)
	{
		fScale = 100.0f;
	}
	else
	{
		fScale = (1.0f / (1.0f - xParams.m_fFalloffInnerRatio));
	}

	float fFallOff = Asura_Maths::Min(1.0f, Asura_Maths::Power(Asura_Maths::Abs((1.0f - fDist) * fScale), xParams.m_fFalloffPower )); 

	// Scale for angle (cosine) and range (linear) falloffs.
	fIntensity *= (fAngleScale * fFallOff);

	if ( pxPhoton->GetLimitAnglesEnabled() )
	{
		// NOTE: there is a minus sign in GetLightDirection.
		const Asura_Vector_3 xLightDir = pxPhoton->GetLightDirection();
		
		const float fCosineHalfInnerAngle = pxPhoton->GetLimitAngleInner();
		const float fCosineHalfOuterAngle = pxPhoton->GetLimitAngleOuter();
		
		// Theta is angle between light direction and to-light direction.
		const float fCosineTheta = xLightDir * xToLight;		

		if (fCosineTheta < fCosineHalfOuterAngle)
		{
			// Point is outside the outer limiting angle.
			return;
		}

		const float fOuterInner = (fCosineHalfInnerAngle - fCosineHalfOuterAngle);

		if (fOuterInner > 0.0f &&
			fCosineTheta < fCosineHalfInnerAngle)
		{
			// Between outer and inner angles. Scale the intensity assuming linear in cosine of angle.
			fIntensity *= (fCosineTheta - fCosineHalfOuterAngle)/fOuterInner;
		}
	}

	// Add to the accumulated intensity.
	m_fLuminance += fIntensity;
}

///////////////////////////////////////////////////////////////////////////////
// AVP_Container_Functor_SetLightingBB
///////////////////////////////////////////////////////////////////////////////

/////////////
// Process //
/////////////
void AVP_Container_Functor_SetLightingBB::Process(const Asura_ClientContainerInstance* pxEntry)
{
	if (pxEntry->GetFlag(ASURA_CONTAINER_INSTANCE_FLAG_DISABLED))
	{
		return;
	}

	const u_int uClass = pxEntry->GetClassification();

	if (uClass == AsuraContainerClass_Projector)
	{
		ProcessProjector(static_cast<const Asura_ClientContainerInstance_Projector*>(pxEntry));
	}
	else if (uClass == AsuraContainerClass_Photon)
	{
		ProcessPhoton(static_cast<const Asura_ClientContainerInstance_Photon*>(pxEntry));
	}
}

//////////////////////
// ProcessProjector //
//////////////////////
void AVP_Container_Functor_SetLightingBB::ProcessProjector(const Asura_ClientContainerInstance_Projector* pxProjectorInstance)
{
	const Asura_Projector* const pxProjector = pxProjectorInstance->GetProjector();
	if (!pxProjector) return;
	
	// Combine projector's BB with the existing BB.
	m_xBB.Set(pxProjector->GetBoundingBox());
	++m_iNumber;
}

///////////////////
// ProcessPhoton //
///////////////////
void AVP_Container_Functor_SetLightingBB::ProcessPhoton(const Asura_ClientContainerInstance_Photon* pxPhotonInstance)
{
	const Asura_Photon* const pxPhoton = pxPhotonInstance->GetPhoton();
	if (!pxPhoton) return;

	const Asura_Vector_3& xPhotonPos = pxPhoton->GetPosition();
	const float fMaxRange = pxPhoton->GetRange();

	// Make BB for the photon.
	Asura_Bounding_Box xBB;
	xBB.SetToPoint( xPhotonPos );
	xBB.Grow( fMaxRange );

	// Combine photon's BB with the existing BB.
	m_xBB.Set(xBB);
	++m_iNumber;
}

///////////////////////////////////////////////////////////////////////////////
// AVP_Container_Functor_SetSurfaceReorientationFlag
///////////////////////////////////////////////////////////////////////////////
void AVP_Container_Functor_SetSurfaceReorientationFlag::Process(const Asura_ClientContainerInstance* pxEntry)
{
	if (pxEntry->GetClassification() == AsuraContainerClass_Mesh)
	{
		ProcessMesh(static_cast<const Asura_ClientContainerInstance_Mesh*>(pxEntry));
	}
}

void AVP_Container_Functor_SetSurfaceReorientationFlag::ProcessMesh(const Asura_ClientContainerInstance_Mesh* pxMeshInstance)
{
	Asura_Physics_Object* const pxPhysics = const_cast<Asura_Physics_Object*>(pxMeshInstance->GetPhysicsObject());
	if (pxPhysics)
	{
		u_int uFlags = pxPhysics->GetPolyFlagsToReturnForActorCollision();

		if ( m_bIgnoreForSurfaceReorientation )
		{
			uFlags |= AVP_PolygonFlag_IgnoreForReorientation;
		}
		else
		{
			uFlags &= ~AVP_PolygonFlag_IgnoreForReorientation;
		}

		pxPhysics->SetPolyFlagsToReturnForActorCollision( static_cast<u_short>(uFlags) );

		// We may as well take this opportunity to set up the collision FX flags too.
		pxPhysics->SetReportObjectCollisionFX(true);
		pxPhysics->SetReportEnvironmentCollisionFX(true);
	}
}


#endif //#ifdef ASURA_USE_CONTAINER_SYSTEM

