//--------------------------------------------------------------------------------------
//	File	:	AVP_ServerEntity_MotionTrackerBlip.cpp
//	Author	:	Gareth Thomas
//	Created	:	18 / 12 / 2007
//	Copyright (C) 2006 Rebellion, All Rights Reserved.
//--------------------------------------------------------------------------------------

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

// Header
#include "Entity/MotionTrackerBlip/AVP_ServerEntity_MotionTrackerBlip.h"

#ifdef ASURA_TOOLS
#include "EntityTemplate/Tools_EntityTemplate_System.h"
#endif

#ifndef ASURA_NO_DEBUG_OUTPUT
#include "Debugging/Asura_DebugLineRender.h"
#include "Camera/Asura_Camera.h"
#include "Interface/Asura_CommandConsole.h"
#include "Rendering/Asura_Font_System.h"
#endif

// Asura
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_Timers.h"

// AVP
#include "Entity/AVP_Entity_Classification.h"
#include "Entity/Locations/OrientedNode/GamesceneNode/AVP_ServerNode_GS2Node.h"
#include "Entity/Locations/Spline/GamesceneSpline/AVP_ServerNode_GS2Spline.h"

//------------------------------------------------------------------------------
// Constants, Defines, Statics and Globals
//------------------------------------------------------------------------------
Asura_Hashed_List<AVP_ServerEntity_MotionTrackerBlip> AVP_ServerEntity_MotionTrackerBlip::s_xList;

DVSVAR bool dvs_bRenderMTBDebugInfo = false;

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
AVP_ServerEntity_MotionTrackerBlip::AVP_ServerEntity_MotionTrackerBlip(const Asura_Guid uGuid)
: PARENT(uGuid)
, m_uStartGS2NodeGuid( ASURA_GUID_UNREGISTERED )
, m_uCurrentNodeGuid( ASURA_GUID_UNREGISTERED )
, m_fDistanceToTraveled( 0.0f )
, m_eUpdateState( UNSET )
, m_xPreviousPosition( Asura_Maths::ZeroVector3 )
, m_fTime( 0.0f )
, m_fVelocity( 0.0f )
#ifndef ASURA_NO_DEBUG_OUTPUT
, m_fProportion( 0.0f )
#endif
{
	Classification = AVP_ENTITYCLASS_MOTIONTRACKERBLIP;

	SetNeverUpdateClient();
	SetNeverUpdates( false );

	s_xList.AddToList(this);

#ifndef ASURA_NO_DEBUG_OUTPUT
#ifdef ASURA_DEBUG_VARIABLE_SYSTEM
	if ( !Asura_CommandConsole::VarExists( L"AVP.MTB.RenderDebugInfo" ) )
	{
		Asura_CommandConsole::AddVar( L"AVP.MTB.RenderDebugInfo", &dvs_bRenderMTBDebugInfo );
	}
#endif // ASURA_DEBUG_VARIABLE_SYSTEM
#endif // ASURA_NO_DEBUG_OUTPUT

}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
AVP_ServerEntity_MotionTrackerBlip::~AVP_ServerEntity_MotionTrackerBlip(void)
{
	s_xList.RemoveFromList(this);
}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
void AVP_ServerEntity_MotionTrackerBlip::PostLoadInitialise(void)
{
	PARENT::PostLoadInitialise();

	if ( GetUpdateState() == UNSET )
	{
		SetUpdateState( BEGIN );
	}
}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
void AVP_ServerEntity_MotionTrackerBlip::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	//Version
	xStream << CURRENT_CHUNK_VERSION;

	//Parent
	PARENT::WriteToChunkStream(xStream);

	xStream << m_uStartGS2NodeGuid;
	xStream << m_uCurrentNodeGuid;
	xStream << m_uCurrentSplineGuid;
	xStream << m_fDistanceToTraveled;

	u_int uUpdateState = (u_int)m_eUpdateState;
	xStream << uUpdateState;

	xStream << m_fTime;
}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
void AVP_ServerEntity_MotionTrackerBlip::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	//Version
	u_int uVersion;
	xStream	>> uVersion;

	//Parent
	PARENT::ReadFromChunkStream(xStream);

	xStream >> m_uStartGS2NodeGuid;
	xStream >> m_uCurrentNodeGuid;
	xStream >> m_uCurrentSplineGuid;
	xStream >> m_fDistanceToTraveled;

	u_int uUpdateState = 0;
	xStream >> uUpdateState;
	m_eUpdateState = (UpdateState)uUpdateState;

	xStream >> m_fTime;
}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
void AVP_ServerEntity_MotionTrackerBlip::Update( void )
{
	PARENT::Update();

	if ( IsEntityActive() )
	{
		switch ( GetUpdateState() )
		{
			case BEGIN:
			{
				Asura_Assert(m_uStartGS2NodeGuid != ASURA_GUID_UNREGISTERED, "AVP_ServerEntity_MotionTrackerBlip : Update - Start Node Is Invalid!" );

				m_uCurrentNodeGuid = m_uStartGS2NodeGuid;

				AVP_ServerNode_GS2Node* pxStartNode = AVP_ServerNode_GS2Node::FindEntity( m_uCurrentNodeGuid );
				if (!pxStartNode)
				{
					Asura_Assertf(false, ("Motion tracker with guid %d has no start node set", GetGuid()));
					SwitchToState(WAITING);
					return;
				}

				SetPosition( pxStartNode->GetPosition() );
				m_xPreviousPosition = GetPosition();

				SwitchToState( ON_LINK );
				
			}break;

			case ON_LINK:
			{
				AVP_ServerNode_GS2Spline* pxGS2Spline = (AVP_ServerNode_GS2Spline*)AVP_ServerNode_GS2Spline::FindEntity( m_uCurrentSplineGuid );

				if ( pxGS2Spline )
				{
					m_fTime					+= Asura_Timers::GetFrameTime();
					m_fVelocity				= pxGS2Spline->GetSpeedFromTime( m_fTime );
					m_fDistanceToTraveled	+= Asura_Timers::GetFrameTime() * m_fVelocity * Asura_Timers::GetGameTimeScale();

					Asura_Vector_3 xNewPos( Asura_Maths::ZeroVector3 );
					Asura_Vector_3 xEndPos( Asura_Maths::ZeroVector3 );

					pxGS2Spline->GetPositionFromDistance(pxGS2Spline->GetTotalLength(), xEndPos );
					pxGS2Spline->GetPositionFromDistance(m_fDistanceToTraveled, xNewPos );

					m_xPreviousPosition = GetPosition();
					SetPosition( xNewPos );

					if ( xEndPos == xNewPos )
					{
						SwitchToState( AT_NODE );
					}
				}

			}break;

			case AT_NODE:
			{
				SwitchToState( ON_LINK );

			}break;

			case WAITING:
			{

			}break;

			case UNSET:
			default:
			{
				
			}break;
		}
	}

#ifndef ASURA_NO_DEBUG_OUTPUT
	if ( dvs_bRenderMTBDebugInfo )
	{
		RenderDebugInfo();
	}
#endif
}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
#ifndef ASURA_NO_DEBUG_OUTPUT
static const char* s_aszMTBSTrings[AVP_ServerEntity_MotionTrackerBlip::MAX_STATES] = 
{
	"MTB - UNSET",
	"MTB - BEGIN",
	"MTB - ON_LINK",
	"MTB - AT_NODE",
	"MTB - WAITING",
};

void AVP_ServerEntity_MotionTrackerBlip::RenderDebugInfo()
{
	Asura_Vector_3 xOutputPos = GetPosition();

	if (!Asura_Camera::ProjectWorldCoordsToScreenSpace( xOutputPos, true ) || xOutputPos.z <= 0.0f)
	{
		return;
	}

	u_int uFlags = ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING;
	u_int uColour = 0xffffffff;

	xOutputPos.y += 10.0f;

	Asura_Font_System::Print( xOutputPos.x, xOutputPos.y, 0.5f, uColour, uFlags, "GUID: %u", GetGuid() );
	xOutputPos.y += 10.0f;

	Asura_Font_System::Print( xOutputPos.x, xOutputPos.y, 0.5f, uColour, ASURA_TEXTFORMAT_CENTREJUSTIFIED, s_aszMTBSTrings[ GetUpdateState() ] );
	xOutputPos.y += 10.0f;

	AVP_ServerNode_GS2Spline* pxGS2Spline = (AVP_ServerNode_GS2Spline*)AVP_ServerNode_GS2Spline::FindEntity( m_uCurrentSplineGuid );

	if ( pxGS2Spline )
	{
		m_fProportion = m_fDistanceToTraveled / pxGS2Spline->GetTotalLength();
		Asura_Font_System::Print( xOutputPos.x, xOutputPos.y, 0.5f, uColour, uFlags, "Distance Along Spline  %0.2f %", m_fProportion * 100.0f );
		xOutputPos.y += 10.0f;
	}

	Asura_Font_System::Print( xOutputPos.x, xOutputPos.y, 0.5f, uColour, uFlags, "Time on Spline %0.2f", m_fTime );
	xOutputPos.y += 10.0f;
	Asura_Font_System::Print( xOutputPos.x, xOutputPos.y, 0.5f, uColour, uFlags, "Current Velocity %0.2f", m_fVelocity );
	xOutputPos.y += 10.0f;

	Asura_DebugLineRender::AddSphere( GetPosition(), 0.125f, 0xff99ff55, 4, 4 );
}
#endif

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
void AVP_ServerEntity_MotionTrackerBlip::SwitchToState( UpdateState eState )
{
	switch ( GetUpdateState() )
	{
		case BEGIN:
		{
		}break;

		case ON_LINK:
		{
#ifndef ASURA_NO_DEBUG_OUTPUT
			m_fProportion = 0.0f;
#endif
			m_fTime = 0.0f;
			m_fVelocity = 0.0f;

		}break;

		case AT_NODE:
		{
		}break;

		case WAITING:
		{
		}break;

		case UNSET:
		default:
		{
		}break;
	}

	SetUpdateState( eState );

	switch ( GetUpdateState() )
	{
		case BEGIN:
		{
		}break;

		case ON_LINK:
		{
#ifndef ASURA_NO_DEBUG_OUTPUT
			m_fProportion = 0.0f;
#endif
			m_fTime = 0.0f;
			m_fVelocity = 0.0f;

			Asura_Guid uSplineGuid = ASURA_GUID_UNREGISTERED;

			ChooseLinkToTake( m_uCurrentNodeGuid, uSplineGuid );

			if ( uSplineGuid != ASURA_GUID_UNREGISTERED )
			{
				m_uCurrentSplineGuid = uSplineGuid;
				m_fDistanceToTraveled = 0.0f;
			}
			else
			{
				SwitchToState( WAITING );
			}

		}break;

		case AT_NODE:
		{
			AVP_ServerNode_GS2Spline* pxGS2Spline = (AVP_ServerNode_GS2Spline*)AVP_ServerNode_GS2Spline::FindEntity( m_uCurrentSplineGuid );

			if ( pxGS2Spline )
			{
				m_uCurrentNodeGuid = pxGS2Spline->GetTargetGuid();
				return;
			}

			SwitchToState( WAITING );

		}break;

		case WAITING:
		{
			SetEntityActive( false );
			SetUpdateState( BEGIN );

		}break;

		case UNSET:
		default:
		{
		}break;
	}
}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
void AVP_ServerEntity_MotionTrackerBlip::ChooseLinkToTake( const Asura_Guid uSourceNodeGuid, Asura_Guid& uLinkGuid )
{
	uLinkGuid = ASURA_GUID_UNREGISTERED;

	Asura_ServerNode_GS2Node* pxSourceNode = Asura_ServerNode_GS2Node::FindEntity( uSourceNodeGuid );

	if ( !pxSourceNode )
	{
		return;
	}
	
	if ( pxSourceNode->GetNumberLinks() == 0 )
	{
		return;
	}

	// Calculate this for the following algorithms.
	u_int uNumActiveLinks = 0;

	for ( u_int u = 0; u < pxSourceNode->GetNumberLinks(); ++u )
	{
		const Asura_ServerNode_GS2Spline* pxLink = Asura_ServerNode_GS2Spline::FindEntity( pxSourceNode->GetLink( u ) );

		if ( pxLink && pxLink->IsEntityActive() )
		{
			++uNumActiveLinks;
		}
	}

	if ( uNumActiveLinks == 0 )
	{
		return;
	}

	if ( pxSourceNode->ShouldUseLinkWeights() )
	{
		// Use link weights to choose.
		float fTotalWeighting = 0.0f;

		for ( u_int u = 0; u < pxSourceNode->GetNumberLinks(); ++u )
		{
			const Asura_ServerNode_GS2Spline* pxLink = Asura_ServerNode_GS2Spline::FindEntity( pxSourceNode->GetLink( u ) );

			if ( pxLink && pxLink->IsEntityActive() )
			{
				fTotalWeighting += pxSourceNode->GetLinkWeight( u );
			}
		}

		if ( fTotalWeighting > 0.0f )
		{
			const float fRandomWeight = Asura_Random::GetFloat( 0.0f, fTotalWeighting );

			float fCumulativeWeighting = 0.0f;
			
			for ( u_int u = 0; u < pxSourceNode->GetNumberLinks(); ++u )
			{
				const Asura_ServerNode_GS2Spline* pxLink = Asura_ServerNode_GS2Spline::FindEntity( pxSourceNode->GetLink( u ) );

				if ( pxLink && pxLink->IsEntityActive() )
				{
					fCumulativeWeighting += pxSourceNode->GetLinkWeight( u );

					if ( fRandomWeight <= fCumulativeWeighting )
					{
						uLinkGuid = pxLink->GetGuid();
						return;
					}
				}
			}
		}
	}

	// Pick a random active link.
	const u_int uRandomLink = Asura_Random::GetIntU( 0, uNumActiveLinks - 1 );

	uNumActiveLinks = 0;

	for ( u_int u = 0; u < pxSourceNode->GetNumberLinks(); ++u )
	{
		const Asura_ServerNode_GS2Spline* pxLink = Asura_ServerNode_GS2Spline::FindEntity( pxSourceNode->GetLink( u ) );
		if ( pxLink && pxLink->IsEntityActive() )
		{
			if ( uNumActiveLinks == uRandomLink )
			{
				uLinkGuid = pxLink->GetGuid();
				return;
			}

			++uNumActiveLinks;
		}
	}
}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
AVP_ServerEntity_MotionTrackerBlip* AVP_ServerEntity_MotionTrackerBlip::Create(const Asura_Guid uGuid)
{
	return(new AVP_ServerEntity_MotionTrackerBlip(uGuid));
}

#ifdef ASURA_TOOLS
void AVP_ServerEntity_MotionTrackerBlip::ApplyChangesForTemplateInstance( Tools_EntityTemplateInstance* pxInstance )
{
	PARENT::ApplyChangesForTemplateInstance( pxInstance );

	m_uStartGS2NodeGuid = pxInstance->ConvertGuid( m_uStartGS2NodeGuid );
}
#endif
