//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_AVP3Alien_KeepDistance.cpp
//
// Copyright (C) 2007 Rebellion. All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

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

// header
#include "Behaviour/BehaviourTypes/AVP3Alien/AVP_Behaviour_AVP3Alien_KeepDistance.h"

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

// axon
#include "Behaviour/BehaviourTypes/AVP_Behaviour_Types.h"
#include "Behaviour/BehaviourTypes/AVP3Alien/AVP_Behaviour_AVP3Alien_GetToMovingTarget.h"
#include "Behaviour/BehaviourTypes/AVP3Alien/AVP_Behaviour_AVP3Alien_Movement.h"
#include "Core/BrainTypes/Axon_Project_Brain.h"
#include "Knowledge/AVP_Knowledge_System.h"

// avp
#include "Entity/PhysicalObject/AVP_ServerEntity_PhysicalObject.h"
#include "Entity/PhysicalObject/Actor/AVP_ServerEntity_Actor.h"


//////////////////////////////////////////////////////////////////////////////
//
// AVP_Behaviour_AVP3Alien_KeepDistance
//
//////////////////////////////////////////////////////////////////////////////

const float AVP_Behaviour_AVP3Alien_KeepDistance::fDEFAULT_TOLERANCE = 3.0f;

AVP_Behaviour_AVP3Alien_KeepDistance::AVP_Behaviour_AVP3Alien_KeepDistance( Axon_Project_Brain* pxBrain )
: PARENT( pxBrain, AXON_BEHAVIOUR_TYPE_AVP3ALIEN_KEEP_DISTANCE )
, m_uTargetGuid( ASURA_GUID_UNREGISTERED )
, m_xTargetPosition( Asura_Maths::ZeroVector3 )
, m_fFollowDist( 0.0f )
, m_fTolerance( fDEFAULT_TOLERANCE )
{
}


bool AVP_Behaviour_AVP3Alien_KeepDistance::Activate()
{
    return true; // PARENT::Activate();
}


void AVP_Behaviour_AVP3Alien_KeepDistance::Deactivate()
{
    // Destroy any children.
    Axon_Behaviour* pxChildBehaviour = GetChild();
    while ( pxChildBehaviour )
    {
        UnlinkChild( pxChildBehaviour );
        GetProjectBrain()->DestroyBehaviour( pxChildBehaviour );
        pxChildBehaviour = GetChild();
    }

    PARENT::Deactivate();
}


void AVP_Behaviour_AVP3Alien_KeepDistance::Update( const float fTimeslice )
{
//	PARENT::Update( fTimeslice );

	if( ( !GetProjectBrain() ) || ( !GetProjectBrain()->GetActor() ) )
    {
        SetFinished();
    }

	if ( IsFinished() || IsInError() ) { return; }

	const float fFollowDistSq = m_fFollowDist * m_fFollowDist;
	const float fToleranceSq = m_fTolerance * m_fTolerance;
	const float fSquareAddition = 2.0f * m_fFollowDist * m_fTolerance;

	const float fDistanceToTargetSq = ( m_xTargetPosition - GetProjectBrain()->GetActor()->GetPosition() ).MagnitudeSquared();

	const bool bTooClose = fDistanceToTargetSq < ( fFollowDistSq - fSquareAddition + fToleranceSq );
	const bool bCloseEnough = fDistanceToTargetSq <= fFollowDistSq;

	const bool bTooFar = fDistanceToTargetSq > ( fFollowDistSq + fSquareAddition + fToleranceSq );
	const bool bFarEnough = fDistanceToTargetSq >= fFollowDistSq;

	if( ( fFollowDistSq <= 0.0f ) || ( m_uTargetGuid == ASURA_GUID_UNREGISTERED ) )
	{
		SwitchToState( STATE_IDLE );
		return;
	}

	switch( GetCurrentState() )
	{
		case STATE_IDLE:
		{
			if( bTooClose )
			{
				SwitchToState( STATE_GET_FARTHER );
			}
			else
			if( bTooFar )
			{
				SwitchToState( STATE_GO_CLOSER );
			}
			else
			{
				SwitchToState( STATE_ORIENT_TO );
			}
			break;
		}

		case STATE_GO_CLOSER:
		{
			if( bTooClose )
			{
				SwitchToState( STATE_GET_FARTHER );
			}
			else
			if( !bCloseEnough )
			{
                AVP_Behaviour_AVP3Alien_GetToMovingTarget* pxGTMTChild = static_cast< AVP_Behaviour_AVP3Alien_GetToMovingTarget* >( GetChildWithType( this, AXON_BEHAVIOUR_TYPE_AVP3ALIEN_GET_TO_MOVING_TARGET ) );
                if ( !pxGTMTChild || pxGTMTChild->IsInError() )
                {
					// Something went wrong
                    SwitchToState( STATE_IDLE );
                }
				else
				if( pxGTMTChild->IsFinished() )
				{
					SwitchToState( STATE_ORIENT_TO );
				}
			}
			else
			{
				SwitchToState( STATE_ORIENT_TO );
			}
			break;
		}

		case STATE_ORIENT_TO:
		{
			if( bTooClose )
			{
				SwitchToState( STATE_GET_FARTHER );
			}
			else
			if( bTooFar )
			{
				SwitchToState( STATE_GO_CLOSER );
			}
			// Orientation updated each frame
			break;
		}

		case STATE_GET_FARTHER:
		{
			if( bTooFar )
			{
				SwitchToState( STATE_GO_CLOSER );
			}
			else
			if( !bFarEnough )
			{
	            AVP_Behaviour_AVP3Alien_Movement* pxMChild = static_cast<AVP_Behaviour_AVP3Alien_Movement*>( GetChildWithType( this, AXON_BEHAVIOUR_TYPE_AVP3ALIEN_MOVEMENT ) );
                if ( !pxMChild || pxMChild->IsInError() )
                {
					// Something went wrong
                    SwitchToState( STATE_IDLE );
                }
				else
				if( pxMChild->IsFinished() )
				{
					SwitchToState( STATE_ORIENT_TO );
				}
			}
			else
			{
				SwitchToState( STATE_ORIENT_TO );
			}
			break;
		}

		default:
        {
			Asura_Assert( 0, "AVP_Behaviour_AVP3Alien_KeepDistance::Update - invalid state" );
			SwitchToState( STATE_IDLE );
        }
	}
}


void AVP_Behaviour_AVP3Alien_KeepDistance::PerFrameUpdate( float fTimeslice )
{
//	PARENT::PerFrameUpdate( fTimeslice );

	UpdateStateTimer( fTimeslice );

	if( ( !GetProjectBrain() ) || ( !GetProjectBrain()->GetActor() ) )
    {
        SetFinished();
    }

    if ( IsFinished() || IsInError() ) { return; }

	if( m_uTargetGuid != ASURA_GUID_UNREGISTERED )
	{
		const AVP_ServerEntity_PhysicalObject* const pxPhysicalObjectTarget = AVP_ServerEntity_PhysicalObject::FindEntity( m_uTargetGuid );
		if ( !pxPhysicalObjectTarget )
		{
			Asura_Assertf( 0, ( "Can't find physical object with Guid:%u", m_uTargetGuid ) );
			m_uTargetGuid = ASURA_GUID_UNREGISTERED;
			SetInError();
			return;
		}

		m_xTargetPosition = pxPhysicalObjectTarget->GetPosition();

		if( GetCurrentState() == STATE_ORIENT_TO )
		{
			// Turn to face the threat.
			OrientToPosition( &m_xTargetPosition, fTimeslice );
		}
	}
}


void AVP_Behaviour_AVP3Alien_KeepDistance::SetTargetGuid( Asura_Guid uTargetGuid )
{
	if( uTargetGuid != m_uTargetGuid )
	{
		m_uTargetGuid = uTargetGuid;
		SwitchToState( STATE_IDLE );
	}
}


#ifndef ASURA_NO_DEBUG_OUTPUT

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

	static const char* aszStateStrings[] =
	{
		"IDLE",
		"APPROACH",
		"ORIENT_TO_THREAT",
		"GET_AWAY"
	};

	static const u_int auStateDebugColour[] =
	{
		0xFFD8D8D8, // STATE_IDLE				- Gray
		0xFF00D8FF, // STATE_GO_CLOSER			- Light Blue
		0xFF08FF08, // STATE_ORIENT_TO			- Green
		0xFFFFFF08	// STATE_GET_FARTHER		- Yellow
	};

	Asura_Font_System::Print( m_pxBrain->GetDebugOutputPos().x, m_pxBrain->GetDebugOutputPos().y, 0.5f, auStateDebugColour[ GetCurrentState() ],
		ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING, "KeepDistance State: %s", aszStateStrings[ GetCurrentState() ] );
    m_pxBrain->GetDebugOutputPos().y += 12.0f;

	Asura_Font_System::Print( m_pxBrain->GetDebugOutputPos().x, m_pxBrain->GetDebugOutputPos().y, 0.5f, 0xFFFFFFFF,
		ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING, "Target: %u", m_uTargetGuid );
    m_pxBrain->GetDebugOutputPos().y += 12.0f;

	if( m_uTargetGuid != ASURA_GUID_UNREGISTERED )
	{
		const float fTargetDist = ( GetProjectBrain()->GetActor()->GetPosition() - m_xTargetPosition ).Magnitude();

		Asura_Font_System::Print( m_pxBrain->GetDebugOutputPos().x, m_pxBrain->GetDebugOutputPos().y, 0.5f, 0xFFFFFFFF,
			ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING, "Target Distance: %f", fTargetDist );
		m_pxBrain->GetDebugOutputPos().y += 12.0f;
	}
}

#endif

//////////////////////////////////////////////////////////////////////////////
//
// private
//
//////////////////////////////////////////////////////////////////////////////

// TODO: Optimise
void AVP_Behaviour_AVP3Alien_KeepDistance::SwitchToState( int iNextState )
{
	// Note: GetProjectBrain() already tested for NULL

	if( GetCurrentState() == iNextState )
	{
		return;
	}

	// Do any work we need to exit our current state.
    switch ( GetCurrentState() )
    {
        case STATE_IDLE:
        {
			// Nothing to do
            break;
        }

        case STATE_GO_CLOSER:
        {
			DestroyChildWithType( AXON_BEHAVIOUR_TYPE_AVP3ALIEN_GET_TO_MOVING_TARGET );
            break;
        }

        case STATE_ORIENT_TO:
        {
			// Nothing to do
            break;
        }

        case STATE_GET_FARTHER:
        {
			DestroyChildWithType( AXON_BEHAVIOUR_TYPE_AVP3ALIEN_MOVEMENT );
            break;
        }

		default:
        {
			Asura_Assert( 0, "AVP_Behaviour_AVP3Alien_KeepDistance::SwitchToState - invalid prev state" );
        }
	}

	// Do any work we need to change into our next state.
    SetCurrentState( iNextState );
	bool bFailed = false;

	switch ( GetCurrentState() )
    {
        case STATE_IDLE:
        {
			// Nothing to do
            break;
        }

        case STATE_GO_CLOSER:
        {
			if( m_uTargetGuid == ASURA_GUID_UNREGISTERED )
			{
				bFailed = true;
			}
			else
			{
				AVP_Behaviour_AVP3Alien_GetToMovingTarget* pxGTMTChild = static_cast< AVP_Behaviour_AVP3Alien_GetToMovingTarget* >( CreateChildWithType( AXON_BEHAVIOUR_TYPE_AVP3ALIEN_GET_TO_MOVING_TARGET ) );
				if ( !pxGTMTChild )
				{
					bFailed = true;
				}
				else
				{
					// We want to move slowly in
					GetProjectBrain()->GetNavData().m_eMovementMode = AVP_Navigation::MM_SLOWEST;
					if( !pxGTMTChild->SetTargetGuid( m_uTargetGuid ) )
					{
						bFailed = true;
					}
				}
			}
			break;
        }

        case STATE_ORIENT_TO:
        {
			if( m_uTargetGuid == ASURA_GUID_UNREGISTERED )
			{
				bFailed = true;
			}
            break;
        }

        case STATE_GET_FARTHER:
        {
			if( m_uTargetGuid == ASURA_GUID_UNREGISTERED )
			{
				bFailed = true;
			}
			else
			{
				AVP_Behaviour_AVP3Alien_Movement* pxMChild = static_cast< AVP_Behaviour_AVP3Alien_Movement* >( CreateChildWithType( AXON_BEHAVIOUR_TYPE_AVP3ALIEN_MOVEMENT ) );
				if ( !pxMChild )
				{
					bFailed = true;
				}
				else
				{
					const Asura_Vector_3& xThreatPosition = GetProjectBrain()->GetKnowledgeSystem()->GetBestThreatKnowledge()->m_xPosition;

					// We want to run away.
					GetProjectBrain()->GetNavData().m_eMovementMode = AVP_Navigation::MM_FAST;
					if ( !pxMChild->GetAwayFromPosition( xThreatPosition, m_fFollowDist ) )
					{
						bFailed = true;
					}
				}
			}
            break;
        }

		default:
        {
			Asura_Assert( 0, "AVP_Behaviour_AVP3Alien_KeepDistance::SwitchToState - invalid next state" );
			iNextState = STATE_IDLE;
        }
    }

	if( bFailed )
	{
		SwitchToState( STATE_IDLE );
	}
}
