//////////////////////////////////////////////////////////////////////////////
//
// Asura_ClientEntity_LookAtTrigger.cpp
//
// Created by Anthony Gregson - 26/04/04
//
// Copyright (c) 2004 Rebellion. All rights reserved.
//
// Description:
//
//     Client-side version of the look-at triggers. Needed as long as camera
//     data is not available on the server, and the functionality should be
//     merged into the server entity as soon as it is.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Entity/Triggers/Asura_ClientEntity_LookAtTrigger.h"

#include "Camera/Asura_Camera.h"
#include "Core/Asura_LOS.h"
#include "Core/Asura_Timers.h"
#include "Entity/Asura_Entity_Classification.h"
#include "Entity/Triggers/Asura_ServerEntity_LookAtTrigger.h"
#include "Network/Asura_Message.h"
#include "Network/Asura_Player.h"

#ifdef ASURA_TOOLS
#include "EntityTemplate/Tools_EntityTemplate_System.h"
#endif


DEBUGVAR float dbg_fCreateRequestRegularity = 1.0f;

Asura_ClientEntity_LookAtTrigger::Asura_ClientEntity_LookAtTrigger(Asura_Guid uGuid)
: Asura_Entity_Client(uGuid)
, m_fMaxDistance(0.0f)
, m_fMinDistance(0.0f)
, m_fMaxAngleOrEdgeDeadzone(0.0f)
, m_fMinAngleOrCentreDeadzone(0.0f)
, m_fInwardConeCosMaxAngle(1.0f)
, m_uTriggeringTypes(0)
, m_iSpecifiedPlayer(0)
, m_xPosition(Asura_Maths::ZeroVector3)
, m_xDirection(0.0f, 0.0f, 1.0f)
, m_uLookAtFlags(0)
, m_iNumberCurrentlyTriggering(0)
, m_fUpdateTimer(dbg_fCreateRequestRegularity)
{
	Classification = AsuraEntityClass_LookAtTrigger;

	AddToUpdateableClientList(this);	
}

Asura_ClientEntity_LookAtTrigger::~Asura_ClientEntity_LookAtTrigger()
{
	// (MH - 31.10.2007) now done in base class
	//RemoveFromUpdateableClientList(this);
}

void Asura_ClientEntity_LookAtTrigger::Update()
{
	if (!m_bReceivedCreateMessage)
	{
		m_fUpdateTimer -= Asura_Timers::GetGameTimeSlice();
		if (m_fUpdateTimer <= 0.0f)
		{
			m_fUpdateTimer = dbg_fCreateRequestRegularity;

			Asura_Message* pxRequest = Asura_Network::CreateMessage(ASURA_MSG_CLIENT_REQUESTCREATEMSG);
			Send(pxRequest, Guid);
		}
	}
	else if (IsEntityActive() && (((m_uTriggeringTypes & ASURA_LOOKATTRIGGER_TYPE_ALLPLAYERS) != 0) || (m_uTriggeringTypes == ASURA_LOOKATTRIGGER_TYPE_SPECIFICPLAYER))) // Should we also ignore when cutscene playing?
	{
		m_fUpdateTimer -= Asura_Timers::GetGameTimeSlice();
		if (m_fUpdateTimer <= 0.0f)
		{
			m_fUpdateTimer += fLOOKAT_TRIGGER_UPDATE_TIME; // += to avoid tending to bunch up over time

			int iPreviousCount = m_iNumberCurrentlyTriggering;
			m_iNumberCurrentlyTriggering = 0;

			// OK, this bit is nasty. We're going through the player entities, so we can do the update only for a specified player if that's how things are working
			// However, we're also assuming that the order of the player list is the order of their cameras in Asura_Camera, which I'm not sure is always true.
			// Since at the moment there is nothing that connects player entities to cameras (except entity to ignore, which shouldn't be valid unless this is a FPS),
			// this seems to be an unavoidable assumption in the default Asura calculation.

			// This iterator is only through the local players, so is quite short
			Asura_Hashed_List_It<Asura_Entity_Player> xIt(Asura_Entity_Player::GetList());
			int iCameraCount = 0;

			u_int uCurrentCamera = Asura_Camera::GetCameraIndex();

			while (!xIt.Done())
			{
				const Asura_Entity* pxEntity = xIt.GetCurrent();
				xIt.Next();

				if ((iCameraCount < Asura_Camera::GetNumberOfCameras()) && pxEntity && (((m_uTriggeringTypes & ASURA_LOOKATTRIGGER_TYPE_ALLPLAYERS) != 0) || (pxEntity->GetGuid() == static_cast<u_int>(1000 + m_iSpecifiedPlayer))))
				{
					Asura_Camera::SetCameraIndex(iCameraCount);

					Asura_Vector_3 xCameraPosition = *Asura_Camera::GetPosition();
					Asura_Vector_3 xCameraDirection;
					Asura_Camera::GetForwardVector(xCameraDirection);

					Asura_Vector_3 xCameraToTriggerDirection = m_xPosition - xCameraPosition;
					float fCameraToTriggerDistance = xCameraToTriggerDirection.Normalise();

					// Range test
					float fAdjustedMaxDistance = m_fMaxDistance;
					float fAdjustedMinDistance = m_fMinDistance;
					if (m_bAdjustForFOV)
					{
						float fFOVAdjustment = Asura_Camera::GetHorizontalFieldOfView() * fLOOKAT_TRIGGER_ONE_OVER_STANDARD_FOV;
						fAdjustedMaxDistance /= fFOVAdjustment;
						fAdjustedMinDistance /= fFOVAdjustment;
					}

					if ((fCameraToTriggerDistance > fAdjustedMaxDistance) || (fCameraToTriggerDistance < fAdjustedMinDistance))
					{
						if (m_bTriggerOnNotLooking) m_iNumberCurrentlyTriggering++; // Failed a test, but this is a pass if we are a 'look away' trigger.
						continue;
					}

					// Trigger entity within screen area or within cone originating from player camera.
					if (m_bUseScreenArea)
					{
						Asura_Vector_3 xScreenSpace = m_xPosition;
						Asura_Camera::ProjectWorldCoordsToScreenSpace(xScreenSpace);
						if (xScreenSpace.z <= 0.0f) // In front of camera
						{
							if (m_bTriggerOnNotLooking) m_iNumberCurrentlyTriggering++;  // Failed a test, but this is a pass if we are a 'look away' trigger.
							continue;
						}

						float fActualFromCentreLivezone = (1.0f - m_fMaxAngleOrEdgeDeadzone) * 0.5f;
						float fActualCentreDeadzone = m_fMinAngleOrCentreDeadzone * 0.5f;
						float fXFromCentre = Asura_Maths::Abs(xScreenSpace.x - 0.5f);
						float fYFromCentre = Asura_Maths::Abs(xScreenSpace.y - 0.5f);
						if ((fXFromCentre > fActualFromCentreLivezone) || (fYFromCentre > fActualFromCentreLivezone)
												|| ((fXFromCentre < fActualCentreDeadzone) && (fYFromCentre < fActualCentreDeadzone)))
						{
							if (m_bTriggerOnNotLooking) m_iNumberCurrentlyTriggering++;  // Failed a test, but this is a pass if we are a 'look away' trigger.
							continue;
						}
					}
					else
					{
						float fCosAngle = xCameraDirection * xCameraToTriggerDirection;
						if ((fCosAngle < m_fMaxAngleOrEdgeDeadzone) || (fCosAngle > m_fMinAngleOrCentreDeadzone))
						{
							if (m_bTriggerOnNotLooking) m_iNumberCurrentlyTriggering++;  // Failed a test, but this is a pass if we are a 'look away' trigger.
							continue;
						}
					}

					// Inward cone test (player camera within cone originating from the trigger)
					if (m_fInwardConeCosMaxAngle < 1.0f)
					{
						float fCosOtherAngle = m_xDirection * -xCameraToTriggerDirection;
						if (fCosOtherAngle < m_fInwardConeCosMaxAngle)
						{
							if (m_bTriggerOnNotLooking) m_iNumberCurrentlyTriggering++;  // Failed a test, but this is a pass if we are a 'look away' trigger.
							continue;
						}
					}

					// LOS test
					bool bLooking = !m_bUseLOS;
					if (m_bUseLOS)
					{
						Asura_LOS_InputData xInput(xCameraPosition, xCameraToTriggerDirection, ASURA_GUID_UNREGISTERED, fCameraToTriggerDistance);
						const Asura_LOS_OutputData* pxOutput = Asura_LOS::TestAgainstEnvironment(&xInput);
						if (!pxOutput->m_bHitEnvironment)
						{
							bLooking = true;
						}
					}

					// Do we need to look towards or away?
					const bool bTriggerOnNotLooking = m_bTriggerOnNotLooking; // warning fix
					if(bLooking != bTriggerOnNotLooking) m_iNumberCurrentlyTriggering++;
				}

				iCameraCount++;
			}

			Asura_Camera::SetCameraIndex(uCurrentCamera);

			if (iPreviousCount != m_iNumberCurrentlyTriggering)
			{
				// Update the server
				Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_LOOKATTRIGGER_CLIENTCOUNTCHANGE, sizeof(Asura_Msg_LookAtTrigger_ClientCountChange));

				if (pxMessage)
				{
					Asura_Msg_LookAtTrigger_ClientCountChange* pxData = static_cast<Asura_Msg_LookAtTrigger_ClientCountChange*>(pxMessage->GetData());
					if (pxData)
					{
						pxData->m_iCurrent = m_iNumberCurrentlyTriggering;
						pxData->m_iPrevious = iPreviousCount;
					}

					Send(pxMessage, Guid);
				}
			}
		}
	}

	PARENT::Update();
}

bool Asura_ClientEntity_LookAtTrigger::HandleMessage(const Asura_Message* pxMessage)
{
	switch (pxMessage->GetMsgID())
	{
	default:
		break;
	case ASURA_MSG_CLIENTCREATE:
#ifdef ASURA_ALLOW_INACTIVE_CLIENT_ENTITIES
	case ASURA_MSG_CLIENTCREATEINACTIVE:
#endif //#ifdef ASURA_ALLOW_INACTIVE_CLIENT_ENTITIES
		{
			Asura_Assert(pxMessage->GetEntityClassification() == Classification, "Create message sent to wrong entity type.");

			const Asura_Msg_LookAtTrigger_ClientCreate* pxData = static_cast<const Asura_Msg_LookAtTrigger_ClientCreate*>(pxMessage->GetData());
			if (pxData)
			{
				m_fMaxDistance = pxData->m_fMaxDistance;
				m_fMinDistance = pxData->m_fMinDistance;
				m_bUseScreenArea = pxData->m_bUseScreenArea;
				m_fMaxAngleOrEdgeDeadzone = pxData->m_fMaxAngleOrEdgeDeadzone;
				m_fMinAngleOrCentreDeadzone = pxData->m_fMinAngleOrCentreDeadzone;
				m_bUseLOS = pxData->m_bUseLOS;
				m_bTriggerOnNotLooking = pxData->m_bTriggerOnNotLooking;
				m_fInwardConeCosMaxAngle = pxData->m_fInwardConeCosMaxAngle;
				m_bAdjustForFOV = pxData->m_bAdjustForFOV;
				m_uTriggeringTypes = pxData->m_uTriggeringTypes;
				m_iSpecifiedPlayer = pxData->m_iSpecifiedPlayer;
				m_xPosition = pxData->m_xPosition;
				m_xDirection = pxData->m_xDirection;
			}
			m_fUpdateTimer = Asura_Random::GetSeededFloat(0.0f, fLOOKAT_TRIGGER_UPDATE_TIME);
			m_bReceivedCreateMessage = true;
		}
		return true;
	}

	return PARENT::HandleMessage(pxMessage);
}

Asura_ClientEntity_LookAtTrigger* Asura_ClientEntity_LookAtTrigger::Create(const Asura_Message* pxMessage)
{
	if (!pxMessage) { return NULL; }

#ifdef ASURA_ALLOW_INACTIVE_CLIENT_ENTITIES
	Asura_Assertf(pxMessage->GetMsgID() == ASURA_MSG_CLIENTCREATE || pxMessage->GetMsgID() == ASURA_MSG_CLIENTCREATEINACTIVE, ("Invalid message (%s) to create client look-at trigger with.", Asura_Network::GetMsgIDDesc(pxMessage->GetMsgID())));
	if (pxMessage->GetMsgID() != ASURA_MSG_CLIENTCREATE && pxMessage->GetMsgID() != ASURA_MSG_CLIENTCREATEINACTIVE) { return NULL; }
#else //#ifdef ASURA_ALLOW_INACTIVE_CLIENT_ENTITIES
	Asura_Assertf(pxMessage->GetMsgID() == ASURA_MSG_CLIENTCREATE, ("Invalid message (%s) to create client look-at trigger with.", Asura_Network::GetMsgIDDesc(pxMessage->GetMsgID())));
	if (pxMessage->GetMsgID() != ASURA_MSG_CLIENTCREATE) { return NULL; }
#endif //#else //#ifdef ASURA_ALLOW_INACTIVE_CLIENT_ENTITIES

	Asura_ClientEntity_LookAtTrigger* pxEntity = new Asura_ClientEntity_LookAtTrigger(pxMessage->GetFromGuid());
	if (pxEntity)
	{
		pxEntity->HandleMessage(pxMessage);
	}
	return pxEntity;
}


#ifdef ASURA_TOOLS
void Asura_ClientEntity_LookAtTrigger::ApplyChangesForTemplateInstance(Tools_EntityTemplateInstance* pxInstance)
{
	//The client entity gets created from a message before ApplyChangesForTemplateInstance is called on the server entity.
	//Therefore we need to apply the changes to the client as well. RAR 23/02/2006

	m_xPosition = pxInstance->TransformPosition(m_xPosition);
	m_xDirection = m_xDirection * pxInstance->GetRelativeMatrix();

}
#endif // ASURA_TOOLS
