//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_Predator_Seeking.cpp
//
// Copyright (C) 2007 Rebellion. All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

// core
#include "Core/Asura.h"

// header
#include "Behaviour/BehaviourTypes/Predator/AVP_Behaviour_Predator_Seeking.h"

// asura
#ifndef ASURA_NO_DEBUG_OUTPUT
#include "Rendering/Asura_Font_System.h"
#endif

// axon
#include "Behaviour/BehaviourTypes/AVP_Behaviour_Types.h"
#include "Behaviour/BehaviourTypes/Predator/AVP_Behaviour_Predator_AtVentagePoint.h"
#include "Behaviour/BehaviourTypes/Predator/AVP_Behaviour_Predator_Sneaking.h"
#include "Core/BrainTypes/AVP_Brain_Actor_Predator.h"
#include "Core/AVP_Waypoint_Manager.h"
#include "Knowledge/AVP_Knowledge_System_Predator.h"
#include "Knowledge/Axon_Knowledge.h"

// avp
#include "Core/AVP_LOS.h"
#include "Entity/PhysicalObject/Actor/AVP_ServerEntity_Actor.h"
#include "Entity/Locations/VantagePoint/AVP_ServerNode_VantagePoint_Predator.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Predator/AVP_ServerActor_Predator.h"

//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_Predator_Seeking
//
//////////////////////////////////////////////////////////////////////////////

DEBUGVAR float dbg_fTimeBetweenLookAround = 4.0f;
DEBUGVAR float dbg_fTimeToUseKnowledge = 10.0f;
DEBUGVAR float dbg_fTimeToRememberAlert = 4.0f;

AVP_Behaviour_Predator_Seeking::AVP_Behaviour_Predator_Seeking( AVP_Brain_Actor_Predator* pxBrain )
: PARENT( pxBrain, AVP_BEHAVIOUR_TYPE_PREDATOR_SEEKING )
, m_eCurrentState(UNSET)
, m_fTimeInState(0.0f)
, m_fLookAroundTime(0.0f)
, m_xDesiredAimDirection(Asura_Maths::ZeroVector3)
, m_pxDestinationVP(NULL)
, m_pxCurrentVP(NULL)
, m_fTimeTillLookAround(0.0f)
, m_xEstimatedPosition(Asura_Maths::ZeroVector3)
, m_fEstimateThreatAge(100.0f)
, m_bAlertByVision(false)
{
}


bool AVP_Behaviour_Predator_Seeking::Activate()
{
#ifndef ASURA_NO_DEBUG_OUTPUT
	GetPredatorBrain()->SetRenderRoute( true );
#endif

	return PARENT::Activate();
}

void AVP_Behaviour_Predator_Seeking::Deactivate()
{
	Axon_Behaviour* pxChild = GetChild();
	while ( pxChild )
	{
		UnlinkChild( pxChild );
		GetProjectBrain()->DestroyBehaviour( pxChild );
		pxChild = GetChild();
	}

	PARENT::Deactivate();
}

void AVP_Behaviour_Predator_Seeking::PerFrameUpdate( const float fTimeslice )
{
	PARENT::Update( fTimeslice );

	if ( IsFinished() || IsInError() )
	{
		return;
	}

	m_fTimeInState += fTimeslice;
	m_fTimeTillLookAround -= fTimeslice;
	m_fEstimateThreatAge += fTimeslice;

	if (!GetPredatorBrain()->CanPlanMove())
	{
		return;
	}

	switch ( m_eCurrentState )
	{
	case UNSET:
		SwitchToState(SEEK);
		break;
	case SEEK:
		{
			AVP_Behaviour_Predator_Sneaking* pxBehaviour = static_cast< AVP_Behaviour_Predator_Sneaking* >( GetChildWithType( this, AVP_BEHAVIOUR_TYPE_PREDATOR_SNEAKING ) );
			if (!pxBehaviour || pxBehaviour->IsInError())
			{
				// pick another VP
				SwitchToState(SEEK);
			}
			else if (pxBehaviour->IsFinished())
			{
				SwitchToState(LOOK_AROUND);
			}
			else if ( ShouldAlert())
			{
				pxBehaviour->AimAt(m_xEstimatedPosition - GetPredatorBrain()->GetActor()->GetPosition());				
			}
			else
			{
					pxBehaviour->AimWhereYouMove();
			}
		}
		break;
	case LOOK_AROUND:
		// we not suppose to fall from ventage point ...
		if (GetPredatorBrain()->JustFinishedFalling())
		{
			SetInError(true);
			SwitchToState(FINISHED);
		}
		else
		{
			LookAround(false);
			if (m_fTimeInState > m_fLookAroundTime)
			{				
				SwitchToState(SEEK);
			}
		}
		break;
	case FINISHED:
		break;
	case NUM_STATES:
		break;
	default:
		Asura_Assert(false, "unexpected state");
	}
}

#ifndef ASURA_NO_DEBUG_OUTPUT
static const char* s_aszStateStrings[] =
{
	"UNSET",
	"SEEK",
	"LOOK_AROUND",
	"FINISHED",
	"NUM_STATES"
};

void AVP_Behaviour_Predator_Seeking::RenderDebugInfo()
{
	PARENT::RenderDebugInfo();

	Asura_Font_System::Print( GetProjectBrain()->GetDebugOutputPos().x, GetProjectBrain()->GetDebugOutputPos().y, 0.5f, 0xffffffff,
		ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING, "%s", s_aszStateStrings[ m_eCurrentState ] );
	GetProjectBrain()->GetDebugOutputPos().y += 10.0f;
}
#endif

AVP_Brain_Actor_Predator* AVP_Behaviour_Predator_Seeking::GetPredatorBrain() const
{
	return static_cast< AVP_Brain_Actor_Predator* >( GetProjectBrain() );
}

void AVP_Behaviour_Predator_Seeking::SwitchToState( const State eNextState )
{

	switch ( m_eCurrentState )
	{
	case UNSET:
	case LOOK_AROUND:		
		m_fTimeTillLookAround = dbg_fTimeBetweenLookAround;
		break;
	case SEEK:
		DestroyChildWithType( AVP_BEHAVIOUR_TYPE_PREDATOR_SNEAKING );
		break;
	case FINISHED:
	default:
		Asura_Assert(false, "unexpected state");
	}

	m_eCurrentState = eNextState;
	m_fTimeInState  = 0.0f;
	switch ( m_eCurrentState )
	{
	case LOOK_AROUND:
		m_pxCurrentVP = m_pxDestinationVP;
		LookAround(true);
		m_fLookAroundTime =  Asura_Random::GetFloat(0.5f, 1.0f) * GetPredatorBrain()->GetConfigInfo().m_xSeekBehaviour.m_fLookAroundMaxTime;
		break;
	case SEEK:
		{
			SelectVP();
			Asura_Assert(m_pxDestinationVP, "Ventage Point Not found");
			if (m_pxDestinationVP)
			{
				if (m_pxCurrentVP && m_pxCurrentVP->GetGuid() == m_pxDestinationVP->GetGuid())
				{
					SwitchToState(LOOK_AROUND);
				}
				else
				{
					Asura_Vector_3 xDestination = m_pxDestinationVP->GetPosition();
					AVP_Behaviour_Predator_Sneaking* pxNewBehaviour = static_cast< AVP_Behaviour_Predator_Sneaking* >( CreateChildWithType( AVP_BEHAVIOUR_TYPE_PREDATOR_SNEAKING ) );
					if ( !pxNewBehaviour || pxNewBehaviour->IsInError() )
					{
						SwitchToState(LOOK_AROUND);
					}
					else 
					{
						pxNewBehaviour->SetDestination(xDestination, true);
					}
				}
			}
			else
			{
				SetInError(true);
				SwitchToState(FINISHED);
			}
		}
		break;
	case FINISHED:
		SetFinished(true);
		break;
	case UNSET:
	default:
		Asura_Assert(false, "unexpected state");
		break;
	}
}


void AVP_Behaviour_Predator_Seeking::LookAround(bool bNewLookAround)
{
	// change look direction
	Asura_AVector3 xCurrecntAimDirectionA;
	GetPredatorBrain()->GetClientActor()->GetAimDirection(xCurrecntAimDirectionA);
	Asura_Vector_3 xCurrecntAimDirection(xCurrecntAimDirectionA);
	float fDeltaAngleHor = Asura_Maths::DegToRad(70.0f);
	bool bZeroVertical = true;
	float fMinVert = -Asura_Maths::Sine(Asura_Maths::DegToRad(45.0f));
	float fMaxVert = Asura_Maths::Sine(Asura_Maths::DegToRad(15.0f));
		

	if (!m_xDesiredAimDirection.ApproximatelyEquals(xCurrecntAimDirection) && !bNewLookAround)
	{
		return;
	}

	Asura_Vector_3 xDir;

	
	if (UpdateAlert())
	{
		xDir = m_xEstimatedPosition - GetPredatorBrain()->GetActor()->GetPosition();
		xDir.Normalise();

		if (m_bAlertByVision)
		{
			fDeltaAngleHor = Asura_Maths::DegToRad(10.0f);
			bZeroVertical = false;
			if (m_pxCurrentVP)
			{
				fMinVert = -Asura_Maths::Sine(Asura_Maths::DegToRad(10.0f));
				fMaxVert = Asura_Maths::Sine(Asura_Maths::DegToRad(10.0f));
			}
			else // dont change look hight if we not in ventage point since we might be on the ground and that look silly
			{
				fMinVert = fMaxVert = 0.0f;

			}
		}
		else
		{
			fDeltaAngleHor = Asura_Maths::DegToRad(30.0f);
			bZeroVertical = false;
			if (m_pxCurrentVP)
			{
				fMinVert = -Asura_Maths::Sine(Asura_Maths::DegToRad(30.0f));
				fMaxVert = Asura_Maths::Sine(Asura_Maths::DegToRad(15.0f));
			}
			else // dont change look hight if we not in ventage point since we might be on the ground and that look silly
			{
				fMinVert = fMaxVert = 0.0f;

			}
		}
	}
	else if (m_pxCurrentVP != NULL)
	{
		Asura_Matrix_3x3 xVPOrientation;
		m_pxCurrentVP->GetOrientation(xVPOrientation);
		xDir = Asura_Vector_3(xVPOrientation.e13, xVPOrientation.e23, xVPOrientation.e33);

		fDeltaAngleHor = Asura_Maths::DegToRad(70.0f);
		bZeroVertical = true;
		fMinVert = -Asura_Maths::Sine(Asura_Maths::DegToRad(45.0f));
		fMaxVert = Asura_Maths::Sine(Asura_Maths::DegToRad(15.0f));
	}
	else
	{
		Asura_Assert(false, "LookAround - not at ventage point and no alert");
		return;
	}


	const Asura_Vector_3 xUp(0.0f, -1.0f, 0.0f);
	Asura_Quat xRotationQuat;
	
	// remove the Y 
	if (bZeroVertical)
	{
		xDir.y = 0.0f;
		xDir.Normalise();
	}
	// rotate around the Y
	xRotationQuat.FromRotation( Asura_Random::GetFloat(-fDeltaAngleHor, fDeltaAngleHor), xUp ); 
	xDir = xRotationQuat.RotateVector( xDir);
	// Add y component from 45deg down to about 16deg up
	xDir.y += Asura_Random::GetFloat(fMinVert, fMaxVert);
	xDir.Normalise();
	m_xDesiredAimDirection = xDir;
	GetPredatorBrain()->SetAimDirection(m_xDesiredAimDirection);
}

void AVP_Behaviour_Predator_Seeking::SelectVP()
{
	if (GetPredatorBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge().GetGuid() != ASURA_GUID_UNREGISTERED)
	{
		if (GetPredatorBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge().m_fAge < dbg_fTimeToUseKnowledge)
		{
			m_pxDestinationVP = 
				SelectRandomVPAround(GetPredatorBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge().m_xPosition,
				GetPredatorBrain()->GetConfigInfo().m_xSeekBehaviour.m_fSeekRadiusAroundTarget );
		}
		else // bit of cheating 
		{
			const AVP_ServerEntity_Actor* pxActor = AVP_ServerEntity_Actor::FindEntity(GetPredatorBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge().GetGuid());
			Asura_Vector_3 xPosition = pxActor->GetPosition();
			m_pxDestinationVP = 
				SelectRandomVPAround(xPosition,
									GetPredatorBrain()->GetConfigInfo().m_xSeekBehaviour.m_fSeekRadiusAroundTarget );
		}

		if (!m_pxDestinationVP)
		{
			m_pxDestinationVP = 
				SelectRandomVPAround(GetPredatorBrain()->GetActor()->GetPosition(), 
				GetPredatorBrain()->GetConfigInfo().m_xSeekBehaviour.m_fSeekRadiusNoTarget);

		}
	}
	else if (GetPredatorBrain()->GetKnowledgeSystem()->GetBestAlertKnowledge().GetGuid() != ASURA_GUID_UNREGISTERED)
	{
		m_pxDestinationVP = 
			SelectRandomVPAround(GetPredatorBrain()->GetKnowledgeSystem()->GetBestAlertKnowledge().m_xPosition,
			GetPredatorBrain()->GetConfigInfo().m_xSeekBehaviour.m_fSeekRadiusAroundTarget );

		if (!m_pxDestinationVP)
		{
			m_pxDestinationVP = 
				SelectRandomVPAround(GetPredatorBrain()->GetActor()->GetPosition(), 
				GetPredatorBrain()->GetConfigInfo().m_xSeekBehaviour.m_fSeekRadiusNoTarget);

		}

	}
	else
	{
		m_pxDestinationVP = 
			SelectRandomVPAround(GetPredatorBrain()->GetSpawnPoint(), 
			GetPredatorBrain()->GetConfigInfo().m_xSeekBehaviour.m_fSeekRadiusNoTarget);
	}

	if (!m_pxDestinationVP)
	{
		m_pxDestinationVP = m_pxCurrentVP;
	}

}

const AVP_ServerNode_VantagePoint_Predator* AVP_Behaviour_Predator_Seeking::SelectClosestVPTo(const Asura_Vector_3& xCenter)
{
	float fBestDistanceSq = Asura_Maths::LargeFloat;
	const AVP_ServerNode_VantagePoint_Predator* pxBestVP = NULL;

	Asura_Hashed_List_ConstIt< AVP_ServerNode_VantagePoint_Predator > m_xVPIt(AVP_ServerNode_VantagePoint_Predator::GetList());
	for (; !m_xVPIt.Done(); m_xVPIt.Next())
	{
		const AVP_ServerNode_VantagePoint_Predator* pxCurrentVP = m_xVPIt.GetCurrent();

		if (!pxCurrentVP || !pxCurrentVP->IsAvailable())
		{
			continue;
		}


		// skip the current and prev destination ventage points.
		if ((m_pxCurrentVP && m_pxCurrentVP->GetGuid() == pxCurrentVP->GetGuid()) || (m_pxDestinationVP && m_pxDestinationVP->GetGuid() == pxCurrentVP->GetGuid()) )
		{
			continue;
		}

		float fCurrentDistanceSq = (xCenter - pxCurrentVP->GetPosition()).MagnitudeSquared();
		if(fCurrentDistanceSq < fBestDistanceSq )
		{
			fBestDistanceSq = fCurrentDistanceSq;
			pxBestVP = m_xVPIt.GetCurrent();
		}
	}

	return pxBestVP;

}

const AVP_ServerNode_VantagePoint_Predator* AVP_Behaviour_Predator_Seeking::SelectRandomVPAround(const Asura_Vector_3& xCenter, float fMaxRange)
{
	int iNumberOfVAlidVP =0;

	Asura_LinkList< const AVP_ServerNode_VantagePoint_Predator* > m_xValidVPList;

	Asura_Hashed_List_ConstIt< AVP_ServerNode_VantagePoint_Predator > m_xVPIt(AVP_ServerNode_VantagePoint_Predator::GetList());
	for (; !m_xVPIt.Done(); m_xVPIt.Next())
	{
		const AVP_ServerNode_VantagePoint_Predator* pxCurrentVP = m_xVPIt.GetCurrent();
		// skip the current and prev destination ventage points.
		if (!pxCurrentVP || !pxCurrentVP->IsAvailable())
		{
			continue;
		}

		if ((m_pxCurrentVP && m_pxCurrentVP->GetGuid() == pxCurrentVP->GetGuid()) || (m_pxDestinationVP && m_pxDestinationVP->GetGuid() == pxCurrentVP->GetGuid()) )
		{
			continue;
		}

		float fCurrentDistanceSq = (xCenter - pxCurrentVP->GetPosition()).MagnitudeSquared();
		if(fCurrentDistanceSq < fMaxRange * fMaxRange )
		{
			++iNumberOfVAlidVP;
			m_xValidVPList.Push_Back(pxCurrentVP);
		}
	}

	if (iNumberOfVAlidVP == 0)
	{
		m_xValidVPList.Clear();
		return SelectClosestVPTo(xCenter);
	}

	int iSelected = Asura_Random::GetInt(0, iNumberOfVAlidVP - 1);

	Asura_LinkList_ConstIt< const AVP_ServerNode_VantagePoint_Predator* > m_xVVPIt(m_xValidVPList);
	for (; iSelected > 0 && !m_xVVPIt.Done(); --iSelected, m_xVVPIt.Next())
	{
	}

	Asura_Assert(!m_xVVPIt.Done(), "Selected ventage point oor");
	if (!m_xVVPIt.Done())
	{
		const AVP_ServerNode_VantagePoint_Predator* pxSelected = *m_xVVPIt.GetCurrent();
		m_xValidVPList.Clear();
		return pxSelected;
	}
	else
	{
		m_xValidVPList.Clear();
		return SelectClosestVPTo(xCenter);
	}
}


bool AVP_Behaviour_Predator_Seeking::ShouldAlert()
{

	if(UpdateAlert())
	{
		return true;
	}
	else
	{
		return false;
	}
}

bool AVP_Behaviour_Predator_Seeking::UpdateAlert()
{
	if (GetPredatorBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge().GetGuid() != ASURA_GUID_UNREGISTERED)
	{
		if (GetPredatorBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge().m_fAge < dbg_fTimeToRememberAlert)
		{
			m_xEstimatedPosition = GetPredatorBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge().m_xPosition;
			m_fEstimateThreatAge = 0.0f;
			m_bAlertByVision = true;
			return true;
		}		
	}
	else if (GetPredatorBrain()->GetKnowledgeSystem()->GetBestAlertKnowledge().GetGuid() != ASURA_GUID_UNREGISTERED)
	{
		if (GetPredatorBrain()->GetKnowledgeSystem()->GetBestAlertKnowledge().m_fAge < dbg_fTimeToRememberAlert)
		{
			m_xEstimatedPosition = GetPredatorBrain()->GetKnowledgeSystem()->GetBestAlertKnowledge().m_xPosition;
			m_fEstimateThreatAge = 0.0f;
			m_bAlertByVision = false;
			return true;
		}		
	}

	if (m_fEstimateThreatAge < dbg_fTimeToRememberAlert)
	{
		return true;
	}

	return false;
}


//////////////////////////////////////////////////////////////////////////////
//
// 
//
//////////////////////////////////////////////////////////////////////////////
