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

#ifdef AVP_USE_INPUT_SIMULATOR


// File's header
#include "InputSimulator/behaviours/AVP_InputSimulator_Behaviour_Marine_Patrol.h"

// Asura
#include "Core/Asura_Timers.h"
#include "Core/Asura_UserInput.h"

// AVP
#include "Core/AVP_Game_Core.h"
#include "Debugging/Asura_DebugLineRender.h"
#include "Debugging/Asura_DebugText.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Human/AVP_ServerActor_Human.h"
#include "Entity/PlayerInfo/AVP_ClientEntity_PlayerInfo.h"
#include "Entity/PlayerInfo/AVP_ServerEntity_PlayerInfo.h"
#include "InputSimulator/AVP_InputSimulator_Behaviour_Types.h"
#include "InputSimulator/AVP_InputSimulator_System.h"
#include "Network/AVP_Player.h"

// Globals
DEBUGVAR float dbg_fTurnAmount					= 6.5f;
DEBUGVAR float dbg_fPlayerWidth					= 2.0f;
DEBUGVAR float dbg_fPlayerHeight				= 2.0f;
DEBUGVAR float dbg_fWaypointDistanceThreshold	= 1.0f;
DEBUGVAR float dbg_fStuckTimerThreshold			= 0.5f;
DEBUGVAR float dbg_fMaxTimeUntilNextJump		= 10.0f;
DEBUGVAR float dbg_fMaxTimeUntilNextSprint		= 30.0f;
DEBUGVAR float dbg_fMaxTimeUntilReload			= 20.0f;
DEBUGVAR u_int dbg_uMaxPathfindingTries			= 3;

enum AVP_INPUT_SIMULATOR_MOVEMENT_DIR { MOVE_FORWARDS, MOVE_BACKWARDS, MOVE_LEFT, MOVE_RIGHT, AVP_INPUT_SIMULATOR_NUM_DIRS }; 

AVP_InputSimulator_Behaviour_Marine_Patrol::AVP_InputSimulator_Behaviour_Marine_Patrol()
: m_eUpdateState( SELECT_RANDOM_POSITION )
, m_uNearestWaypoint(0)
, m_uWaypointIndex(0)
, m_bCalculateStuckTimer(true)
, m_bUseSprint(false)
, m_uPathfindingTries(0)
, m_fJumpTimer(0.0f)
, m_fSprintTimer(0.0f)
, m_fStuckTimer(0.0f)
, m_fReloadTimer(0.0f)
{
	DestroyAllChildren();
	CreateChildWithType( AVP_INPUTSIMULATOR_BEHAVIOUR_TYPE_MARINE_HUNT );
}

// Gets the local player puppet. Returns NULL if no player is found.
AVP_ClientEntity_Actor* AVP_InputSimulator_Behaviour_Marine_Patrol::GetPlayer()
{
	Asura_Guid uLocalPlayerGuid = AVP_Game_Core::GetPlayerGuid();
	AVP_ClientEntity_PlayerInfo* pxLocalPlayerInfo = AVP_ClientEntity_PlayerInfo::FindEntity( uLocalPlayerGuid );
	if(pxLocalPlayerInfo)
	{
		// We have a player.
		Asura_Guid uCurrentPlayerPuppetGuid = pxLocalPlayerInfo->GetPuppetGuid();
		// Does our player have a puppet?
		AVP_ClientEntity_Actor* pxCurrentPlayerPuppet = AVP_ClientEntity_Actor::FindEntity( uCurrentPlayerPuppetGuid ); 
		if(pxCurrentPlayerPuppet)
		{
			// Found a puppet, from this puppet we can retrieve position information, orientation etc.
			return pxCurrentPlayerPuppet;
		}
	}
	return NULL;
}

void AVP_InputSimulator_Behaviour_Marine_Patrol::Update( u_char* ucCurrentInput, float* pfAxisValue ) 
{
	AVP_ServerActor_Human* pxActor = AVP_ServerActor_Human::FindEntity( AVP_Game_Core::GetPlayer()->GetPuppetGuid() );
	if( pxActor )
	{
		// We should not patrol when we are dead. 
		if( pxActor->IsDead() )
		{
			SetFinished();
		}
	}	

	switch( m_eUpdateState )
	{
		case SELECT_RANDOM_POSITION:
		{
			// Tries to find a random waypoint close to a enemy.
			if( SelectWaypointCloseToRandomEnemy() )
			{
				SetUpdateState( SETUP_PATH );
			}
			else
			{
				// We could not get a random waypoint close to a enemy, try to find a random waypoint on the map.
				if( SelectRandomWaypoint() )
				{
					SetUpdateState( SETUP_PATH );
				}
				else
				{
					// We could not find any waypoints, finish and let our parent handle it.
					SetFinished();
				}
			}
				
			break;
		}
		case SETUP_PATH:
		{
			// Tries to find a path to our waypoint that we acquired from SelectRandomWaypoint().
			if( SetupPath() )
			{
				SetUpdateState( MOVE );
			}
			else
			{
				// We could not find a path, could be that we were looking outside our zone. Let's try to find another waypoint and path.
				if( m_uPathfindingTries < dbg_uMaxPathfindingTries )
				{
					SetUpdateState( SELECT_RANDOM_POSITION );
					m_uPathfindingTries++;
				}
				else
				{
					// We could not get a setup a path, finish and let our parent handle it.
					m_uPathfindingTries = 0;
					SetFinished();
				}
			}
			break;
		}
		case MOVE:
		{
			// Move with the path. Returns false if we are stuck or we are at the final waypoint.
			if( Move( ucCurrentInput, pfAxisValue ) )
			{
				SetFinished();
			}

			// Do Marine specific actions such as, jump, sprint etc.
			DoActions( ucCurrentInput );
			break;
		}
		case NOT_SET:
		default:
		{
			SetFinished();
			break;
		}
	}
}


// This function a random waypoint near a enemy target.
bool AVP_InputSimulator_Behaviour_Marine_Patrol::SelectWaypointCloseToRandomEnemy()
{
	Asura_Hashed_List_ConstIt<AVP_ClientEntity_Actor> xIt( AVP_ClientEntity_Actor::GetList() );
	u_int uEnemyIndex = 0;
	if( AVP_ClientEntity_Actor::GetList()->GetNumEntries() == 0 )
	{
		// No enemies found.
		return false;
	}
	else
	{
		// Get a random enemy index from the list.
		uEnemyIndex = Asura_Random::GetIntU( 0, AVP_ClientEntity_Actor::GetList()->GetNumEntries() );
	}

	u_int iIndexCount = 0;
	for( ; !xIt.Done(); xIt.Next() )
	{
		if( uEnemyIndex != iIndexCount )
		{
			iIndexCount++;
		}
		else
		{
			const AVP_ClientEntity_Actor* pxActor = xIt.GetCurrent();
			// If our enemy is dead or if we found our own information, skip.
			if( !pxActor->IsDead() && AVP_Game_Core::GetPlayer()->GetGuid() != pxActor->GetPlayerInfoGuid() ) 
			{	
				m_uNearestWaypoint = Asura_AI_Navigation_System::FindWaypointNearestTo( pxActor->GetCurrentPosition() );
				return true;
			}
			else
			{
				// If the list contains more then one player get a new enemy index.
				if( AVP_ClientEntity_Actor::GetList()->GetNumEntries() > 1 )
				{
					uEnemyIndex = Asura_Random::GetIntU( 0, AVP_ClientEntity_Actor::GetList()->GetNumEntries() );
					xIt.Restart();
				}
				else
				{
					// Only our local player active in the game.
					return false;
				}
			}
		}
	}
	return false;
}


// This function will find a waypoint randomly on the map and set it to the m_uNearestWaypoint.
bool AVP_InputSimulator_Behaviour_Marine_Patrol::SelectRandomWaypoint()
{
	u_short uNumOfWaypoints = Asura_AI_Navigation_System::GetNumberOfWaypoints() - 1;
	if( uNumOfWaypoints == 0 )
	{
		// No waypoints on this map.
		return false;
	}

	m_uNearestWaypoint = static_cast<u_short>( Asura_Random::GetInt( 0, uNumOfWaypoints ) );

	return true;
}

// Setup a path to our goal. 
bool AVP_InputSimulator_Behaviour_Marine_Patrol::SetupPath()
{
	if( !AVP_Game_Core::GetPlayer() )
	{
		return false;
	}

	Asura_Vector_3 xPlayerPosition( 0.0f, 0.0f, 0.0f );
	AVP_ClientEntity_Actor* pxPlayer = GetPlayer();
	if( pxPlayer )
	{
		xPlayerPosition = pxPlayer->GetCurrentPosition();

		u_short uGoalWaypoint;
		if( AVP_Navigation::GetNearestWaypointID( xPlayerPosition, uGoalWaypoint ) )
		{
			Asura_AI_NavVertex*	pxStartWaypoint = Asura_AI_Navigation_System::GetWaypoint( m_uNearestWaypoint );
			Asura_Assert( pxStartWaypoint, "Can not find the start waypoint" );
			if( !pxStartWaypoint ) { return false; }
			Asura_AI_NavVertex*	pxEndWaypoint = Asura_AI_Navigation_System::GetWaypoint( uGoalWaypoint );
			Asura_Assert( pxStartWaypoint, "Can not find the end waypoint" );
			if( !pxEndWaypoint ) { return false; }

			m_xNavData.ResetRoute();

			m_xNavData.m_uPathfinderFlags		= AVP_Navigation::AVP_AI_CONNECTION_FLAG_HUMANOID; 
			m_xNavData.m_eMovementMode			= AVP_Navigation::MM_MEDIUM;
			m_xNavData.m_usCurrentWPID			= static_cast<u_short>(m_uNearestWaypoint);
			m_xNavData.m_xStartPosition			= pxStartWaypoint->GetPosition();
			m_xNavData.m_usDestinationWPID		= static_cast<u_short>(uGoalWaypoint);
			m_xNavData.m_xDestinationPosition	= pxEndWaypoint->GetPosition();
			m_xNavData.m_usCurrentZone			= pxStartWaypoint->m_usZone;
			m_xNavData.m_fCharacterHeight		= dbg_fPlayerHeight;
			m_xNavData.m_fCharacterWidth		= dbg_fPlayerWidth;
 
			if( AVP_Navigation::FindRoute( m_xNavData, &AVP_Navigation::s_xHumanoidAStarFunctor ) )
			{
				m_uWaypointIndex = m_xNavData.m_usRouteLength-1;
				m_uPathfindingTries = 0;
				return true;
			}
		}
	}
	
 	return false;
}


// This function calculates which direction from the player (N, W, S, E) is closes to our current waypoint and moves the player in that direction.
void AVP_InputSimulator_Behaviour_Marine_Patrol::MoveTowardsWaypoint( const Asura_Vector_3 xPlayerPosition, const Asura_AI_NavVertex* pxNextWaypoint, u_char* ucCurrentInput, float* pfAxisValue )
{
	float fDistance[AVP_INPUT_SIMULATOR_NUM_DIRS];

	Asura_Vector_3 xPlayerForward	= xPlayerPosition + (Asura_Vector_3(0.0f,0.0f,1.0f) * *Asura_Camera::GetOrientation());
	Asura_Vector_3 xPlayerBackward	= xPlayerPosition + (Asura_Vector_3(0.0f,0.0f,-1.0f) * *Asura_Camera::GetOrientation());
	Asura_Vector_3 xPlayerLeft		= xPlayerPosition + (Asura_Vector_3(-1.0f,0.0f,0.0f)  * *Asura_Camera::GetOrientation());
	Asura_Vector_3 xPlayerRight		= xPlayerPosition + (Asura_Vector_3(1.0f,0.0f,0.0f) * *Asura_Camera::GetOrientation());

	fDistance[MOVE_FORWARDS] = (pxNextWaypoint->GetPosition() - xPlayerForward).Magnitude();
	fDistance[MOVE_BACKWARDS] = (pxNextWaypoint->GetPosition() - xPlayerBackward).Magnitude();
	fDistance[MOVE_LEFT] = (pxNextWaypoint->GetPosition() - xPlayerLeft).Magnitude();
	fDistance[MOVE_RIGHT] = (pxNextWaypoint->GetPosition() - xPlayerRight).Magnitude();

	// Reset the move input
	AVP_InputSimulator_System::Platform_SetMoveForwardsButton( false, ucCurrentInput, pfAxisValue );
	AVP_InputSimulator_System::Platform_SetMoveBackwardsButton( false, ucCurrentInput, pfAxisValue );
	AVP_InputSimulator_System::Platform_SetMoveLeftButton( false, ucCurrentInput, pfAxisValue );
	AVP_InputSimulator_System::Platform_SetMoveRightButton( false, ucCurrentInput, pfAxisValue );

	float fNearestDistance = Asura_Maths::LargeFloat;

	int iIndex = 0;
	for( int i = 0; i < AVP_INPUT_SIMULATOR_NUM_DIRS; i++ ) 
	{
		if( fDistance[i] < fNearestDistance )
		{
			iIndex = i;
			fNearestDistance = fDistance[i];
		}
	}

	switch( iIndex )
	{
		case MOVE_FORWARDS:
		{
			AVP_InputSimulator_System::Platform_SetMoveForwardsButton( true, ucCurrentInput, pfAxisValue );
			break;
		}
		case MOVE_BACKWARDS:
		{
			AVP_InputSimulator_System::Platform_SetMoveBackwardsButton( true, ucCurrentInput, pfAxisValue );
			break;
		}
		case MOVE_LEFT:
		{
			AVP_InputSimulator_System::Platform_SetMoveLeftButton( true, ucCurrentInput, pfAxisValue );
			break;
		}
		case MOVE_RIGHT:
		{
			AVP_InputSimulator_System::Platform_SetMoveRightButton( true, ucCurrentInput, pfAxisValue );
			break;
		}
	}
	
}

// This function updates our player's orientation towards our current waypoint.
void AVP_InputSimulator_Behaviour_Marine_Patrol::UpdateOrientation( float* pfAxisValue, Asura_Vector_3 xToTarget )
{
	xToTarget.y = 0.0f;
	xToTarget.Normalise();

	Asura_Vector_3 xOurForward = Asura_Vector_3(1.0f,0.0f,0.0f) * *Asura_Camera::GetOrientation();
	xOurForward.y = 0.0f;
	xOurForward.Normalise();

	float fDot = xOurForward * xToTarget;
	float fAngle = Asura_Maths::ArcCosine( fDot );

	if( Asura_Maths::Abs( fAngle-Asura_Maths::Deg90 ) > 0.1f )
	{
		if(fDot > 0.0f)
		{
			AVP_InputSimulator_System::Platform_SetTurnAxisHorizontal( dbg_fTurnAmount, pfAxisValue );
		}
		else
		{
			AVP_InputSimulator_System::Platform_SetTurnAxisHorizontal( -dbg_fTurnAmount, pfAxisValue );
		}
	}
}

void AVP_InputSimulator_Behaviour_Marine_Patrol::DoActions( u_char* ucCurrentInput )
{
	// Should we jump? If so, jump and get a random float for our jump timer.
	m_fJumpTimer -= Asura_Timers::GetFrameTime();
	if( m_fJumpTimer < 0.0f )
	{
		AVP_InputSimulator_System::Platform_SetJumpButton( true, ucCurrentInput );
		m_fJumpTimer = Asura_Random::GetFloat( 0.0f, dbg_fMaxTimeUntilNextJump );
	}

	m_fSprintTimer -= Asura_Timers::GetFrameTime();
	if( m_fSprintTimer < 0.0f )
	{
		m_bUseSprint = !m_bUseSprint;
		m_fSprintTimer = Asura_Random::GetFloat( 0.0f, dbg_fMaxTimeUntilNextSprint );
	}
	if( m_bUseSprint )
	{
		AVP_InputSimulator_System::Platform_SetSprintButton( true, ucCurrentInput );
	}
	else
	{
		AVP_InputSimulator_System::Platform_SetSprintButton( false, ucCurrentInput );
	}
	
	m_fReloadTimer -= Asura_Timers::GetFrameTime();
	if( m_fReloadTimer < 0.0f )
	{
		AVP_InputSimulator_System::Platform_SetReloadButton( true, ucCurrentInput );
		m_fReloadTimer = Asura_Random::GetFloat( 0.0f, dbg_fMaxTimeUntilReload );
	}

}

bool AVP_InputSimulator_Behaviour_Marine_Patrol::Move( u_char* ucCurrentInput, float* pfAxisValue )
{
	Asura_Vector_3 xPlayerPosition( 0.0f, 0.0f, 0.0f );
	AVP_ClientEntity_Actor* pxPlayer = GetPlayer();
	if( pxPlayer )
	{
		xPlayerPosition = pxPlayer->GetCurrentPosition();

		Asura_AI_NavVertex*	pxNextWaypoint = Asura_AI_Navigation_System::GetWaypoint( m_xNavData.m_pusRoute[m_uWaypointIndex] );	
		Asura_Assert( pxNextWaypoint, "Unable to get next waypoint in route." );
		Asura_Vector_3 xToTarget(pxNextWaypoint->GetPosition() - xPlayerPosition);
		
		if( m_bCalculateStuckTimer ) // Calculates the distance to our waypoint and use our it for the stuck timer.
		{
			Asura_Vector_3 xDistance = xToTarget;
			xDistance.y = 0.0f;
			m_fStuckTimer = ( xDistance.Magnitude() + dbg_fStuckTimerThreshold );
			m_bCalculateStuckTimer = false;
		}

		m_fStuckTimer -= Asura_Timers::GetFrameTime(); 
		if( m_fStuckTimer < 0.0f )
		{
			// We are stuck.
			return true;
		}

		// Update our orientation.
		UpdateOrientation( pfAxisValue, xToTarget );

		// Move towards our current waypoint.
		MoveTowardsWaypoint( xPlayerPosition, pxNextWaypoint, ucCurrentInput, pfAxisValue );

		// Have We arrived at our current waypoint?
		if( (pxNextWaypoint->GetPosition() - xPlayerPosition).Magnitude() < dbg_fWaypointDistanceThreshold )
		{
			if( m_uWaypointIndex > 0 )
			{
				// Need to calculate the distance to our next waypoint.
				m_bCalculateStuckTimer = true;
				m_uWaypointIndex--;
			}
			else
			{
				// We are at our goal waypoint.
				return true;
			}
		}
	}

	return false;
}

#endif // AVP_USE_INPUT_SIMULATOR
