//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_Alien_CycleRoundDelayed.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_CycleRoundDelayed.h"

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

// 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/Axon_Messages.h"
#include "Core/BrainTypes/AVP_Brain_Actor_Alien.h"
#include "Knowledge/AVP_Knowledge_System_AlienTeam.h"

// avp
#include "Core/AVP_Game_Core.h"
#include "Entity/Controller/AVP_ServerEntity_Squad_Alien.h"
#include "Entity/Controller/AVP_ServerEntity_Team_Alien.h"
#include "Entity/Locations/OrientedNode/GamesceneNode/AVP_ServerNode_GS2Node.h"
#include "Entity/PhysicalObject/Actor/AVP_ServerEntity_Actor.h"
#include "Entity/PlayerInfo/AVP_ServerEntity_PlayerInfo.h"
#include "Entity/Spawners/AVP_ServerEntity_SpawnController.h"
#include "Entity/Spawners/AVP_ServerEntity_SpawnPoint2.h"

//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_Alien_CycleRoundDelayed
//
//////////////////////////////////////////////////////////////////////////////

static const u_int s_uMAX_NUM_ATTEMPTS  = 5;
static const float s_fRUN_AWAY_DISTANCE = 75.0f;
static const float s_fMIN_HIDE_DISTANCE_SQ = 2500.0f; // 50^2
static const float s_fMAX_HIDE_DISTANCE_SQ = 5625.0f; // 75^2

AVP_Behaviour_Alien_CycleRoundDelayed::AVP_Behaviour_Alien_CycleRoundDelayed( Axon_Project_Brain* pxBrain )
: PARENT( pxBrain, AVP_BEHAVIOUR_TYPE_ALIEN_CYCLEROUNDDELAYED )
, m_uNumAttempts( 0 )
, m_uDespawnPointGuid( ASURA_GUID_UNREGISTERED )
, m_uHideWP( ASURA_INVALID_WAYPOINT_ID )
, m_bDestroy( false )
{
	SetCurrentState( UNSET );
}

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

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

	if ( GetCurrentState() == RUNNING_TO_UNSPAWN )
	{
		if ( GetAlienBrain()->GetMovement().IsInError() )
		{
			// Try to run to unspawn again.
			SwitchToState( RUNNING_TO_UNSPAWN );
		}
		else if ( GetAlienBrain()->GetMovement().IsFinished() )
		{
			AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = AVP_ServerEntity_SpawnPoint2::FindEntity( m_uDespawnPointGuid );
			if ( pxSpawnPoint )
			{
				if ( pxSpawnPoint->GetGamesceneInNodeGuid() == ASURA_GUID_UNREGISTERED )
				{
					UnspawnOrDestroy();
					SwitchToState( FINISHED );
				}
				else
				{
					SwitchToState( UNSPAWNING_GAMESCENE );
				}
			}
			else
			{
				SwitchToState( RUNNING_TO_UNSPAWN );
			}
		}
	}
	else if ( GetCurrentState() == UNSPAWNING_GAMESCENE )
	{
		Axon_Behaviour* pxBehaviour = GetChildWithType( this, AXON_BEHAVIOUR_TYPE_GS2_ACTOR );
		if ( !pxBehaviour || pxBehaviour->IsInError() )
		{
			SwitchToState( RUNNING_TO_UNSPAWN );
		}
		else if ( pxBehaviour->IsFinished() )
		{
			UnspawnOrDestroy();
			SwitchToState( FINISHED );
		}
	}
}

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

	UpdateStateTimer( fTimeslice );

	if (!GetAlienBrain()->CanPlanMove())
	{
		return;
	}
	
	switch ( GetCurrentState() )
	{
		case UNSET:
		{
			SwitchToState( RUNNING_TO_UNSPAWN );
			break;
		}

		case RUNNING_TO_HIDE:
		{
			if ( GetAlienBrain()->GetMovement().IsInError() || GetAlienBrain()->GetMovement().IsFinished() )
			{
				// Try to run to unspawn again.
				SwitchToState( RUNNING_TO_UNSPAWN );
			}

			break;
		}

		case RUNNING_TO_UNSPAWN:
		{
			// Also handled in the PerFrameUpdate because we need to switch to GS as soon as we arrive.

			// If our spawn point is made inactive then find another one.
			const AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = AVP_ServerEntity_SpawnPoint2::FindEntity( m_uDespawnPointGuid );
			if ( !pxSpawnPoint || !pxSpawnPoint->IsEntityActive() || !pxSpawnPoint->GetUseForDespawning() )
			{
				SwitchToState( RUNNING_TO_UNSPAWN );
			}
			break;
		}

		case RUNNING_AWAY:
		{
			if ( GetAlienBrain()->GetMovement().IsInError() || GetAlienBrain()->GetMovement().IsFinished() )
			{
				// Try to run to unspawn again.
				SwitchToState( RUNNING_TO_UNSPAWN );
			}

			break;
		}

		case UNSPAWNING_GAMESCENE:
		{
			// Handled in the PerFrameUpdate because we need to unspawn as soon as we're finished.
			break;
		}

		case WAITING_UNTIL_NOT_RENDERED:
		{
			if ( !AVP_ServerEntity_PlayerInfo::IsVisibleByAnyPlayer( GetAlienBrain()->GetActor()->GetPosition() ) )
			{
				UnspawnOrDestroy();
				SwitchToState( FINISHED );
			}
			break;
		}

		case FINISHED:
		default:
		{
			break;
		}
	}
}

#ifndef ASURA_NO_DEBUG_OUTPUT
const char* AVP_Behaviour_Alien_CycleRoundDelayed::s_aszStateStrings[] =
{
	"UNSET",
	"RUNNING_TO_HIDE",
	"RUNNING_TO_UNSPAWN",
	"RUNNING_AWAY",
	"UNSPAWNING_GAMESCENE",
	"WAITING_UNTIL_NOT_RENDERED",
	"FINISHED"
};

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

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

void AVP_Behaviour_Alien_CycleRoundDelayed::UnspawnOrDestroy()
{
	if ( m_bDestroy )
	{
		GetAlienBrain()->GetActor()->SetThisAndClientsAsDestroyed();
		// Actors register themselves as out of play when they are killed or deactivated.
		// We're skipping all of this, so we have to do it ourselves.
		AVP_ServerEntity_SpawnController::ActorOutOfPlay( GetProjectBrain()->GetOwnerGuid(), true );	// It is despawning, so send message that we are despawning.
		AVP_ServerEntity_SpawnPoint2::ActorOutOfPlay( GetProjectBrain()->GetOwnerGuid() );
	}
	else
	{
		GetAlienBrain()->GetAlienSquad()->UnspawnAlien( *GetAlienBrain()->GetAlienActor(), ASURA_GUID_UNREGISTERED );
	}
}

void AVP_Behaviour_Alien_CycleRoundDelayed::SwitchToState( int iNewState )
{
	switch ( GetCurrentState() )
	{
		case RUNNING_TO_UNSPAWN:
		{
			if ( iNewState != UNSPAWNING_GAMESCENE )
			{
				AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = AVP_ServerEntity_SpawnPoint2::FindEntity( m_uDespawnPointGuid );
				if ( pxSpawnPoint )
				{
					pxSpawnPoint->SetIsOccupied( false );
				}
				m_uDespawnPointGuid = ASURA_GUID_UNREGISTERED;
			}
			break;
		}

		case RUNNING_TO_HIDE:
		{
			m_uHideWP = ASURA_INVALID_WAYPOINT_ID;
			break;
		}

		case RUNNING_AWAY:
		{
			break;
		}

		case UNSPAWNING_GAMESCENE:
		{
			AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = AVP_ServerEntity_SpawnPoint2::FindEntity( m_uDespawnPointGuid );
			if ( pxSpawnPoint )
			{
				pxSpawnPoint->SetIsOccupied( false );
			}
			m_uDespawnPointGuid = ASURA_GUID_UNREGISTERED;
			DestroyChildWithType( AXON_BEHAVIOUR_TYPE_GS2_ACTOR );
			break;
		}

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

		case WAITING_UNTIL_NOT_RENDERED:
		case UNSET:
		default:
		{
			break;
		}
	}

	SetCurrentState( iNewState );
	ResetStateTimer();
	switch ( GetCurrentState() )
	{
		case RUNNING_TO_UNSPAWN:
		{
			if ( m_uNumAttempts++ < s_uMAX_NUM_ATTEMPTS )
			{
				bool bOk = false;

				// Find a spawn point to disappear into. Alternatively we'll have to just run away to somewhere out of LOF from the threats.
				AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetAlienBrain()->GetBestExit();
				if ( pxSpawnPoint )
				{
					if ( GetAlienBrain()->GetMovement().GetToPosition( pxSpawnPoint->GetGamesceneInPosition(), 0.0f, AVP_Navigation::s_xAlienAStarFunctor, AVP_Alien_Movement::EMM_MEDIUM ) )
					{
						m_uDespawnPointGuid = pxSpawnPoint->GetGuid();
						pxSpawnPoint->SetIsOccupied( true );
						bOk = true;
					}
				}

				if ( !bOk )
				{
					SwitchToState( RUNNING_TO_HIDE );
				}
			}
			else
			{
				SwitchToState( WAITING_UNTIL_NOT_RENDERED );
			}
			
			break;
		}

		case RUNNING_TO_HIDE:
		{
			bool bOk = false;

			m_uHideWP = GetHideWP();
			if ( m_uHideWP != ASURA_INVALID_WAYPOINT_ID )
			{
				const Asura_AI_NavVertex* pxWP = Asura_AI_Navigation_System::GetWaypoint( m_uHideWP );
				if ( pxWP )
				{
					if ( GetAlienBrain()->GetMovement().GetToPosition( pxWP->GetPosition(), 0.0f, AVP_Navigation::s_xAlienAStarFunctor, AVP_Alien_Movement::EMM_MEDIUM ) )
					{
						bOk = true;
					}
				}
			}

			if ( !bOk )
			{
				SwitchToState( RUNNING_AWAY );
			}

			break;
		}

		case RUNNING_AWAY:
		{
			const AVP_AlienTeam_Knowledge* pxTargetKnowledge = GetAlienBrain()->GetCurrentTargetKnowledge();
			const Asura_Vector_3& xFleePosition = pxTargetKnowledge ? pxTargetKnowledge->GetPosition() : GetAlienBrain()->GetActor()->GetPosition();
			AVP_Navigation::AlienAwayFromFunctor xFunctor(xFleePosition, s_fRUN_AWAY_DISTANCE);
			if ( GetAlienBrain()->GetMovement().GetAwayFromPosition( xFunctor, AVP_Alien_Movement::EMM_MEDIUM ) )
			{
			}
			else
			{
				SwitchToState( WAITING_UNTIL_NOT_RENDERED );
			}

			break;
		}

		case UNSPAWNING_GAMESCENE:
		{
			bool bOk = false;

			AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = AVP_ServerEntity_SpawnPoint2::FindEntity( m_uDespawnPointGuid );
			if ( pxSpawnPoint )
			{
				AVP_ServerNode_GS2Node*	pxGS2Node = AVP_ServerNode_GS2Node::FindEntity( pxSpawnPoint->GetGamesceneInNodeGuid() );
				if ( pxGS2Node )
				{
					Axon_Behaviour* pxGS2Behaviour = CreateChildWithType( AXON_BEHAVIOUR_TYPE_GS2_ACTOR );
					if ( pxGS2Behaviour )
					{
						Asura_Message* pxMessage = Asura_Network::CreateMessage( ASURA_MSG_AXON_GS2_TELEPORT_TO_NODE,
																				 sizeof( Axon_Msg_GS2_Teleport_To_Node ) );
						if ( pxMessage )
						{
							Axon_Msg_GS2_Teleport_To_Node* pxData = static_cast< Axon_Msg_GS2_Teleport_To_Node* >( pxMessage->GetData() );
							pxData->m_uNodeGuid = pxSpawnPoint->GetGamesceneInNodeGuid();

							pxGS2Behaviour->DoHandleMessage( pxMessage );
							Asura_Network::DestroyMessage( pxMessage );

							bOk = true;
						}
					}
				}
			}

			if ( !bOk )
			{
				SwitchToState( WAITING_UNTIL_NOT_RENDERED );
			}

			break;
		}

		case FINISHED:
		{
			GetAlienBrain()->GetMovement().Stop();
			m_uDespawnPointGuid = ASURA_GUID_UNREGISTERED;
			m_uHideWP           = ASURA_INVALID_WAYPOINT_ID;
			SetFinished();
			break;
		}

		case WAITING_UNTIL_NOT_RENDERED:
		{
			GetAlienBrain()->GetMovement().Stop();
		}
		case UNSET:
		default:
		{
			break;
		}
	}
}

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

u_int AVP_Behaviour_Alien_CycleRoundDelayed::GetHideWP() const
{
	// Get a waypoint that is a reasonable distance away and has no LOF to the local player (any player would be better but too slow).
	const AVP_ServerEntity_PlayerInfo* pxPlayerInfo = AVP_ServerEntity_PlayerInfo::FindEntity( AVP_Game_Core::GetPlayerGuid() );
	if ( !pxPlayerInfo )
	{
		return ASURA_INVALID_WAYPOINT_ID;
	}

	AVP_ServerEntity_Actor* pxFirstPlayer = AVP_ServerEntity_Actor::FindEntity( pxPlayerInfo->GetPuppetGuid() );
	if ( !pxFirstPlayer )
	{
		return ASURA_INVALID_WAYPOINT_ID;
	}

	AVP_Navigation::AlienFindCoverInRangeFunctor xFCIRF( GetAlienBrain()->GetActor()->GetCurrentWPID(),
														 pxFirstPlayer->GetCurrentWPID(),
														 s_fMIN_HIDE_DISTANCE_SQ,
														 s_fMAX_HIDE_DISTANCE_SQ );
	AVP_Navigation::SearchWaypoints( GetAlienBrain()->GetNavData().m_uPathfinderFlags, &xFCIRF );
	return xFCIRF.GetCoverWPID();
}

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