//////////////////////////////////////////////////////////////////////////////
//
// Axon_Behaviour_GS2Attractor.cpp
//
// Copyright (C) 2008 Rebellion. All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

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

#ifdef ASURA_USE_AXON

// header
#include "Behaviour/BehaviourTypes/Axon_Behaviour_GS2Attractor.h"

// asura
#include "AnimTemplates/Axon_Gamescene_AnimTemplate_System.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Entity/Locations/OrientedNode/GamesceneNode/AttractorNode/Asura_ServerNode_GS2Attractor.h"

// axon
#include "Behaviour/BehaviourParamsTypes/Axon_BehaviourParams_GS2AttractorEnRoute.h"
#include "Behaviour/BehaviourTypes/Axon_Behaviour_GS2Actor.h"
#include "Behaviour/BehaviourTypes/Axon_Behaviour_Types.h"
#include "Core/Axon_Brain.h"

//////////////////////////////////////////////////////////////////////////////
//
// Axon_Behaviour_GS2AttractorEnRoute
//
//////////////////////////////////////////////////////////////////////////////

Axon_Behaviour_GS2AttractorEnRoute::Axon_Behaviour_GS2AttractorEnRoute( Axon_Brain* pxBrain )
: PARENT( AXON_BEHAVIOUR_TYPE_GS2_ATTRACTOR_EN_ROUTE, pxBrain )
, m_iState( UNSET )
, m_uAnimSet( uAXON_ANIMATION_SEQUENCE_ID_NONE )
, m_fArrivalRange( 0.0f )
, m_xTargetPosition( Asura_Maths::ZeroVector3 )
, m_pxGetToBehaviour( NULL )
{
}

void Axon_Behaviour_GS2AttractorEnRoute::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
	u_int uVersion;
	xStream >> uVersion;

	PARENT::ReadFromChunkStream( xStream );

	if ( uVersion == uCURRENT_CHUNK_VERSION )
	{
		xStream >> m_iState;
		xStream >> m_uAnimSet;
		xStream >> m_fArrivalRange;
		xStream >> m_xTargetPosition;

		bool bChunkedGetToBehaviour;
		xStream >> bChunkedGetToBehaviour;
		if ( bChunkedGetToBehaviour )
		{
			m_pxGetToBehaviour = GetGSActorBehaviour()->CreateGetToBehaviour();
			if ( m_pxGetToBehaviour )
			{
				LinkChild( m_pxGetToBehaviour );
				m_pxGetToBehaviour->ReadFromChunkStream( xStream );
			}
			else
			{
				Asura_Assert( false, "Axon_Behaviour_GS2AttractorEnRoute::ReadFromChunkStream - couldn't create get to behaviour" );
				SetInError();
			}
		}
	}
	else if ( uVersion == 0 )
	{
		xStream >> m_iState;
		float fTimeInState;
		xStream >> fTimeInState;
		xStream >> m_uAnimSet;
		xStream >> m_fArrivalRange;
		xStream >> m_xTargetPosition;
		Asura_Quat     xInitialOriQ;
		Asura_Vector_3 xInitialPos;
		xStream >> xInitialOriQ;
		xStream >> xInitialPos;

		bool bChunkedGetToBehaviour;
		xStream >> bChunkedGetToBehaviour;
		if ( bChunkedGetToBehaviour )
		{
			m_pxGetToBehaviour = GetGSActorBehaviour()->CreateGetToBehaviour();
			if ( m_pxGetToBehaviour )
			{
				LinkChild( m_pxGetToBehaviour );
				m_pxGetToBehaviour->ReadFromChunkStream( xStream );
			}
			else
			{
				Asura_Assert( false, "Axon_Behaviour_GS2AttractorEnRoute::ReadFromChunkStream - couldn't create get to behaviour" );
				SetInError();
			}
		}
	}
	else
	{
		Asura_Assertf( false, ( "Axon_Behaviour_GS2AttractorEnRoute::ReadFromChunkStream - unhandled chunk version (%u)", uVersion ) );
	}
}

void Axon_Behaviour_GS2AttractorEnRoute::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	xStream << uCURRENT_CHUNK_VERSION;

	PARENT::WriteToChunkStream( xStream );

	xStream << m_iState;
	xStream << m_uAnimSet;
	xStream << m_fArrivalRange;
	xStream << m_xTargetPosition;
	if ( m_pxGetToBehaviour )
	{
		xStream << true;
		m_pxGetToBehaviour->WriteToChunkStream( xStream );
	}
	else
	{
		xStream << false;
	}
}

void Axon_Behaviour_GS2AttractorEnRoute::HandleParams( const Axon_BehaviourParams* pxParams )
{
	PARENT::HandleParams( pxParams );

	Asura_Assert( pxParams->IsBaseOrDerivativeOf( AXON_BP_TYPE_GS2_ATTRACTOR_ENROUTE ), "Axon_Behaviour_GS2AttractorEnRoute::HandleParams - unexpected params type" );
	if ( pxParams->IsBaseOrDerivativeOf( AXON_BP_TYPE_GS2_ATTRACTOR_ENROUTE ) )
	{
		const Axon_BehaviourParams_GS2AttractorEnRoute* pxAttrParams = static_cast< const Axon_BehaviourParams_GS2AttractorEnRoute* >( pxParams );
		m_uAnimSet		= pxAttrParams->m_uAnimSet;
		m_fArrivalRange = pxAttrParams->m_fArrivalRange;
	}
}

bool Axon_Behaviour_GS2AttractorEnRoute::Activate()
{
	const bool bParentOk = PARENT::Activate();

	GetGSActorBehaviour()->PlayAnimSet( m_uAnimSet );

	SwitchToState( MOVING_TO_ATTRACTOR );

	return bParentOk;
}

void Axon_Behaviour_GS2AttractorEnRoute::Deactivate()
{
	SwitchToState( FINISHED );
	PARENT::Deactivate();
}

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

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

	switch ( m_iState )
	{
		case MOVING_TO_ATTRACTOR:
		{
			if ( !m_pxGetToBehaviour || m_pxGetToBehaviour->IsInError() )
			{
				Asura_Assertf( false, ( "Axon_Behaviour_GS2AttractorEnRoute::PerFrameUpdate - actor %u could not get to attractor %u - teleporting.",
					GetBrain()->GetOwnerGuid(), GetGSActorBehaviour()->GetCurrentEntityGuid() ) );

				// We cannot give up on getting to an attractor, so we must teleport there instead.
				const Asura_ServerNode_GS2Attractor* pxAttractor =
					Asura_ServerNode_GS2Attractor::FindEntity( GetGSActorBehaviour()->GetCurrentEntityGuid() );
				if ( pxAttractor )
				{
					Asura_Quat xAttractorQOri;
					pxAttractor->GetOrientation( xAttractorQOri );

					Asura_Matrix_3x3 xAttractorMOri;
					xAttractorQOri.ToMatrix( xAttractorMOri );

					GetGSActorBehaviour()->TeleportOwnerToPosOri( m_xTargetPosition, xAttractorMOri );
					SwitchToState( FINISHED );
				} 
				else
				{
					Asura_Assert( false, "Axon_Behaviour_GS2AttractorEnRoute::PerFrameUpdate - was going to teleport, but can't find the attractor either" );
					SetInError();
					SwitchToState( FINISHED );
				}
			}
			else if ( m_pxGetToBehaviour->IsFinished() )
			{
				SwitchToState( FINISHED );
			}
			break;
		}

		case UNSET:
		case FINISHED:
		case NUM_STATES:
		default:
		{
			break;
		}
	}
}

void Axon_Behaviour_GS2AttractorEnRoute::SwitchToState( const int iState )
{
	// Exit our old state.
	switch ( m_iState )
	{
		case MOVING_TO_ATTRACTOR:
		{
			if ( m_pxGetToBehaviour )
			{
				UnlinkChild( m_pxGetToBehaviour );
				m_pxGetToBehaviour->DoDeactivate();
				GetGSActorBehaviour()->DestroyGetToBehaviour( m_pxGetToBehaviour );
				m_pxGetToBehaviour = NULL;
			}

			GetGSActorBehaviour()->SetOwnerPhysicsToExtControl();
			break;
		}

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

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

	// Enter our new state.
    m_iState = iState;
	switch ( m_iState )
	{
		case MOVING_TO_ATTRACTOR:
		{
			GetGSActorBehaviour()->ResetOwnerPhysicsFromExtControl();

			Asura_ServerNode_GS2Attractor* pxAttractor = Asura_ServerNode_GS2Attractor::FindEntity( GetGSActorBehaviour()->GetCurrentEntityGuid() );
			if ( pxAttractor )
			{
				// Determine the target position from the target node and the follow ground stuff.
				m_xTargetPosition = pxAttractor->GetPosition();
				if ( pxAttractor->ShouldFollowSurface() )
				{
					Asura_Matrix_3x3 xTargetOrient;
					pxAttractor->GetOrientation( xTargetOrient );
					const Asura_Vector_3 xDown = Asura_Vector_3( 0.0f, 1.0f, 0.0f ) * xTargetOrient;

					float fDistanceToIntersection = 0.0f;
					if ( Axon_Behaviour_GS2Actor::FollowSurfaceTest( m_xTargetPosition, xDown, fDistanceToIntersection ) )
					{
						m_xTargetPosition += xDown * fDistanceToIntersection;
					}
				}

				Axon_Behaviour_GS2Actor::GetToBehaviourParams xGTBParams;
				xGTBParams.SetAttractor( pxAttractor );
				if ( m_fArrivalRange > 0.0f )
				{
					// Pick a random position near to the node. We assume the node is orientated to the floor.
					// Get a random vector in the world XZ plane.
					Asura_Vector_3 xRandomVector( 0.0f, 0.0f, Asura_Random::GetFloat( 0.0f, m_fArrivalRange ) );
					Asura_Quat xQYAxisRotation;
					xQYAxisRotation.FromRotation( Asura_Random::GetFloat( -Asura_Maths::Pi, Asura_Maths::Pi ), Asura_Vector_3( 0.0f, 1.0f, 0.0f ) );
					xRandomVector = xQYAxisRotation.RotateVector( xRandomVector );
					// Transform it into the node's local space.
					Asura_Quat xQNodeOrient;
					pxAttractor->GetOrientation( xQNodeOrient );
					xRandomVector = xQNodeOrient.RotateVector( xRandomVector );

					xGTBParams.SetPosition( m_xTargetPosition + xRandomVector );
				}
				else
				{
					xGTBParams.SetPosition( m_xTargetPosition );
					pxAttractor->GetOrientation( xGTBParams.m_xOrientation );
					xGTBParams.SetContains( Axon_Behaviour_GS2Actor::GetToBehaviourParams::ORIENTATION );
				}
				m_pxGetToBehaviour = GetGSActorBehaviour()->CreateGetToBehaviour( &xGTBParams );
				Asura_Assert( m_pxGetToBehaviour, "Axon_Behaviour_GS2AttractorEnRoute::SwitchToState - could not create get to behaviour." );
				if ( m_pxGetToBehaviour )
				{
					// Don't activate this, as it'll be activated directly after us.
					LinkChild( m_pxGetToBehaviour );
				}
			}

			// If we fail to create a get to behaviour the update will detect this and teleport us to our destination.
			// This is far from ideal, but designers are now using attractors in situations where they cannot be allowed to fail.

			break;
		}

		case FINISHED:
		{
			GetGSActorBehaviour()->StopAnimSet();
			SetFinished();
			break;
		}

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

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

#endif // ASURA_USE_AXON
