/**
*
* \file AVP_Player.cpp
*
* Created by Anthony Gregson - 30/08/06
*
* Copyright (C) 2006 Rebellion, All Rights Reserved.
*
* \brief
*
**/

#include "Core/Asura.h"

#include "Network/AVP_Player.h"

#include "Animation/Asura_Animation.h"
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Timers.h"
#include "Network/Asura_Network.h"
#include "Rendering/Asura_Font_System.h"
#include "Rendering/Asura_Render.h"
#include "Sound/Asura_Audio_System.h"
#ifdef ASURA_USE_STREAMING
#include "Streaming/Asura_Streaming_System.h"
#endif //#ifdef ASURA_USE_STREAMING

#include "Actor/Vertebrates/AVP_Vertebrate_InputHandler.h"
#include "Core/AVP_Core.h"
#include "Core/AVP_Game_Core.h"
#include "Entity/AVP_Entity_Classification.h"
#include "Entity/AVP_Messages.h"
#include "Entity/AVP_Server_Messages.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/AVP_ClientActor_Vertebrate.h"
#include "Entity/PhysicalObject/CommandPost/AVP_ClientEntity_CommandPost.h"
#include "Entity/PlayerInfo/AVP_PlayerInfo_Messages.h"
#include "Multiplayer/AVP_Multiplayer_GameMessageSystem.h"
#include "Multiplayer/AVP_Multiplayer_GameRules.h"

#ifdef ASURA_TOOLS
#include "Interface/Asura_Overlay_System.h"
#endif

DVSVAR	float	dvs_fResetLookIdleTime		= 5.0f; // 5 second pause before leveling out camera.
DVSVAR	float	dvs_fResetLookSpeed		= Asura_Maths::Deg90; // move 90 degrees per second

Asura_Hashed_List<AVP_Player> AVP_Player::s_xList;

AVP_Player::AVP_Player()
: m_uPuppetGuid(ASURA_GUID_UNREGISTERED)
, m_fIdleTimer( 0.0f )
, m_fRespawnTimer( 0.0f )
{
	s_xList.AddToList(this);

	Classification = AVP_ENTITYCLASS_PLAYER;

	Reset();

#ifdef ASURA_USE_STREAMING
	Asura_Streaming_System::SetControllerEntityGuid(Guid);
#endif //#ifdef ASURA_USE_STREAMING

#ifdef AVP_SECOND_CONTROL_FLY_CAM
	m_xExternalCamera.CreateControlMap( Guid );
#endif
}

AVP_Player::~AVP_Player()
{
	s_xList.RemoveFromList(this);

#ifdef ASURA_USE_STREAMING
	Asura_Streaming_System::ResetControllerEntityGuid();
#endif //#ifdef ASURA_USE_STREAMING
}

void AVP_Player::Reset()
{
	// AJG - 06/11/07 - The code below looks dangerous to me, surely the client entity should be destroyed by its server entity only/
	//AVP_ClientEntity_Actor*	pxClient	= AVP_ClientEntity_Actor::FindEntity(Guid);

	//if(pxClient != NULL)
	//{
	//	pxClient->SetAsDestroyed();
	//}
}

void AVP_Player::Render()
{
	AVP_ClientEntity_Actor*	pxClient	= AVP_ClientEntity_Actor::FindEntity(m_uPuppetGuid);

	if(pxClient != NULL && Asura_Render::IsSystemEnabled(ASURA_RENDER_SYSTEM_HUD))
	{
		const Asura_Handle	iFontHandle	= AVP_Core::GetFontHandle(AVP_Core::HUD_FONT);
		Asura_Font_System::SetFont(iFontHandle);

		pxClient->BeginRenderHUD();
		pxClient->RenderHUD();
		pxClient->EndRenderHUD();
	}
	// TODO - Some kind of limbo-state hud rendering?
}

void AVP_Player::SetCamera()
{
	AVP_ClientEntity_Actor*	pxClient	= AVP_ClientEntity_Actor::FindEntity(m_uPuppetGuid);

#ifdef AVP_SECOND_CONTROL_FLY_CAM
	if( !m_xExternalCamera.GetUsingFlyCam() && pxClient != NULL && !pxClient->IsRagdoll())
#else
	if(pxClient != NULL && !pxClient->IsRagdoll())
#endif
	{
		pxClient->SetCamera();
		// Keep the camera on our character, so it starts there should we suddenly lose them.
		//remove roll component
		Asura_Matrix_3x3 xOrientation = *Asura_Camera::GetOrientation();
		Asura_Euler_3 xEulerOrient;
		xEulerOrient.FromMatrix(xOrientation);
		xEulerOrient.z = 0;
		xEulerOrient.ToMatrix(xOrientation);
		m_xExternalCamera.Initialise(*Asura_Camera::GetPosition(), xOrientation);
	}
	// TODO: different handling depending on whether we thought we had an actor (i.e. the puppet guid is not unregistered) or not?
	else
	{
		m_xExternalCamera.SetCamera();
	}
}

void AVP_Player::SetExternalCameraTarget(Asura_Guid uPrimaryTargetGuid, Asura_Guid uSecondaryTargetGuid, float fInitialDistance)
{
	m_xExternalCamera.Initialise( uPrimaryTargetGuid, fInitialDistance ); 
	m_xExternalCamera.SetLastKnownTarget(uSecondaryTargetGuid); 
	//m_xExternalCamera.SetFirstPersonCamera(true);
	Asura_Matrix_3x3 xCameraOrientation; 
	Asura_Maths::OrientationFromDirection(xCameraOrientation, Asura_Vector_3(0.0f, 1.0f, 0.0f));
	m_xExternalCamera.SetCameraOrientation(xCameraOrientation); 
}

void AVP_Player::MakeCommandMessageForServer()
{
	Asura_Assert(false, "Shouldn't be called any more.");
}

void AVP_Player::Update()
{
	AVP_ClientActor_Vertebrate*	pxClient	= AVP_ClientActor_Vertebrate::FindEntity(m_uPuppetGuid);
	if (pxClient != NULL)
	{
		// update our client entity
		pxClient->UpdateInput();

		AVP_Actor_AnimationHandler* pxAnimHandler = pxClient->GetAnimationHandler();
		if ( pxAnimHandler != NULL ) 
		{

			const Asura_Vector_3&			xClientPosition		= pxClient->GetCurrentPosition();
			Asura_Matrix_3x3				xAnimOrientation	= pxClient->GetCurrentOrientation();
			u_int	uFlags	= ASURA_HIERARCHY_RENDER_NO_FRUSTUM_TEST | ASURA_HIERARCHY_RENDER_NO_MODULELINK_TEST | ASURA_HIERARCHY_RENDER_DONTRENDER | ASURA_HIERARCHY_RENDER_UPDATEBONESALWAYS;
			if ( pxAnimHandler->IsRagdoll() )
			{
				uFlags |= ASURA_HIERARCHY_RENDER_BONES_IN_WORLDSPACE;
			}

			// Ensure that the animation has been updated this frame so our marker points etc. are up to date...
			if(pxAnimHandler->GetAnimation())
			{
				pxAnimHandler->GetAnimation()->Update(xClientPosition, xAnimOrientation, pxClient->GetShapeInstance().GetAnimTimestep(), uFlags);
			}
			else
			{
				Asura_Assert(false, "AVP_Player::Update() - No animation, unable to update!");
			}
		}

		AVP_ClientEntity_PlayerInfo* pxPlayerInfo = AVP_ClientEntity_PlayerInfo::FindEntity( GetGuid() );
		if ( pxPlayerInfo )
		{
			pxPlayerInfo->UpdateForcedLooking();
		}
	}
	// TODO: different handling depending on whether we thought we had an actor (i.e. the puppet guid is not unregistered) or not?
	else
	{
		m_xExternalCamera.Update();

		// TODO - probably need a check on whether we're allowed to respawn, although maybe that can be done entirely server-side.
		// JG - 17 September 2008 - Added a check client side for multiplayer games, to stop players spamming the server with create actor messages
		bool bAllowRespawning = true; 
		bool bAutomaticRespawning = false; 
		
		if (AVP_Settings::IsMultiPlayer())
		{
			// Check the respawn timer to see if this player is allowed to respawn
			if (m_fRespawnTimer > 0.0f)
			{
				// No, wait a little bit longer
				bAllowRespawning = false; 
				AVP_Multiplayer_GameMessageSystem::UpdateRespawnTimer( m_fRespawnTimer ); 
				//if (m_xExternalCamera.GetFirstPersonCamera() && 
				if (m_xExternalCamera.GetLastKnownTarget() != ASURA_GUID_UNREGISTERED &&
					m_fRespawnTimer < (AVP_Multiplayer_GameRules::GetRespawnDelay() / 2.0f))
				{
					//m_xExternalCamera.SetFirstPersonCamera(false); 
					m_xExternalCamera.SetTargetEntity( m_xExternalCamera.GetLastKnownTarget() ); 
					m_xExternalCamera.SetLastKnownTarget( ASURA_GUID_UNREGISTERED ); 
					m_xExternalCamera.ResetCameraToTarget(); 
				}
			}
			else
			{
				// Automatically respawn this frame, prevent sending more messages for at least a second
				bAutomaticRespawning = true; 
				SetRespawnTimer( 1.0f ); 
				
				// Hide the respawn timer and also any respawn related message
				AVP_Multiplayer_GameMessageSystem::HideRespawnTimer(); 
				AVP_Multiplayer_GameMessageSystem::HidePriorityMessage(); 

				AVP_Settings::SetPlayerSpecies(AVP_Settings::AVP_SPECIES_UNKNOWN);
			}
		}
		
		bool bManualRespawning = m_xExternalCamera.GetControlMapValue(AVP_External_Camera::RESPAWN) != 0.0f;
		
		if (bAllowRespawning && (bAutomaticRespawning || bManualRespawning))
		{
			SendActorCreateMessage(true);
		}
	}
#ifdef AVP_SECOND_CONTROL_FLY_CAM
	if( pxClient != NULL )
	{
		m_xExternalCamera.Update();
	}
#endif

	AVP_ClientEntity_Actor* pxActor = AVP_ClientEntity_Actor::FindEntity( m_uPuppetGuid );
	
	UpdateAutoCenter(pxActor);

	UpdateCommandPostCapture(pxActor); 

	// Update the respawn timer
	if (m_fRespawnTimer > 0.0f)
	{
		m_fRespawnTimer -= Asura_Timers::GetGameTimeSlice();
	}
}

bool AVP_Player::IsLocalPlayer(Asura_Guid uGuid)
{
	u_int uPlayerGuid = GetGuid();

	return(uGuid == uPlayerGuid) || (uGuid == m_uPuppetGuid);
}

void AVP_Player::SetPuppetGuid(Asura_Guid uGuid)
{
	if (m_uPuppetGuid == uGuid) { return; }

	// We no longer attempt to return an actor we're leaving to an AI, as it was breaking in many entertaining ways and we don't actually have game mechanics that need it anyway.
	// We'll assume that if we're leaving an actor, it no longer has much need to do anything anyway.
	// It's probably also reasonable to leave it up to the player info, rather than doing it here too.

	m_uPuppetGuid = uGuid;

	if (m_uPuppetGuid != ASURA_GUID_UNREGISTERED)
	{
		// Set the new actor as a player.
		AVP_ClientEntity_Actor* pxActor = AVP_ClientEntity_Actor::FindEntity(m_uPuppetGuid);
		if (pxActor)
		{
			pxActor->MakePlayer(GetGuid());
		}

#ifdef AVP_SECOND_CONTROL_FLY_CAM
		// To make sure we recreate the fly came after the death cam gets destroyed
		m_xExternalCamera.SetRecreateFlyCamControlMap( true );
#endif
		if (m_xExternalCamera.HasControlMap())
		{
			m_xExternalCamera.DestroyControlMap();
		}

		// CAH added line below because the HUD should be active at the start of any level, even if it is turned off later with the consol. 
		// this is in responce to the global variable g_bHudWasEnabled added to AVP_GUIMenu_System that annoyingly means if you quit a level it will never allow the HUD to be seen again. 
		// but it has to be kept apparently becuase it has its uses for Command consol debugging. 
		Asura_Render::SetSystemEnabled( ASURA_RENDER_SYSTEM_HUD, true );

		// Set this guid to ignore for occlusion checks
		Asura_Audio_System::SetGUIDToIgnoreForOcclusion( m_uPuppetGuid );
	}
	else
	{
		if (!m_xExternalCamera.HasControlMap())
		{
			m_xExternalCamera.CreateControlMap(Guid);
		}

		if (AVP_Settings::IsMultiPlayer())
		{
			SetRespawnTimer( AVP_Multiplayer_GameRules::GetRespawnDelay() ); 
			AVP_Multiplayer_GameMessageSystem::ShowRespawnTimer(); 
		}

		Asura_Audio_System::SetGUIDToIgnoreForOcclusion( ASURA_GUID_SERVER );
	}
}

AVP_Settings::Species AVP_Player::GetPuppetSpecies() const
{
	const AVP_ClientEntity_PlayerInfo* pxPI = AVP_ClientEntity_PlayerInfo::FindEntity( GetGuid() );
	Asura_Assertf( pxPI, ( "Unable to find playerinfo with guid %d", GetGuid() ) );
	if ( pxPI )
	{
		return pxPI->GetPuppetSpecies();
	}
	else
	{
		return AVP_Settings::AVP_SPECIES_UNKNOWN;
	}
}

AVP_Settings::Team AVP_Player::GetMultiplayerTeam() const
{
	AVP_Settings::Team eTeam = AVP_Settings::AVP_TEAM_NONE; 
	AVP_ClientEntity_PlayerInfo* pxPlayerInfo = AVP_ClientEntity_PlayerInfo::FindEntity( GetGuid() ); 
	if (pxPlayerInfo)
	{
		eTeam = pxPlayerInfo->GetMultiplayerTeam(); 
	}
	else
	{
		Asura_Assert( false, "This player doesn't have player info associated in this client" );
	}
	return eTeam; 
}

bool AVP_Player::HandleMessage(const Asura_Message *pxMessage)
{
	if (!pxMessage) { return false; }

	// Is this something we handle?
	// Or is this a messsage type we need to send to the actor? Being more selective in this class as it's going to be sent more non-actor messages than the PlayerInfo class is.
	switch (pxMessage->GetMsgID())
	{
	case AVP_MSGID_SETPUPPETGUID:
		{
			const AVP_Message_SetPuppetGuid* pxData = static_cast<const AVP_Message_SetPuppetGuid*>(pxMessage->GetData());
			if (pxData)
			{
				SetPuppetGuid(pxData->GetPuppetGuid());
			}
		}
		return true;

	default:
		break;
	}

	// Finally, check our parent.
	return PARENT::HandleMessage(pxMessage);
}

void AVP_Player::SendActorCreateMessage(bool bRespawn)
{
	AVP_Message_CreatePlayerActor xData;
	xData.m_uRequestedSpecies		= AVP_Game_Core::GetRespawnSpecies();
	xData.m_uRequestedMultiplayerTeam = AVP_Settings::GetRequestedMultiplayerTeam(); 
	xData.m_bRespawn				= bRespawn;
	if (AVP_Settings::IsSinglePlayer())
	{
		// Client is authoritative about the player species in single player games
		AVP_Settings::SetPlayerSpecies(static_cast<AVP_Settings::Species>(xData.m_uRequestedSpecies));
	}
	Asura_Message* pxMessage = Asura_Network::CreateMessage(AVP_MSGID_CREATEPLAYERACTOR, sizeof(AVP_Message_CreatePlayerActor), Classification, &xData);

	Send(pxMessage, ASURA_GUID_SERVER);
}

AVP_Player* AVP_Player::Create()
{
	AVP_Player* pxNewPlayer = new AVP_Player();

	Asura_Assert(pxNewPlayer, "Failed to create player entity");

	return pxNewPlayer;
}

void AVP_Player::UpdateAutoCenter(AVP_ClientEntity_Actor* pxActor)
{
	if ( pxActor != NULL )
	{
		if ( pxActor->IsIdleForAutoCenter() )
		{
			m_fIdleTimer += Asura_Timers::GetGameTimeSlice();
		}
		else
		{
			m_fIdleTimer = 0.0f;
		}

		if ( AVP_Settings::GetCurrentProfile()->GetControls().GetAutoCenter() && m_fIdleTimer > dvs_fResetLookIdleTime )
		{
			const Asura_Matrix_3x3& xOri = pxActor->GetCurrentOrientation();
			Asura_AVector3 xAimDir;
			Asura_AVector3 xForwards( xOri.e13, xOri.e23, xOri.e33 );
			pxActor->GetAimDirection( xAimDir );
			Asura_Quat xRot;
			xRot.RotationFromAToB( xAimDir, xForwards );
			Asura_AVector3 xAxis;
			float fAng = xRot.ToRotation( xAxis );
			if ( fAng < 0.0f )
			{
				xAxis *= -1.0f;
				fAng *= -1.0f;
			}

			const float fMaxAng = dvs_fResetLookSpeed * Asura_Timers::GetGameTimeSlice();
			if ( fAng > fMaxAng )
			{
				xRot.FromRotation( fMaxAng, xAxis );
			}
			xAimDir = xRot.RotateVector( xAimDir);
			
			static_cast< AVP_Vertebrate_InputHandler* >( pxActor->GetInputHandler() )->SetNewLookDirection( *pxActor, xAimDir, true );
		}
	}
}

void AVP_Player::UpdateCommandPostCapture(AVP_ClientEntity_Actor* pxActor)
{
	// Disruption domination multiplayer match?
	if (pxActor != NULL &&
		AVP_Settings::IsMultiPlayer() &&
		AVP_Multiplayer_GameRules::IsDominationBasedGame())
	{
		// Check if this player is close enough to a command post to capture
		Asura_Hashed_List<AVP_ClientEntity_CommandPost>* pxCommandPostList = AVP_ClientEntity_CommandPost::GetList();
		Asura_Hashed_List_ConstIt<AVP_ClientEntity_CommandPost> xCommandPostListConstIt( pxCommandPostList ); 
		AVP_Multiplayer_GameMessageSystem::HideCaptureInProgress(); 
		while(!xCommandPostListConstIt.Done())
		{
			const AVP_ClientEntity_CommandPost* pxCurrentCommandPost = xCommandPostListConstIt.GetCurrent();
			if (pxCurrentCommandPost &&
				pxCurrentCommandPost->IsEntityActive() &&
				!pxCurrentCommandPost->IsCaptureDisabled())
			{
				Asura_Vector_3 xDistanceToCP( pxCurrentCommandPost->GetCurrentPosition() - pxActor->GetCurrentPosition() ); 
				float fDistanceToCP = xDistanceToCP.Magnitude(); 
				// Are we close enough to capture this CP?
				if (fDistanceToCP < AVP_Multiplayer_GameRules::GetDDOMCaptureRadius() &&
					(pxCurrentCommandPost->IsContested() || pxCurrentCommandPost->GetCaptureTimer() < pxCurrentCommandPost->GetCaptureTime()))
				{
					AVP_Multiplayer_GameMessageSystem::ShowCaptureInProgress(); 
					break; 
				}
			}
			xCommandPostListConstIt.Next(); 
		}
	}
}

/// End of file. ///
