//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_Alien_Injured.cpp
//
// Copyright (C) 2008 Rebellion. All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

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

// header
#include "Behaviour/BehaviourTypes/Alien/AVP_Behaviour_Alien_Injured.h"

// asura
#ifndef ASURA_NO_DEBUG_OUTPUT
#include "Debugging/Asura_DebugLineRender.h"
#include "Rendering/Asura_Font_System.h"
#endif
// avp
#include "Actor/AVP_Actor_InteractionHandler.h"
#include "Actor/Vertebrates/Alien/AVP_Alien_MeleeCounter_System.h"
#include "Blueprint/AVP_Blueprint_HashIDs.h"
#include "Entity/AVP_Messages.h"
#include "Entity/Damage/AVP_EntityDamage_Request.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/AVP_ServerActor_Vertebrate.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/AVP_Vertebrate_Messages.h"


// axon
#include "Behaviour/BehaviourTypes/AVP_Behaviour_Types.h"
#include "Core/AVP_Navigation_Types.h"
#include "Core/BrainTypes/AVP_Brain_Actor_Alien.h"
#include "Knowledge/AVP_Knowledge_System_AlienTeam.h"

//////////////////////////////////////////////////////////////////////////////
//
// AVP_InjuredAlienFunctor
//
//////////////////////////////////////////////////////////////////////////////
class AVP_InjuredAlienFunctor : public AVP_Navigation::AlienAStarFunctor
{
public:
	virtual float EstimateTraversalCost( const int iWaypointIndex, const int iLinkIndex, const u_int uPathfinderFlags );
	virtual bool LinkFlagsTest( const Asura_AI_NavConnection* pxLink, const u_int uPathfinderFlags );

	AVP_InjuredAlienFunctor() 
	: PARENT()
	{}

	typedef AVP_Navigation::AlienAStarFunctor PARENT;
};

float AVP_InjuredAlienFunctor::EstimateTraversalCost( const int iWaypointIndex, const int iLinkIndex, const u_int uPathfinderFlags )
{
	const Asura_AI_NavVertex* pxFromWP = Asura_AI_Navigation_System::GetWaypoint( iWaypointIndex );
	const Asura_AI_NavVertex* pxToWP   = Asura_AI_Navigation_System::GetWaypoint( pxFromWP->m_pxConnections[ iLinkIndex ].m_usIndex );

	// ceiling or wall are not allowed
	if (pxToWP->IsCeilingWaypoint() || pxToWP->IsWallWaypoint())
	{
		return -1.0f;
	}

	return PARENT::EstimateTraversalCost( iWaypointIndex, iLinkIndex, uPathfinderFlags );
}

bool AVP_InjuredAlienFunctor::LinkFlagsTest( const Asura_AI_NavConnection* pxLink, const u_int uPathfinderFlags )
{
	u_short uLinkFlags = static_cast<u_short>(pxLink->GetFlags());
	if (AVP_Navigation::IsEnvHookLink(uLinkFlags) || 
		AVP_Navigation::IsGS2Link(uLinkFlags) || 
		AVP_Navigation::IsTransitionLink(uLinkFlags) )
	{
		return true;
	}
	return PARENT::LinkFlagsTest( pxLink, uPathfinderFlags );
}


//////////////////////////////////////////////////////////////////////////////
//
// AVP_InjuredAlienAwayFromFunctor
//
//////////////////////////////////////////////////////////////////////////////
class AVP_InjuredAlienAwayFromFunctor : public AVP_Navigation::AlienAwayFromFunctor
{
public:
	virtual float EstimateTraversalCost( const int iWaypointIndex, const int iLinkIndex, const u_int uPathfinderFlags );
	virtual bool LinkFlagsTest( const Asura_AI_NavConnection* pxLink, const u_int uPathfinderFlags );

	AVP_InjuredAlienAwayFromFunctor( const Asura_Vector_3& xPosition, const float fRange )
		: PARENT(xPosition, fRange)
	{}

	typedef AVP_Navigation::AlienAwayFromFunctor PARENT;
};

float AVP_InjuredAlienAwayFromFunctor::EstimateTraversalCost( const int iWaypointIndex, const int iLinkIndex, const u_int uPathfinderFlags )
{
	const Asura_AI_NavVertex* pxFromWP = Asura_AI_Navigation_System::GetWaypoint( iWaypointIndex );
	const Asura_AI_NavVertex* pxToWP   = Asura_AI_Navigation_System::GetWaypoint( pxFromWP->m_pxConnections[ iLinkIndex ].m_usIndex );

	// ceiling or wall are not allowed
	if (pxToWP->IsCeilingWaypoint() || pxToWP->IsWallWaypoint())
	{
		return -1.0f;
	}

	return PARENT::EstimateTraversalCost( iWaypointIndex, iLinkIndex, uPathfinderFlags );
}

bool AVP_InjuredAlienAwayFromFunctor::LinkFlagsTest( const Asura_AI_NavConnection* pxLink, const u_int uPathfinderFlags )
{
	u_short uLinkFlags = static_cast<u_short>(pxLink->GetFlags());
	if (AVP_Navigation::IsEnvHookLink(uLinkFlags) || 
		AVP_Navigation::IsGS2Link(uLinkFlags) || 
		AVP_Navigation::IsTransitionLink(uLinkFlags) )
	{
		return true;
	}
	return PARENT::LinkFlagsTest( pxLink, uPathfinderFlags );
}


//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_Alien_Injured
//
//////////////////////////////////////////////////////////////////////////////


DEBUGVAR float dbg_fRetryToAcquireTarget = 1.0f;

AVP_Behaviour_Alien_Injured::AVP_Behaviour_Alien_Injured( AVP_Brain_Actor_Alien* pxBrain )
: PARENT( pxBrain, AVP_BEHAVIOUR_TYPE_ALIEN_INJURED )
, m_fBehaviourTimer( 0.0f )
, m_bBeingMeleed(false)
{
	SetCurrentState( UNSET );
}

bool AVP_Behaviour_Alien_Injured::Activate()
{
	GetAlienBrain()->GetMovement().SetShouldJumpFromDanger( false );
	return PARENT::Activate();
}

void AVP_Behaviour_Alien_Injured::Deactivate()
{
	SwitchToState( UNSET );
	PARENT::Deactivate();
}

//DEBUGVAR float dbg_fWaitForInjuredAttack = 2.0f;
DEBUGVAR float dbg_fCannotMoveTime = 2.0f;
DEBUGVAR float dbg_fStabilisationTime = 0.4f;

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

	m_fBehaviourTimer    += fTimeslice;
	UpdateStateTimer(fTimeslice);

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

	if ( m_fBehaviourTimer > GetAlienBrain()->GetInjuredLifeSpan() && GetCurrentState() != FINISHED )
	{
		SwitchToState( FINISHED );
		return;
	}

	switch ( GetCurrentState() )
	{
		case UNSET:
		{
			AVP_Navigation::NavData& xNavData = GetAlienBrain()->GetNavData();
			xNavData.m_uPathfinderFlags = AVP_Navigation::AVP_AI_PATHFINDER_FLAG_ALIEN_LOW_MOBILITY;
			if (GetStateTimer() > dbg_fStabilisationTime)
			{
				SwitchToState(START);
			}
		}
		break;
		case START:
		{
			AVP_Navigation::NavData& xNavData = GetAlienBrain()->GetNavData();

			const Asura_AI_NavVertex* pxWP = Asura_AI_Navigation_System::GetWaypoint( xNavData.m_usCurrentWPID );
			if ( pxWP && !pxWP->IsCeilingWaypoint() && !pxWP->IsWallWaypoint() )
			{
				// Set our movement flags to disallow transition links.
				xNavData.m_uPathfinderFlags = AVP_Navigation::AVP_AI_PATHFINDER_FLAG_ALIEN_LOW_MOBILITY;

				// Also, set our current zone now, as we can't do any whizz-bang navigation anymore.
				xNavData.m_usCurrentZone = static_cast< u_short >( pxWP->GetZone() );

				// Pick our best threat.
				const AVP_AlienTeam_Knowledge* pxBestTarget = GetAlienBrain()->ChooseNewTarget();
				if ( pxBestTarget )
				{
					SwitchToState( CRAWLING_IN, pxBestTarget->GetGuid() );
				}
				else
				{
					SwitchToState( CRAWLING_ABOUT );
				}
			}
			else if (GetStateTimer() > dbg_fCannotMoveTime)
			{
				SwitchToState(FINISHED);
			}
			break;
		}

		case CRAWLING_IN:
		{
			const AVP_AlienTeam_Knowledge* pxTargetKnowledge = GetAlienBrain()->GetCurrentTargetKnowledge();
			
			if ( !pxTargetKnowledge )
			{
				SwitchToState( CRAWLING_ABOUT );
				break;
			}
			
			if ( ShouldAttack(pxTargetKnowledge) )
			{
				SwitchToState( ANKLE_BITING );
				break;
			}

			if ( GetAlienBrain()->GetMovement().IsFinished() || GetAlienBrain()->GetMovement().IsInError() )
			{
				// We have the opportunity to switch threats now.
				const AVP_AlienTeam_Knowledge* pxBestTarget = GetAlienBrain()->ChooseNewTarget();
				if ( pxBestTarget )
				{
					SwitchToState( CRAWLING_IN, pxBestTarget->GetGuid() );
				}
				else
				{
					SwitchToState( CRAWLING_ABOUT );
				}
			}
			else if ( GetAlienBrain()->ShouldRefreshPath((pxTargetKnowledge->GetPosition()- GetAlienBrain()->GetNavData().m_xDestinationPosition).MagnitudeSquared() ) )
			{
				AVP_InjuredAlienFunctor xInjuredFunctor;
				if (!GetAlienBrain()->GetMovement().GetToPosition( GetAlienBrain()->GetCurrentTargetPosition(), 0.0f, xInjuredFunctor, AVP_Alien_Movement::EMM_MEDIUM, true ))
				{
					SwitchToState( CRAWLING_ABOUT );
				}
			}

			break;
		}

		case CRAWLING_ABOUT:
			{
				if ( GetAlienBrain()->GetMovement().IsFinished() || GetAlienBrain()->GetMovement().IsInError() ) 
				{
					const AVP_AlienTeam_Knowledge* pxBestTarget = GetAlienBrain()->ChooseNewTarget();
					if ( pxBestTarget )
					{
						SwitchToState( CRAWLING_IN, pxBestTarget->GetGuid() );
					}
					else
					{
						SwitchToState(CRAWLING_ABOUT);

					}
				}
				else if ( GetStateTimer() > dbg_fRetryToAcquireTarget )
				{
					const AVP_AlienTeam_Knowledge* pxBestTarget = GetAlienBrain()->ChooseNewTarget();
					if ( pxBestTarget )
					{
						SwitchToState( CRAWLING_IN, pxBestTarget->GetGuid() );
					}
				}
			}

		case ANKLE_BITING:
// 		// since we can not relay on the move to report it finished 
// 		// wait for 2 seconds and give up
// 		if (GetStateTimer() > dbg_fWaitForInjuredAttack)
// 		{
// 			SwitchToState(FINISHED);
// 		}
		break;

		case FINISHED:
		{
			break;
		}
		default:
		case NUM_STATES:
			Asura_Assert(false, "Unexpected state");

	}
}

bool AVP_Behaviour_Alien_Injured::HandleMessage( const Asura_Message* pxMessage )
{
	if ( pxMessage->GetMsgID() == ASURA_MSG_MOVECHAINCOMPLETE )
	{
		if (GetCurrentState() == ANKLE_BITING)
		{
			SwitchToState(FINISHED);
			return true;
		}
	}
	 
	if (pxMessage->GetMsgID() == AVP_MSGID_INCOMING_MELEE_STARTED )
	{
		m_bBeingMeleed = true;
		return true;
	}
	
	if (pxMessage->GetMsgID() == AVP_MSGID_INCOMING_MELEE_FINISHED )
	{
		m_bBeingMeleed = false;
	}


	return PARENT::HandleMessage( pxMessage );
}

#ifndef ASURA_NO_DEBUG_OUTPUT

static const char* s_aszStateStrings[] =
{
	"UNSET",
	"START",
	"CRAWLING_ABOUT",
	"CRAWLING_IN",
	"ANKLE_BITING",
	"FINISHED",
	"NUM_STATES"
};

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

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

}
#endif

AVP_Brain_Actor_Alien* AVP_Behaviour_Alien_Injured::GetAlienBrain() const
{
	return static_cast< AVP_Brain_Actor_Alien* >( GetBrain() );
}

void AVP_Behaviour_Alien_Injured::SwitchToState( const State eState, Asura_Guid uGuid /*= ASURA_GUID_UNREGISTERED*/ )
{
	switch ( GetCurrentState() )
	{
	case ANKLE_BITING:
		GetAlienBrain()->StoppedTargetingCurrentTarget();
		break;
	case CRAWLING_IN:
		GetAlienBrain()->SetNearbyActorGuidToIgnore(ASURA_GUID_UNREGISTERED);
		break;
	case CRAWLING_ABOUT:
	case START:
	case NUM_STATES:
	case FINISHED:
	case UNSET:
	default:
		break;
	}

	SetCurrentState( eState );
	ResetStateTimer();
	switch ( GetCurrentState() )
	{
		case CRAWLING_IN:
		{
			GetAlienBrain()->StartedTargeting( uGuid );
			GetAlienBrain()->SetNearbyActorGuidToIgnore(uGuid);
			AVP_InjuredAlienFunctor xInjuredFunctor;
			if ( !GetAlienBrain()->GetMovement().GetToPosition( GetAlienBrain()->GetCurrentTargetPosition(), 0.0f, xInjuredFunctor, AVP_Alien_Movement::EMM_MEDIUM, true ) )
			{
				// If we can't get to our target then we should ignore it for a while to see if we can target someone else.
				GetAlienBrain()->IgnoreTarget( GetAlienBrain()->GetCurrentTargetGuid(), 2.0f );
				SwitchToState( START );
			}
			break;
		}

		case CRAWLING_ABOUT:
			{
				AVP_InjuredAlienAwayFromFunctor xFunctor(GetAlienBrain()->GetActor()->GetPosition(), 10.0f);
				if ( !GetAlienBrain()->GetMovement().GetAwayFromPosition( xFunctor, AVP_Alien_Movement::EMM_MEDIUM ) )
				{
					SwitchToState( FINISHED );
				}
				break;
			}


		case ANKLE_BITING:
		{
			const Asura_Guid uTargetGuid = GetAlienBrain()->GetCurrentTargetGuid();
			AVP_ServerActor_Vertebrate* pxTarget = AVP_ServerActor_Vertebrate::FindEntity(uTargetGuid);
			if (!pxTarget || pxTarget->IsDead() || AVP_Actor_InteractionHandler::IsEntityInteracting(uTargetGuid) ) 
			{
				SwitchToState( FINISHED);
				return;
			}

			GetAlienBrain()->GetMovement().Stop();
			GetAlienBrain()->StartedAttackingCurrentTarget();
			// Fire off the ankle-biting move.
			// Request a melee. This will be interpreted as us trying to counter the grapple we're in.
			Asura_Message* pxMessage = Asura_Network::CreateMessage( AVP_MSGID_REQUEST_MELEE, sizeof( AVP_Message_Vertebrate_RequestMelee ) );
			if ( pxMessage )
			{
				AVP_Message_Vertebrate_RequestMelee* pxData = static_cast< AVP_Message_Vertebrate_RequestMelee* >( pxMessage->GetData() );

				pxData->SetType( AVP_ALIEN_MELEEATTACK_TYPE_AI_INJURED );
				pxData->SetTargetGuid( GetAlienBrain()->GetCurrentTargetGuid() );

				pxMessage->SetToGuid( GetAlienBrain()->GetOwnerGuid() );
				pxMessage->SetFromGuid( GetAlienBrain()->GetOwnerGuid() );
				Asura_Network::Send( pxMessage );
			}


			break;
		}

		case FINISHED:
			{
				//## Eventually we'll play a thrash anim first.
				// Kill the actor and finish, as we're obviously not getting anywhere.
				AVP_EntityDamage_RequestVictim xDamage( uAVP_BLUEPRINT_NAME_DAMAGE_MELEE,
					GetBrain()->GetOwnerGuid(),
					ASURA_HASH_ID_UNSET,
					GetBrain()->GetOwnerGuid(),
					1000000.0f,
					GetAlienBrain()->GetActor()->GetPosition(),
					Asura_Vector_3( 0.0f, 1.0f, 0.0f ),
					Asura_Vector_3( 0.0f, 1.0f, 0.0f ) );
				GetAlienBrain()->GetActor()->TakeDamageExtended( xDamage, true );
			}
			GetAlienBrain()->GetMovement().Stop();
			SetFinished(true);
			break;
		case START:
		case NUM_STATES:
		case UNSET:
		default:
		{
			break;
		}
	}
}

DEBUGVAR float dbg_fMaxDistanceToBite = 2.5f; 
DEBUGVAR float dbg_fMinDistanceToBite = 1.5f; 
DEBUGVAR float dbg_fMaxHorisontalDistanceToBite = 0.5f; 
DEBUGVAR float dbg_fFrontCos = Asura_Maths::Cosine(Asura_Maths::DegToRad(30.0f));
bool AVP_Behaviour_Alien_Injured::ShouldAttack(const AVP_AlienTeam_Knowledge* pxTargetKnowledge) const
{

	Asura_Vector_3 xOurHeadPosition;
	GetAlienBrain()->GetActor()->GetHeadPosition(xOurHeadPosition);
	Asura_Vector_3 xToTarget = pxTargetKnowledge->GetPosition() - xOurHeadPosition;
	float fHorizontalDelta = (pxTargetKnowledge->GetPosition() - GetAlienBrain()->GetActor()->GetPosition()).y;
	xToTarget.y = 0.0f; // ignore hight 
	float fDistanceToTarget = xToTarget.Normalise();

	if (fDistanceToTarget >= dbg_fMaxDistanceToBite || Asura_Maths::Abs(fHorizontalDelta) > dbg_fMaxHorisontalDistanceToBite)
	{
		return false;
	}
		
	const Asura_Matrix_3x3& xOrientation = GetAlienBrain()->GetActor()->GetOrientation();
	Asura_Vector_3 xForward(xOrientation.e13, xOrientation.e23, xOrientation.e33);
	

	if (xToTarget * xForward < dbg_fFrontCos)
	{
		return false;
	}

	float fChance = Asura_Random::GetFloat(0.0f ,1.0f);

	if (!m_bBeingMeleed)
	{
		if (fDistanceToTarget < dbg_fMinDistanceToBite)
		{
			return true;
		}

		return fChance > (fDistanceToTarget - dbg_fMinDistanceToBite) / (dbg_fMaxDistanceToBite - dbg_fMinDistanceToBite);
	}
	else
	{
		return false;
	}
}


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