//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_Alien_Stalk.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_Stalk.h"

// asura
#include "Network/Asura_Guid.h"
#ifndef ASURA_NO_DEBUG_OUTPUT
#include "Debugging/Asura_DebugLineRender.h"
#include "Rendering/Asura_Font_System.h"
#endif

// avp
#include "Entity/Controller/AVP_ServerEntity_Team_Alien.h"

// axon
#include "Behaviour/BehaviourTypes/AVP_Behaviour_Types.h"
#include "Behaviour/BehaviourTypes/Alien/AVP_Behaviour_Alien_Movement.h"
#include "Core/AVP_Waypoint_Manager.h"
#include "Core/BrainTypes/AVP_Brain_Actor_Alien.h"
#include "Knowledge/AVP_Knowledge_System_AlienTeam.h"

//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_Alien_Stalk
//
//////////////////////////////////////////////////////////////////////////////

static const float s_fBurstDelay = 1.0f;

AVP_Behaviour_Alien_Stalk::AVP_Behaviour_Alien_Stalk( Axon_Project_Brain* pxBrain )
: PARENT( pxBrain, AVP_BEHAVIOUR_TYPE_ALIEN_STALK )
, m_fIdealDistance( 0.0f )
, m_fLightAvoidTimer( 0.0f )
, m_xLightCentroid( Asura_Maths::ZeroVector3 )
, m_fBurstTimer( s_fBurstDelay )
{
	SetCurrentState( UNSET );
}

bool AVP_Behaviour_Alien_Stalk::CanFinish() const
{
	return ( GetCurrentState() != MOVING_AWAY );
}

bool AVP_Behaviour_Alien_Stalk::Activate()
{
	const bool bParentOk = PARENT::Activate();

	// Set the correct initial movement parameters.
	switch ( GetAlienBrain()->GetBehaviourData().m_iMoveInMode )
	{
		case AVP_AlienBehaviourData::CREEP:
			//GetAlienBrain()->GetMovement().SetMovementMode( AVP_Navigation::MM_SLOW );
			GetAlienBrain()->GetMovement().SetMovementMode( AVP_Alien_Movement::EMM_MEDIUM );
			GetAlienBrain()->GetMovement().SetRouteType( AVP_Navigation::RT_SNEAKY );
			GetAlienBrain()->GetMovement().SetTraverseStyle( AVP_Navigation::TS_ALIEN_INSECTY );
			break;

		default:
		case AVP_AlienBehaviourData::DIRECT_CREEP:
			//GetAlienBrain()->GetMovement().SetMovementMode( AVP_Navigation::MM_SLOW );
			GetAlienBrain()->GetMovement().SetMovementMode( AVP_Alien_Movement::EMM_MEDIUM );
			GetAlienBrain()->GetMovement().SetRouteType( AVP_Navigation::RT_STANDARD );
			GetAlienBrain()->GetMovement().SetTraverseStyle( AVP_Navigation::TS_STANDARD );
			break;

		case AVP_AlienBehaviourData::CHARGE:
			GetAlienBrain()->GetMovement().SetMovementMode( AVP_Alien_Movement::EMM_MEDIUM );
			GetAlienBrain()->GetMovement().SetRouteType( AVP_Navigation::RT_STANDARD );
			GetAlienBrain()->GetMovement().SetTraverseStyle( AVP_Navigation::TS_STANDARD );
			break;
	}

	return bParentOk;
}

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

static Asura_Vector_3 s_xToLight;
static Asura_Vector_3 s_xToLightResolved;
static Asura_Vector_3 s_xLightCentroid;

void AVP_Behaviour_Alien_Stalk::PerFrameUpdate( const float fTimeslice )
{
	PARENT::PerFrameUpdate( fTimeslice );

	if ( fTimeslice == 0.0f ) { return; }
	UpdateStateTimer( fTimeslice );

	m_fBurstTimer -= fTimeslice;

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

	if ( !GetAlienBrain()->GetCurrentTargetKnowledge() )
	{
		SwitchToState( FINISHED );
	}

	switch ( GetCurrentState() )
	{
		case UNSET:
		{
			const AVP_AlienBehaviourData& xBehaviourData = GetAlienBrain()->GetBehaviourData();
			m_fIdealDistance = Asura_Random::GetFloat( xBehaviourData.m_fIdealDistanceMin * 1.1f, xBehaviourData.m_fIdealDistanceMax * 0.9f );

			SwitchToState( MOVING_ABOUT );


			//switch ( GetAlienBrain()->GetCurrentTargetRange() )
			//{
			//	case AVP_AR_NEAR:	SwitchToState( MOVING_AWAY );	break;
			//	default:
			//	case AVP_AR_OK:		SwitchToState( MOVING_ABOUT );	break;
			//	case AVP_AR_FAR:	SwitchToState( MOVING_IN );		break;
			//}
			break;
		}

		case MOVING_IN:
		{

			if ( GetAlienBrain()->GetCurrentTargetDistance() < m_fIdealDistance )
			{
				SwitchToState( WAITING );
			}
			else
			{
				AVP_Alien_Movement& xMovement = GetAlienBrain()->GetMovement();
				if ( xMovement.IsInError() || xMovement.IsFinished() )
				{
					SwitchToState( WAITING );
				}
				else
				{
					UpdateMovementMode();
					if ( GetAlienBrain()->ShouldRefreshPath((GetAlienBrain()->GetCurrentTargetPosition() - GetAlienBrain()->GetNavData().m_xDestinationPosition).MagnitudeSquared() ) )
					{
						xMovement.GetToPosition( GetAlienBrain()->GetCurrentTargetPosition(), AVP_Alien_Movement::GetMaxArrivalRange(), AVP_Navigation::s_xAlienAStarFunctor, AVP_Alien_Movement::EMM_MEDIUM );
					}
				}
			}

			break;
		}

		case BACKING_OFF:
		{
			if ( GetStateTimer() > 2.0f )
			{
				SwitchToState( MOVING_AWAY );
			}
			else if ( GetAlienBrain()->NeedToAvoidLight() )
			{
				SwitchToState( MOVING_AWAY );
			}
			else if ( GetStateTimer() > 1.0f && GetAlienBrain()->GetCurrentTargetRange() != AVP_AR_NEAR )
			{
				SwitchToState( WAITING );
			}
			else
			{
				UpdateMovementMode();

				const Asura_Vector_3& xOurPosition = GetAlienBrain()->GetActor()->GetPosition();
				Asura_Vector_3 xToTarget = GetAlienBrain()->GetCurrentTargetKnowledge()->GetPosition() - xOurPosition;
				xToTarget.Normalise();
				GetAlienBrain()->ForceMovementDirection( -xToTarget );
				GetAlienBrain()->SetLookAtPosition( GetAlienBrain()->GetCurrentTargetKnowledge()->GetPosition() );
				GetAlienBrain()->SetAimDirection( xToTarget );
				GetAlienBrain()->GetMovement().StartMoving();
			}

			break;
		}

		case MOVING_AWAY:
		{

			const AVP_AlienBehaviourData& xBehaviourData = GetAlienBrain()->GetBehaviourData();
			AVP_Alien_Movement& xMovement = GetAlienBrain()->GetMovement();
			if ( xMovement.IsInError() || xMovement.IsFinished() )
			{
				SwitchToState( WAITING );
			}
			else if ( GetAlienBrain()->GetCurrentTargetRange() >= AVP_AR_OK )
			{
				SwitchToState( WAITING );
			}
			else
			{
				UpdateMovementMode();

				if ( GetAlienBrain()->ShouldRefreshPath((GetAlienBrain()->GetCurrentTargetPosition() - GetAlienBrain()->GetNavData().m_xDestinationPosition).MagnitudeSquared() ) )
				{
					AVP_Navigation::AlienAwayFromFunctor xFunctor(GetAlienBrain()->GetCurrentTargetPosition(), xBehaviourData.m_fIdealDistanceMax);
					xMovement.GetAwayFromPosition( xFunctor, AVP_Alien_Movement::EMM_MEDIUM );
				}
			}

			break;
		}

		case AVOIDING_LIGHT: // TEST BEHAVIOUR!
		{
			m_fLightAvoidTimer -= fTimeslice;
			if ( m_fLightAvoidTimer <= 0.0f )
			{
				m_fLightAvoidTimer += Asura_Random::GetFloat( 0.5f, 0.75f );

				if ( !GetAlienBrain()->NeedToAvoidLight() )
				{
					switch ( GetAlienBrain()->GetCurrentTargetRange() )
					{
						case AVP_AR_NEAR:	SwitchToState( MOVING_AWAY );	break;
						default:
						case AVP_AR_OK:		SwitchToState( WAITING );		break;
						case AVP_AR_FAR:	SwitchToState( MOVING_IN );		break;
					}
				} 
				else if ( GetAlienBrain()->CalcLightCentroid( m_xLightCentroid ) )
				{
					// Move away from the light.
					UpdateMovementMode();

					const Asura_Vector_3& xOurPosition = GetAlienBrain()->GetActor()->GetPosition();
					Asura_Vector_3 xToTarget = GetAlienBrain()->GetCurrentTargetKnowledge()->GetPosition() - xOurPosition;
					xToTarget.Normalise();

					// Work out the best way to move.
					s_xLightCentroid = m_xLightCentroid;
					Asura_Vector_3 xFromLight = xOurPosition - m_xLightCentroid;
					s_xToLight = -xFromLight;
					GetAlienBrain()->ResolveToOurPlane( xFromLight, xFromLight );
					xFromLight.Normalise();

					// Ideally we should move directly away from the light, but if this is going to take us too near or far from the threat
					// we need to alter the direction of movement (to sideways, so we effectively circle-strafe instead).
					if ( GetAlienBrain()->GetCurrentTargetRange() < AVP_AR_OK )
					{
						// Make sure we're not moving any further forwards.
						const Asura_Matrix_3x3& xOurOrientation = GetAlienBrain()->GetActor()->GetOrientation();
						const Asura_Vector_3 xOurForward( xOurOrientation.e13, xOurOrientation.e23, xOurOrientation.e33 );
						const Asura_Vector_3 xOurSideways( xOurOrientation.e11, xOurOrientation.e21, xOurOrientation.e31 );
						if ( xFromLight * xOurForward > 0.707107f )
						{
							// Resolve this sideways instead.
							if ( xFromLight * xOurSideways > 0.0f )
							{
								xFromLight = xOurSideways;
							}
							else
							{
								xFromLight = -xOurSideways;
							}
						}
					}
					else if ( GetAlienBrain()->GetCurrentTargetRange() > AVP_AR_OK )
					{
						// Make sure we're not moving any further backwards.
						const Asura_Matrix_3x3& xOurOrientation = GetAlienBrain()->GetActor()->GetOrientation();
						const Asura_Vector_3 xOurForward( xOurOrientation.e13, xOurOrientation.e23, xOurOrientation.e33 );
						const Asura_Vector_3 xOurSideways( xOurOrientation.e11, xOurOrientation.e21, xOurOrientation.e31 );
						if ( xFromLight * xOurForward < -0.707107f )
						{
							// Resolve this sideways instead.
							if ( xFromLight * xOurSideways > 0.0f )
							{
								xFromLight = xOurSideways;
							}
							else
							{
								xFromLight = -xOurSideways;
							}
						}
					}

					s_xToLightResolved = xFromLight;
					GetAlienBrain()->ForceMovementDirection( xFromLight );
					GetAlienBrain()->SetLookAtPosition( GetAlienBrain()->GetCurrentTargetKnowledge()->GetPosition() );
					GetAlienBrain()->SetAimDirection( xToTarget );
					GetAlienBrain()->GetMovement().StartMoving();
				}
			}

			break;
		}

		case WAITING:
		{
			if (GetStateTimer() > 0.1f)
			{
				const Asura_Vector_3& xOurPosition = GetAlienBrain()->GetActor()->GetPosition();
				Asura_Vector_3 xToTarget = GetAlienBrain()->GetCurrentTargetKnowledge()->GetPosition() - xOurPosition;
				xToTarget.Normalise();
				// Force the movement direction so that we move off in the correct direction.
				//GetAlienBrain()->ForceMovementDirection( xToTarget );
				//GetAlienBrain()->SetLookAtPosition( GetAlienBrain()->GetCurrentTargetKnowledge()->GetPosition() );
				//GetAlienBrain()->SetAimDirection( xToTarget );

				if ( GetAlienBrain()->GetCurrentTargetRange() == AVP_AR_NEAR )
				{
					SwitchToState( MOVING_AWAY );
				}
				else if ( GetAlienBrain()->GetCurrentTargetRange() == AVP_AR_FAR )
				{
					SwitchToState( MOVING_IN );
				}
				else if ( GetAlienBrain()->NeedToAvoidLight() )
				{
					SwitchToState( AVOIDING_LIGHT );
				}
				else
				{
					SwitchToState( MOVING_ABOUT );
				}
			}

			break;
		}

		case MOVING_ABOUT:
		{
			UpdateMovementMode();

			AVP_Alien_Movement& xMovement = GetAlienBrain()->GetMovement();
			if ( xMovement.IsInError() || xMovement.IsFinished() )
			{
				SwitchToState( MOVING_ABOUT );
			}

			break;
		}

		case FINISHED:
		default:
			break;
	}
}

#ifndef ASURA_NO_DEBUG_OUTPUT
const char* AVP_Behaviour_Alien_Stalk::s_aszStateStrings[] =
{
	"UNSET",
	"MOVING_IN",
	"WAITING",
	"BACKING_OFF",
	"MOVING_AWAY",
	"AVOIDING_LIGHT",
	"MOVING_ABOUT",
	"FINISHED"
};

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

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

	const Asura_Vector_3& xOurPosition = GetAlienBrain()->GetActor()->GetPosition();
	Asura_DebugLineRender::AddLine( xOurPosition, xOurPosition + s_xToLight, 0xffff0000 );
	Asura_DebugLineRender::AddLine( xOurPosition, xOurPosition + s_xToLightResolved, 0xff00ff00 );
	Asura_DebugLineRender::AddSphere( s_xLightCentroid, 0.5f, 0xffff0000, 4, 2 );
}
#endif

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

void AVP_Behaviour_Alien_Stalk::SwitchToState( int iState )
{
	switch ( GetCurrentState() )
	{
		case MOVING_AWAY:
		case MOVING_IN:
		case MOVING_ABOUT:
		{
			break;
		}

		case FINISHED:
		{
			SetFinished( false );
			break;
		}

		case BACKING_OFF:
		case AVOIDING_LIGHT:
		{
			break;
		}
		case WAITING:
			GetAlienBrain()->GetMovement().SetMovementMode(AVP_Alien_Movement::EMM_MEDIUM);
			break;
		case UNSET:
		default:
			break;
	}

	SetCurrentState( iState );
	switch ( GetCurrentState() )
	{
		case MOVING_IN:
		{
			if ( !GetAlienBrain()->GetMovement().GetToPosition( GetAlienBrain()->GetCurrentTargetPosition(), AVP_Alien_Movement::GetMaxArrivalRange(), AVP_Navigation::s_xAlienAStarFunctor, AVP_Alien_Movement::EMM_MEDIUM ) )
			{
				// 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(), 5.0f );
				SetInError();
				SwitchToState( FINISHED );
			}
			break;
		}

		case MOVING_AWAY:
		{
			const AVP_AlienBehaviourData& xBehaviourData = GetAlienBrain()->GetBehaviourData();
			AVP_Navigation::AlienAwayFromFunctor xFunctor(GetAlienBrain()->GetCurrentTargetPosition(), xBehaviourData.m_fIdealDistanceMax);
			if ( !GetAlienBrain()->GetMovement().GetAwayFromPosition( xFunctor, AVP_Alien_Movement::EMM_MEDIUM ) )
			{
				SwitchToState( UNSET );
			}
			break;
		}

		case FINISHED:
		{
			if ( GetAlienBrain()->IsActive() )
			{
				GetAlienBrain()->GetMovement().Stop();
			}
			SetFinished();
			break;
		}

		case AVOIDING_LIGHT:
		{
			m_fLightAvoidTimer = 0.0f;
			break;
		}

		case MOVING_ABOUT:
		{
			AVP_Navigation::NavData& xNavData = GetAlienBrain()->GetNavData();

			// Pick a random position at the correct range from the current target.
			const Asura_Vector_3& xCurrentTargetPosition = GetAlienBrain()->GetCurrentTargetPosition();

			// Get a route away from the current target in the correct direction to the correct distance.
			AVP_Navigation::TestParams xTestParams;
            xTestParams.m_uPathfinderFlags     = xNavData.m_uPathfinderFlags;
			xTestParams.m_bRejectNonalignedWPs = true;
			u_short usNearestWP = ASURA_INVALID_WAYPOINT_ID;
			if ( !AVP_Navigation::GetNearestWaypointID( xCurrentTargetPosition, usNearestWP, xTestParams ) )
			{
				SwitchToState( WAITING );
				break;
			}

			Asura_Vector_3 xRandomDirection;
			xRandomDirection.RandomDirection();
			AVP_Navigation::AlienAwayFromInDirFunctor xAFID( xCurrentTargetPosition, m_fIdealDistance, xRandomDirection );

			int iRouteLength = 0;
			Asura_AI_Navigation_System::FindRouteCustom( AVP_Navigation::GetWorkingRouteBuffer(), iRouteLength, AVP_Navigation::GetWorkingRouteBufferLength(),
				usNearestWP, xNavData.m_uPathfinderFlags, &xAFID,
				AVP_Navigation::FunctorGoalCostFunction,
				AVP_Navigation::FunctorTraversalCostFunction,
				AVP_Navigation::FunctorGoalAchievedFunction,
				AVP_Navigation::FunctorLinkFlagsTestFunction );

			if ( iRouteLength )
			{
				// If we have found a route away from the current target then get a route from our current position to the final position on that route.
				const Asura_AI_NavVertex* pxEndWP = Asura_AI_Navigation_System::GetWaypoint( AVP_Navigation::GetWorkingRouteBuffer()[ iRouteLength - 1 ] );
				if ( !GetAlienBrain()->GetMovement().GetToPosition( pxEndWP->GetPosition(), AVP_Alien_Movement::GetMaxArrivalRange(), AVP_Navigation::s_xAlienAStarFunctor, AVP_Alien_Movement::EMM_MEDIUM ) )
				{
					SwitchToState( WAITING );
				}
			}
			else
			{
				SwitchToState( WAITING );
			}

			break;
		}
		case WAITING:
			{
				GetAlienBrain()->GetMovement().Stop();
			}
			break;

		case BACKING_OFF:
		case UNSET:
		default:
			break;
	}
}

void AVP_Behaviour_Alien_Stalk::UpdateMovementMode()
{
	//if ( GetAlienBrain()->IsInCoverFromTarget() )
	//{
	//	GetAlienBrain()->GetMovement().SetMovementMode( AVP_Navigation::MM_MEDIUM );
	//} 
	/*else */
	//if ( m_fBurstTimer <= 0.0f )
	//{
	//	if ( GetAlienBrain()->GetMovement().GetRouteFreeDistance() > 10.0f && !GetAlienBrain()->GetMovement().IsBursting() )
	//	{
	//		GetAlienBrain()->GetMovement().AddBurst();
	//		m_fBurstTimer = s_fBurstDelay;
	//	}
	//}

	//const AVP_AlienBehaviourData& xBehaviourData = GetAlienBrain()->GetBehaviourData();

	//if ( GetCurrentState() == MOVING_IN )
	//{
	//	// How near are we to our target?
	//	if ( GetAlienBrain()->GetCurrentTargetRange() < AVP_AR_FAR )
	//	{
	//		if ( xBehaviourData.m_iMoveInMode == AVP_AlienBehaviourData::CHARGE )
	//		{
	//			GetAlienBrain()->SetMovementMode( AVP_Navigation::MM_MEDIUM );
	//		}
	//		else
	//		{
	//			GetAlienBrain()->SetMovementMode( AVP_Navigation::MM_SLOW );
	//		}
	//		return;
	//	}

	//	GetAlienBrain()->SetMovementMode( AVP_Navigation::MM_MEDIUM );

	//	//// Do we have LOF to our target? If so we can potentially be spotted, so we should run.
	//	//const AVP_Navigation::NavData& xNavData = GetAlienBrain()->GetNavData();
	//	//const AVP_AlienTeam_Knowledge* pxTargetKnowledge = GetAlienBrain()->GetCurrentTargetKnowledge();
	//	//if ( pxTargetKnowledge )
	//	//{
	//	//	Asura_Vector_3 xToTarget = pxTargetKnowledge->GetPosition() - GetAlienBrain()->GetActor()->GetPosition();
	//	//	float		   fToTarget = xToTarget.Magnitude();

	//	//	if ( AVP_Waypoint_Manager::GetMaxLOFValue( xNavData.m_usCurrentWPID, xToTarget ) < fToTarget )
	//	//	{
	//	//		// We're hidden.
	//	//		GetAlienBrain()->SetMovementMode( AVP_Navigation::MM_SLOW );
	//	//	}
	//	//	else
	//	//	{
	//	//		// We're not hidden.
	//	//		GetAlienBrain()->SetMovementMode( AVP_Navigation::MM_MEDIUM );
	//	//	}
	//	//}
	//}
	//else if ( GetCurrentState() == MOVING_AWAY )
	//{
	//	GetAlienBrain()->SetMovementMode( AVP_Navigation::MM_MEDIUM );
	//}
	//else if ( GetCurrentState() == AVOIDING_LIGHT || GetCurrentState() == BACKING_OFF )
	//{
	//	GetAlienBrain()->SetMovementMode( AVP_Navigation::MM_SLOW );
	//}
}

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