//////////////////////////////////////////////////////////////////////////////
//
// AVP_Actor_ObjectiveHandler.cpp
//
// (AJK - 16.01.2009) Created
//
// Copyright (c) 2009 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Actor/AVP_Actor_ObjectiveHandler.h"

// Asura
#include "Core/Asura_Timers.h"
#include "GUIMenu/Asura_GUIMenu_Element_Image.h"
#include "GUIMenu/AVP_GUIMenu_System.h"

// AVP
#include "Actor/AVP_Actor_HUDHandler.h"
#include "Entity/ObjectiveSystem/PlayerObjective/AVP_ServerEntity_LocationMarker.h"
#include "Entity/ObjectiveSystem/PlayerObjective/AVP_ServerEntity_PlayerObjective.h"
#include "Entity/ObjectiveSystem/RoomNode/AVP_RoomNode_AStar.h"
#include "Entity/ObjectiveSystem/RoomNode/AVP_RoomNode_ExitLink.h"
#include "Entity/ObjectiveSystem/RoomNode/AVP_ServerEntity_RoomNode.h"
#include "Entity/PhysicalObject/Actor/AVP_ServerEntity_Actor.h"

// Debug vars
DEBUGVAR float dbg_fCalcInterval              = 0.5f;
DEBUGVAR float dbg_fNearExitPointDistanceSqrd = 4.0f;
DEBUGVAR float dbg_fAngleStepSpeed            = 0.75f; // From clamped angle 0 to 1 in 1.5 seconds

AVP_Actor_ObjectiveHandler::AVP_Actor_ObjectiveHandler(  AVP_ServerEntity_Actor& xOwner )
: m_xOwner                ( xOwner                  )
, m_uObjectiveToTrack     ( ASURA_GUID_UNREGISTERED )
, m_uRoomNodeToTrack      ( ASURA_GUID_UNREGISTERED )
, m_fRouteCalcInterval    ( 0.0f                    )
, m_uCurrentRouteRoomNode ( ASURA_GUID_UNREGISTERED )
, m_uNextRouteRoomNode    ( ASURA_GUID_UNREGISTERED )
, m_bObjectiveChanged     ( false                   )
, m_bObjectiveMoving      ( false                   )
, m_PlayerCurrentRoomNode ( ASURA_GUID_UNREGISTERED )
, m_fClampedAngle         ( 0.5f                    )
{
	AVP_Actor_HUDHandler::SetShowObjectiveArrow( false );
}

void AVP_Actor_ObjectiveHandler::Update()
{
	if ( m_uObjectiveToTrack != ASURA_GUID_UNREGISTERED)
	{
		m_PlayerCurrentRoomNode = FindRoomNodeContainingPosition(m_xOwner.GetPosition());

		m_fRouteCalcInterval    += Asura_Timers::GetGameTimeSlice();

		// Instant route calc if there is a new objective, or objective changed location, or objective changed target, or nodes changed (Activated or deactivated )
		if ( m_bObjectiveChanged )
		{
			DoRouteFindingTasks();
			FindCurAndNextRouteRoomNodes();
			UpdateObjectiveArrow();
			return;
		}

		// Periodical route calc if player outside route, or no route to objective, or objective moving
		if ( m_fRouteCalcInterval >= dbg_fCalcInterval )
		{
			m_fRouteCalcInterval = 0.0f;
			if ( m_uCurrentRouteRoomNode == ASURA_GUID_UNREGISTERED || m_uNextRouteRoomNode == ASURA_GUID_UNREGISTERED || m_bObjectiveMoving )
			{
				DoRouteFindingTasks();
				FindCurAndNextRouteRoomNodes();
				UpdateObjectiveArrow();
				return;
			}
		}

		FindCurAndNextRouteRoomNodes();
		UpdateObjectiveArrow();
	}
}

void AVP_Actor_ObjectiveHandler::DoRouteFindingTasks()
{
	m_bObjectiveMoving = false;
	FindRoomNodeToTrack();
	CalcRoute();
	m_fRouteCalcInterval = 0.0f;
	m_bObjectiveChanged  = false;
}

AVP_Actor_ObjectiveHandler::~AVP_Actor_ObjectiveHandler()
{
	AVP_Actor_HUDHandler::SetShowObjectiveArrow( false );
}

bool AVP_Actor_ObjectiveHandler::GetDirToNextNode( Asura_Vector_3& xDirection)
{
	if ( m_uRoomNodeToTrack != ASURA_GUID_UNREGISTERED )
	{
		if ( m_uCurrentRouteRoomNode != ASURA_GUID_UNREGISTERED && m_uNextRouteRoomNode != ASURA_GUID_UNREGISTERED)
		{
			const AVP_ServerEntity_RoomNode* pxCurrentRoomNode = AVP_ServerEntity_RoomNode::FindEntity( m_uCurrentRouteRoomNode );
			const AVP_ServerEntity_RoomNode* pxNextRoomNode    = AVP_ServerEntity_RoomNode::FindEntity( m_uNextRouteRoomNode    );

			if ( pxCurrentRoomNode &&  pxNextRoomNode)
			{
				if ( pxCurrentRoomNode != pxNextRoomNode)
				{
					// All nodes use an exit point; so use it
					const AVP_RoomNode_ExitLink* pxExitLink;
					if ( ( pxExitLink = pxCurrentRoomNode->GetNearestExitLinkTo( m_uNextRouteRoomNode, m_xOwner ) ) != NULL )
					{
						xDirection = pxExitLink->GetPosition() - m_xOwner.GetPosition();
					}
					else
					{
						Asura_Assert(false, "Where is the exit point?");
						return false;
					}
					// Test if we are near the exit point
					if ( dbg_fNearExitPointDistanceSqrd > xDirection.MagnitudeSquared() )
					{
						// Near, so get direction to next node
						xDirection = pxNextRoomNode->GetPosition() - m_xOwner.GetPosition();
					}
					return true;
				}
			}
		}
		else if( m_uRoomNodeToTrack == m_PlayerCurrentRoomNode ) // Actor in objective room
		{
			// Get direction directly to the objective
			const AVP_ServerEntity_PlayerObjective* pxObjective = AVP_ServerEntity_PlayerObjective::FindEntity(m_uObjectiveToTrack);
			if (pxObjective)
			{
				// Based on target type, find room node to track
				if ( pxObjective->GetTargetType() == AVP_ServerEntity_PlayerObjective::TARGET_ENTITY )
				{
					const AVP_ServerEntity_Actor* pxEntity = AVP_ServerEntity_Actor::FindEntity(pxObjective->GetTargetGuid());
					if (pxEntity)
					{
						xDirection = pxEntity->GetPosition() - m_xOwner.GetPosition();
					}
					else
					{
						Asura_Assert(false, "Where is the target to track? dead?.");
						return false;
					}
				}
				else if ( pxObjective->GetTargetType() == AVP_ServerEntity_PlayerObjective::TARGET_LOCATION_MARKER )
				{
					const AVP_ServerEntity_LocationMarker* pxMarker = AVP_ServerEntity_LocationMarker::FindEntity(pxObjective->GetTargetGuid());
					if (pxMarker)
					{
						xDirection = pxMarker->GetPosition() - m_xOwner.GetPosition();
					}
					else
					{
						Asura_Assert(false, "Where is the marker point?.");
						return false;
					}
				}
				else if ( pxObjective->GetTargetType() == AVP_ServerEntity_PlayerObjective::TARGET_OWN_LOCATION ) 
				{
					xDirection = pxObjective->GetPosition() - m_xOwner.GetPosition();
				}
				else
				{
					Asura_Assert(false, "Wrong objective type!.");
					return false;
				}
				return true;
			}
		}
	}
	return false;
}

void AVP_Actor_ObjectiveHandler::CalcRoute()
{
	if ( m_uRoomNodeToTrack != ASURA_GUID_UNREGISTERED && m_PlayerCurrentRoomNode != ASURA_GUID_UNREGISTERED)
	{
		AVP_RoomNode_AStar::GetRouteTo( m_uRoomNodeToTrack, m_PlayerCurrentRoomNode, m_xOwner, m_xRoute );
	}
}

void AVP_Actor_ObjectiveHandler::SetObjectiveToTrack( Asura_Guid uGuid)
{
	AVP_Actor_HUDHandler::SetShowObjectiveArrow( false );

	if ( ( m_uObjectiveToTrack = uGuid ) != ASURA_GUID_UNREGISTERED )
	{
		m_uObjectiveToTrack = uGuid;
		m_bObjectiveChanged = true;
	}
}

void AVP_Actor_ObjectiveHandler::FindRoomNodeToTrack()
{
	m_uRoomNodeToTrack = ASURA_GUID_UNREGISTERED;
	const AVP_ServerEntity_PlayerObjective* pxObjective = AVP_ServerEntity_PlayerObjective::FindEntity(m_uObjectiveToTrack);
	if (pxObjective)
	{
		// Based on target type, find room node to track
		if ( pxObjective->GetTargetType() == AVP_ServerEntity_PlayerObjective::TARGET_ENTITY )
		{
			m_bObjectiveMoving = true;
			const AVP_ServerEntity_Actor* pxEntity = AVP_ServerEntity_Actor::FindEntity(pxObjective->GetTargetGuid());
			if (pxEntity)
			{
				m_uRoomNodeToTrack   = FindRoomNodeContainingPosition(pxEntity->GetPosition());
			}
			else
			{
				Asura_Assertf(false, ("AVP_Actor_ObjectiveHandler::FindRoomNodeToTrack - could not find actor with guid %u", pxObjective->GetTargetGuid()));
			}
		}
		else if ( pxObjective->GetTargetType() == AVP_ServerEntity_PlayerObjective::TARGET_LOCATION_MARKER )
		{
			const AVP_ServerEntity_LocationMarker* pxMarker = AVP_ServerEntity_LocationMarker::FindEntity(pxObjective->GetTargetGuid());
			if (pxMarker)
			{
				m_uRoomNodeToTrack   = FindRoomNodeContainingPosition(pxMarker->GetPosition());
			}
		}
		else if ( pxObjective->GetTargetType() == AVP_ServerEntity_PlayerObjective::TARGET_OWN_LOCATION )
		{
			m_uRoomNodeToTrack   = FindRoomNodeContainingPosition(pxObjective->GetPosition());
		}
	}
}

Asura_Guid AVP_Actor_ObjectiveHandler::FindRoomNodeContainingPosition(const Asura_Vector_3& xPosition)
{
	Asura_Hashed_List_It<AVP_ServerEntity_RoomNode> xRoomIter  (AVP_ServerEntity_RoomNode::GetList());
	// Loop through all rooms
	for ( ; !xRoomIter.Done(); xRoomIter.Next() )
	{
		if ( xRoomIter.GetCurrent()->GetVolumeGroup().Contains(xPosition) )
		{
			return xRoomIter.GetCurrent()->GetGuid();
		}
	}

	return ASURA_GUID_UNREGISTERED;
}

void AVP_Actor_ObjectiveHandler::FindCurrentRouteRoomNode()
{
	m_uCurrentRouteRoomNode = ASURA_GUID_UNREGISTERED;
	Asura_LinkList_It< Asura_Guid > xLinkListIter( m_xRoute );

	while ( !xLinkListIter.Done() )
	{
		const AVP_ServerEntity_RoomNode* xRoomNode = AVP_ServerEntity_RoomNode::FindEntity(*xLinkListIter.GetCurrent());
		if ( xRoomNode )
		{
			if ( xRoomNode->GetVolumeGroup().Contains(m_xOwner.GetPosition()) )
			{
				m_uCurrentRouteRoomNode = xRoomNode->GetGuid();
				return;
			}
		}
		else
		{
			return; 
		}
		xLinkListIter.Next();
	}
}

void AVP_Actor_ObjectiveHandler::FindNextRouteRoomNode()
{
	m_uNextRouteRoomNode = ASURA_GUID_UNREGISTERED;
	Asura_LinkList_It< Asura_Guid > xLinkListIter( m_xRoute );

	while ( !xLinkListIter.Done() )
	{
		if ( *xLinkListIter.GetCurrent() == m_uCurrentRouteRoomNode )
		{
			xLinkListIter.Next();
			if ( !xLinkListIter.Done() )
			{
				m_uNextRouteRoomNode = *xLinkListIter.GetCurrent();
			}
			return;
		}
		xLinkListIter.Next();
	}
}

void AVP_Actor_ObjectiveHandler::FindCurAndNextRouteRoomNodes()
{
	m_uCurrentRouteRoomNode = ASURA_GUID_UNREGISTERED;
	m_uNextRouteRoomNode    = ASURA_GUID_UNREGISTERED;
	Asura_LinkList_It< Asura_Guid > xLinkListIter( m_xRoute );

	while ( !xLinkListIter.Done() )
	{
		const AVP_ServerEntity_RoomNode* xRoomNode = AVP_ServerEntity_RoomNode::FindEntity(*xLinkListIter.GetCurrent());
		if ( xRoomNode )
		{
			if ( xRoomNode->GetVolumeGroup().Contains(m_xOwner.GetPosition()) )
			{
				m_uCurrentRouteRoomNode = xRoomNode->GetGuid();
				xLinkListIter.Next();
				if ( !xLinkListIter.Done() )
				{
					m_uNextRouteRoomNode = *xLinkListIter.GetCurrent();
				}
				return;
			}
		}
		else
		{
			return; 
		}
		xLinkListIter.Next();
	}
}

void AVP_Actor_ObjectiveHandler::UpdateObjectiveArrow()
{
	Asura_Vector_3 xDirection;

	if ( GetDirToNextNode(xDirection) )
	{
		AVP_Actor_HUDHandler::SetShowObjectiveArrow( true );

		// We discard Y value, because we are interested in the X-Z plane
		xDirection.y = 0.0f;
		xDirection.Normalise();

		Asura_Vector_3   xLookAtDirection;

		// Use either forward vector or Aim direction
		Asura_Camera::GetForwardVector(xLookAtDirection);

		// We discard Y value, because we are interested in the X-Z plane
		xLookAtDirection.y = 0.0f;
		xLookAtDirection.Normalise();

		// Get Angle between Aim direction and Objective direction
		float fAngle = Asura_Maths::ArcTangent(xDirection.x, xDirection.z) - Asura_Maths::ArcTangent(xLookAtDirection.x, xLookAtDirection.z);
		fAngle	     = Asura_Maths::RadiansToZeroCentredRange(fAngle);

		const AVP_Settings::Species eSpecies = AVP_Settings::GetPlayerSpecies();

		switch( eSpecies )
		{
			default:	// Default use 90 degrees
			case AVP_Settings::AVP_SPECIES_UNKNOWN:
			case AVP_Settings::AVP_SPECIES_ALIEN:	// No concept for alien objective arrow yet so default to 90 degree system
			case AVP_Settings::AVP_SPECIES_PREDATOR:
			{
				float fPiOver4	= Asura_Maths::Pi * 0.25f;

				if( fAngle >= fPiOver4 )
				{
					fAngle = 1.0f;
				}
				else if( fAngle <= -fPiOver4 )
				{
					fAngle = 0.0f;
				}
				else
				{
					fAngle = ((fAngle + fPiOver4) / (Asura_Maths::Pi * 0.5f));
				}
				break;
			}
			case AVP_Settings::AVP_SPECIES_HUMAN:
			{
				float fPiOver2	= Asura_Maths::Pi * 0.5f;

				if( fAngle >= fPiOver2 )
				{
					fAngle = 1.0f;
				}
				else if( fAngle <= -fPiOver2 )
				{
					fAngle = 0.0f;
				}
				else
				{
					fAngle = ((fAngle + fPiOver2) / (Asura_Maths::Pi));
				}
				break;
			}
		}

		UpdateObjectiveClampedAngle( fAngle );

		AVP_Actor_HUDHandler::SetClampedAngle( m_fClampedAngle );
	}
	else
	{
		AVP_Actor_HUDHandler::SetShowObjectiveArrow( false );
	}
}

void AVP_Actor_ObjectiveHandler::UpdateObjectiveClampedAngle(float fNewAngle)
{
	if ( m_fClampedAngle < fNewAngle )
	{
		float m_fAdvancedAngle = m_fClampedAngle + ( Asura_Timers::GetGameTimeSlice() * dbg_fAngleStepSpeed );
		if ( m_fAdvancedAngle < fNewAngle )
		{
			m_fClampedAngle = m_fAdvancedAngle;
		}
		else
		{
			m_fClampedAngle = fNewAngle;
		}
	}
	else if ( m_fClampedAngle > fNewAngle )
	{
		float m_fAdvancedAngle = m_fClampedAngle - ( Asura_Timers::GetGameTimeSlice() * dbg_fAngleStepSpeed );
		if ( m_fAdvancedAngle > fNewAngle )
		{
			m_fClampedAngle	= m_fAdvancedAngle;
		}
		else
		{
			m_fClampedAngle = fNewAngle;
		}
	}
}