//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_Predator_Flee.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_Flee.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_Sneaking.h"
#include "Core/AVP_Waypoint_Manager.h"
#include "Knowledge/AVP_Knowledge_System_Predator.h"
#include "Knowledge/Axon_Knowledge.h"

// avp
#include "Core/BrainTypes/AVP_Brain_Actor_Predator.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Predator/AVP_ServerActor_Predator.h"

//////////////////////////////////////////////////////////////////////////////
//
// AVP_FaceHuggerAvoidenceCostCalc
// Class that calculate cost for A* algorithm trying to avoid moving near a position
//
//////////////////////////////////////////////////////////////////////////////

class AVP_PredatorFindHidingPlace : public AVP_Navigation::BreadthFirstSearch
{
public:
	AVP_PredatorFindHidingPlace(int iStartWPID, float fSearchRadius, const AVP_Knowledge_System_Predator* pxKnowledgeSystem, const Asura_Vector_3& xMyStartPosition, float fMinDistanceToThreats, float fMinMoveDistance)
		: BreadthFirstSearch(static_cast<u_short>(iStartWPID), fSearchRadius)
		, m_pxKnowledgeSystem(pxKnowledgeSystem)
		, m_xMyStartPosition(xMyStartPosition)
		, m_fMinDistanceToThreatSquared(fMinDistanceToThreats * fMinDistanceToThreats)
		, m_fMinDistanceToMoveSquared(fMinMoveDistance * fMinMoveDistance)
		, m_iHideWP(-1)
		, m_iBestWPHiddenCounter(-1)
		, m_fBestWPWorstEstimate(-Asura_Maths::LargeFloat)
	{}

	int GetHidingPlace() const { return m_iHideWP; }

	virtual bool VisitNode( const int iWaypointIndex );
	virtual bool LinkFlagsTest( const Asura_AI_NavConnection* pxLink, const u_int uPathfinderFlags );

private:
	bool EstimateWP( const Asura_AI_NavVertex* pxWP, int iWPIndex, const Asura_Vector_3& xThreatPosition, float& fWPEstimate);

	const AVP_Knowledge_System_Predator* m_pxKnowledgeSystem;
	const Asura_Vector_3 m_xMyStartPosition;
	const float m_fMinDistanceToThreatSquared;
	const float m_fMinDistanceToMoveSquared;
	int m_iHideWP;
	int m_iBestWPHiddenCounter;
	float m_fBestWPWorstEstimate;
};

bool AVP_PredatorFindHidingPlace::VisitNode( const int iWaypointIndex )
{
	const Asura_AI_NavVertex* pxWP = Asura_AI_Navigation_System::GetWaypoint( iWaypointIndex );

	// want to move some distance
	if ( (m_xMyStartPosition - pxWP->GetPosition()).MagnitudeSquared() < m_fMinDistanceToMoveSquared)
	{
		return false;
	}

	const Axon_Knowledge xKnowledge = m_pxKnowledgeSystem->GetBestThreatKnowledge();
	int iHideCount = 0;
	// if we have a threat to avoid 
	float fWorstWPEstimate = -Asura_Maths::LargeFloat;
	if (xKnowledge.GetGuid() != ASURA_GUID_UNREGISTERED)
	{
		float fWPEstimate;
		bool bSeenByThreat = EstimateWP(pxWP, iWaypointIndex, xKnowledge.m_xPosition, fWPEstimate);
		if (!bSeenByThreat)
		{
			++iHideCount;
			fWorstWPEstimate = fWPEstimate;
		}
	}

	for (u_int u =0; u < m_pxKnowledgeSystem->GetNumSecondaryThreats(); ++u)
	{
		const Axon_Knowledge xKnowledge = m_pxKnowledgeSystem->GetSecondaryThreatKnowledge(u);
		if (xKnowledge.GetGuid() != ASURA_GUID_UNREGISTERED)
		{
			float fWPEstimate;
			bool bSeenByThreat = EstimateWP(pxWP, iWaypointIndex, xKnowledge.m_xPosition, fWPEstimate);
			if (!bSeenByThreat)
			{
				++iHideCount;
				fWorstWPEstimate = (fWorstWPEstimate < fWPEstimate) ? fWorstWPEstimate : fWPEstimate;
			}
		}
	}

	if ((m_iBestWPHiddenCounter < iHideCount) ||
		((m_iBestWPHiddenCounter == iHideCount) && m_fBestWPWorstEstimate < fWorstWPEstimate) )
	{
		m_iBestWPHiddenCounter = iHideCount;
		m_fBestWPWorstEstimate = fWorstWPEstimate;
		m_iHideWP = iWaypointIndex;
	}

	// continue the search.
	return false;
}

bool AVP_PredatorFindHidingPlace::LinkFlagsTest( const Asura_AI_NavConnection* pxLink, const u_int uPathfinderFlags )
{
	return AVP_Navigation::PredatorLinkFlagsTest(pxLink, uPathfinderFlags);
}


bool AVP_PredatorFindHidingPlace::EstimateWP( const Asura_AI_NavVertex* pxWP, int iWPIndex, const Asura_Vector_3& xThreatPosition, float& fEstimate)
{
	fEstimate = -1.0f;

	// must not be too close to threat;
	float fDistanceSquared = (xThreatPosition - pxWP->GetPosition()).MagnitudeSquared();
	if (fDistanceSquared < m_fMinDistanceToThreatSquared)
	{
		return true;
	}

	// rather not be seen by threat
	Asura_Vector_3 xWPToThreatDirection = xThreatPosition -pxWP->GetPosition();
	xWPToThreatDirection.Normalise();
	float fMaxDistanceToSeeSq = AVP_Waypoint_Manager::GetMaxLOFValue(iWPIndex, xWPToThreatDirection);

	// can see the target (so target can probably see me)
	// make it cost more the closer the target is
	if (fDistanceSquared < fMaxDistanceToSeeSq)
	{
		fEstimate = 0.0f; // its not as bad as being close to the threat but its very bad 
		return true;
	}
	else // we can't be seen but if the distance between us and the cover is relativly short to the distance from the target its much better cover.
	{
		fEstimate = fDistanceSquared / (fMaxDistanceToSeeSq + 0.1f);
		return false;
	}
}



//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_Predator_Flee
//
//////////////////////////////////////////////////////////////////////////////

AVP_Behaviour_Predator_Flee::AVP_Behaviour_Predator_Flee( AVP_Brain_Actor_Predator* pxBrain )
: PARENT( pxBrain, AVP_BEHAVIOUR_TYPE_PREDATOR_FLEE )
, m_eCurrentState( FLEE )
, m_fTimeInState( 0.0f )
, m_xDestination(Asura_Maths::ZeroVector3)
{
}

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

	return PARENT::Activate();
}

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

	PARENT::Deactivate();
}

void AVP_Behaviour_Predator_Flee::Update( const float fTimeslice )
{
	PARENT::Update( fTimeslice );

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

	m_fTimeInState += fTimeslice;

	// wait until the jump or fall is done before doing anything
	if (!GetPredatorBrain()->CanPlanMove())
	{
		return;
	}

	switch ( m_eCurrentState )
	{
	case UNSET:
		Asura_Assert(false, "unexpected UNSET state");
		SetInError(true);
		SwitchToState(FINISHED);
	case FLEE:
		{
			Axon_Behaviour* pxSneakingChild = GetChildWithType( this, AVP_BEHAVIOUR_TYPE_PREDATOR_SNEAKING );
			if ( !pxSneakingChild || pxSneakingChild->IsInError() || pxSneakingChild->IsFinished())
			{
				SwitchToState(FLEE); // try again ... forever till higher behaviour stop us
			}
			else if ( ShouldReplan())
			{
				SwitchToState(FLEE); 
			}
		}
		break;
	case FINISHED:
		break;
	default:
		Asura_Assert(false, "unexpected state");
	}
}

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

void AVP_Behaviour_Predator_Flee::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_Flee::GetPredatorBrain() const
{
	return static_cast< AVP_Brain_Actor_Predator* >( GetProjectBrain() );
}

void AVP_Behaviour_Predator_Flee::SwitchToState( const State eNextState )
{

	switch ( m_eCurrentState )
	{
	case UNSET:
	case FLEE:
		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 FLEE:
		if (FindHidingPlace())
		{
			AVP_Behaviour_Predator_Sneaking* pxBehaviour =
				static_cast< AVP_Behaviour_Predator_Sneaking* >( CreateChildWithType( AVP_BEHAVIOUR_TYPE_PREDATOR_SNEAKING ) );
			if ( !pxBehaviour )
			{
				SetInError(true);
				SwitchToState( FINISHED );
			}
			else
			{
				pxBehaviour->SetDestination(m_xDestination, false);
			}
		}
		break;
	case FINISHED:
		SetFinished(true);
		break;
	case UNSET:
	default:
		Asura_Assert(false, "unexpected state");
		break;
	}
}


bool AVP_Behaviour_Predator_Flee::FindHidingPlace()
{
	AVP_Navigation::NavData& xNavData = GetPredatorBrain()->GetNavData();
	const AVP_Brain_Actor_Predator_ConfigurationData &xConfig = GetPredatorBrain()->GetConfigInfo();

	AVP_PredatorFindHidingPlace xHidingPlaceFinder( xNavData.m_usCurrentWPID, 
													xConfig.m_xFleeBehaviour.m_fFleeSearchRadius,  
													GetPredatorBrain()->GetKnowledgeSystem(), 
													GetPredatorBrain()->GetActor()->GetPosition(), 
													xConfig.m_xHighLevelBehaviour.m_fAvoidDistance, 
													xConfig.m_xFleeBehaviour.m_fFleeMinDistance);

	// Clear out the old route and find the new one.
	xNavData.ResetRoute();

	AVP_Navigation::SearchWaypoints(xNavData.m_uPathfinderFlags, &xHidingPlaceFinder);

	int iWPIndex = xHidingPlaceFinder.GetHidingPlace();
	if (iWPIndex == -1 )
	{
		SetInError();
		SwitchToState( FINISHED );
		return false;
	}

	const Asura_AI_NavVertex* pxWP = Asura_AI_Navigation_System::GetWaypoint( iWPIndex );
	if (!pxWP)
	{
		SetInError();
		SwitchToState( FINISHED );
		return false;
	}

	m_xDestination = pxWP->GetPosition();
	return true;
}


DEBUGVAR float dbg_fRecentlySeenTime = 0.2f;
DEBUGVAR float dbg_fReplanGraceTime = 1.0f;

bool AVP_Behaviour_Predator_Flee::ShouldReplan()
{
	return m_fTimeInState > dbg_fReplanGraceTime && GetPredatorBrain()->WasAnyTargetSeenRecently(dbg_fRecentlySeenTime);
}


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

