//////////////////////////////////////////////////////////////////////////////
//
// Axon_Behaviour_LookAt.cpp
//
// Copyright (C) 2009 Rebellion. All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

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

#ifdef ASURA_USE_NEW_NAV_STUFF

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

// asura
#include "Core/Asura_Maths.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#ifndef ASURA_NO_DEBUG_OUTPUT
#include "Debugging/Asura_DebugLineRender.h"
#endif
#include "Entity/PhysicalObject/Asura_ServerEntity_PhysicalObject.h"
#include "Entity/PhysicalObject/Asura_ClientEntity_PhysicalObject.h"
#ifndef ASURA_NO_DEBUG_OUTPUT
#include "Rendering/Asura_Font_System.h"
#endif

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


//// S T A T I C S ///////////////////////////////////////////////////////////

static const float fSEARCHING_FOR_THREAT_TIME_INTERVAL	= 1.0f;
static const float fLOOKINGAT_VALIDATE_TIME_INTERVAL	= 1.0f;
static const float fMAX_LOOKAT_RANGE					= 100.0f;

//////////////////////////////////////////////////////////////////////////////
//
// Axon_Behaviour_LookAt
//
//////////////////////////////////////////////////////////////////////////////

Axon_Behaviour_LookAt::Axon_Behaviour_LookAt( Axon_Brain* pxBrain )
: PARENT( AXON_BEHAVIOUR_TYPE_LOOK_AT, pxBrain )
, m_uLookAtFlags( 0 )
, m_uLookAtGuid( ASURA_GUID_UNREGISTERED )
, m_uLookAtMarkerHash( ASURA_HASH_ID_UNSET )
, m_iState( WAITING_FOR_LOOK_AT )
, m_fWaitTimer( 0.0f )
, m_fMaxLookAtRange( fMAX_LOOKAT_RANGE )
, m_pxLookAtPhyObj( NULL )
, m_pxOwnerPhyObj( NULL )
{
	m_bUseLOS = true;
}

//-----------------------------------------------------

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

	PARENT::ReadFromChunkStream( xStream );

	if ( uVersion == uCURRENT_CHUNK_VERSION )
	{
		xStream >> m_uLookAtGuid;
		xStream >> m_uLookAtMarkerHash;
		xStream >> m_uLookAtFlags;
	}
	else
	{
		Asura_Assertf( false, ( "Axon_Behaviour_LookAt::ReadFromChunkStream - unhanded chunk version (%u)", uVersion ) );
	}
}

//-----------------------------------------------------

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

	PARENT::WriteToChunkStream( xStream );

	xStream << m_uLookAtGuid;
	xStream << m_uLookAtMarkerHash;
	xStream << m_uLookAtFlags;
}

//-----------------------------------------------------

void Axon_Behaviour_LookAt::Deactivate()
{
	SwitchToState( WAITING_FOR_LOOK_AT );
	PARENT::Deactivate();
}

//-----------------------------------------------------

void Axon_Behaviour_LookAt::PostLoadInitialise()
{
	PARENT::PostLoadInitialise();
}

//-----------------------------------------------------

void Axon_Behaviour_LookAt::Update( const float fTimeslice )
{
	PARENT::Update( fTimeslice );

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

	m_fWaitTimer += fTimeslice;

	if ( !UpdateOwnerPhyObjPtr() )
	{
		SetInError();
		return;
	}

	if ( !UpdateLookAtPhyObjPointer() )
	{
		SwitchToState( WAITING_FOR_LOOK_AT );
	}
	else
	{
		UpdateLookAtPositionAndRange();
	}
	
	switch ( m_iState )
	{
	case WAITING_FOR_LOOK_AT:
		{
			if ( GetLookAtPhyObj() )
			{
				SwitchToState( SEARCHING_FOR_LOOK_AT_TARGET );
			}
			break;
		}

	case LOOKING_AT:
		{

			OwnerLookAtPosition();

			// Frequently check the validity of our LookAt target
			if ( m_fWaitTimer > fLOOKINGAT_VALIDATE_TIME_INTERVAL )
			{
				m_fWaitTimer = 0.0f;

				if ( !GetInRange() ) 
				{
					SwitchToState( SEARCHING_FOR_LOOK_AT_TARGET ); 
					break;
				}
				
				if ( GetUseLOS() && !IsLookAtPositionInLOS() )
				{
					SwitchToState( SEARCHING_FOR_LOOK_AT_TARGET );
				}				
			}
			break;
		}

	case SEARCHING_FOR_LOOK_AT_TARGET:
		{
		
			if ( m_fWaitTimer > fSEARCHING_FOR_THREAT_TIME_INTERVAL )
			{
				m_fWaitTimer = 0.0f;

				// Make sure the LookAt is in Range, and also if we have a Line Of Sight to the Target if desired.
				if ( GetInRange() )
				{
					if ( GetUseLOS() )
					{
						if ( IsLookAtPositionInLOS() )
						{
							SwitchToState( LOOKING_AT );
						}
					}
					else
					{
						SwitchToState( LOOKING_AT );
					}
				}
			}
			break;
		}

	default:
		{
			m_uLookAtGuid = ASURA_GUID_UNREGISTERED;
			break;
		}
		break;
	}
}

//-----------------------------------------------------

void Axon_Behaviour_LookAt::OwnerLookAtPosition()
{
	const Asura_Vector_3 xDirection = GetLookAtPosition() - GetOwnerPhyObj()->GetPosition();
	Asura_Matrix_3x3 xOrientMat;
	Asura_Maths::OrientationFromDirection( xOrientMat, xDirection );
	m_pxOwnerPhyObj->SetOrientation( xOrientMat );
}

//-----------------------------------------------------

bool Axon_Behaviour_LookAt::IsLookAtPositionInLOS()
{
	Asura_Vector_3 xActorToLookAtVector = ( m_xLookAtPosition - GetOwnerPhyObj()->GetPosition() );
	float fDistanceToLookAtPosition = xActorToLookAtVector.Normalise();
	Asura_LOS_InputData xInput( GetOwnerPhyObj()->GetPosition(), xActorToLookAtVector, GetOwnerPhyObj()->GetGuid(), fDistanceToLookAtPosition);
	const Asura_LOS_OutputData* pxOutput = Asura_LOS::TestAgainstEnvironment(&xInput);
	if (!pxOutput->m_bHitEnvironment)
	{
		return true;
	}
	return false;
}

//-----------------------------------------------------

void Axon_Behaviour_LookAt::UpdateLookAtPositionAndRange()
{

	m_xLookAtPosition = GetLookAtPhyObj()->GetPosition();

	// If we have a markerHash then we try to look at that on the LookAt Object. Get it from the Animation where possible.
	if ( m_uLookAtMarkerHash != ASURA_HASH_ID_UNSET )
	{
		const Asura_ClientEntity_PhysicalObject* const pxClientLookAtPhyObj = Asura_ClientEntity_PhysicalObject::FindEntity( GetOwnerPhyObj()->GetGuid() );
		if ( pxClientLookAtPhyObj )
		{
			if ( pxClientLookAtPhyObj->GetAnimation() )
			{
				Asura_Vector_3 xMarkerPos( Asura_Maths::ZeroVector3 );
				if ( pxClientLookAtPhyObj->GetAnimation()->GetMarkerLocationFromHash( m_uLookAtMarkerHash, &xMarkerPos, NULL ) )
				{
					m_xLookAtPosition += xMarkerPos;
				}
			}
			else
			{
				Asura_Hash_ID uSkinHash = pxClientLookAtPhyObj->GetShapeInstance().GetShapeOrSkinHashID();
				Asura_Hierarchy_Skin* pxSkin = Asura_Animation_System::GetSkinByHash(uSkinHash);
				Asura_Vector_3 xMarkerPos( Asura_Maths::ZeroVector3 );
				if ( pxSkin && pxSkin->GetMarkerPositionInRestPose( m_uLookAtMarkerHash, &xMarkerPos ) )
				{
					m_xLookAtPosition += xMarkerPos;
				}
			}
		}
	}

	// Set if we are in Range of our LookAt Position.
	const float fDistSQ = ( m_xLookAtPosition - GetOwnerPhyObj()->GetPosition() ).MagnitudeSquared();
	m_bInRange = ( fDistSQ < (m_fMaxLookAtRange*m_fMaxLookAtRange) );
}

//-----------------------------------------------------

void Axon_Behaviour_LookAt::SetLookAtMarkerHash( const Asura_Guid uMarkerHash )
{
	m_uLookAtMarkerHash = uMarkerHash;
}

//-----------------------------------------------------

void Axon_Behaviour_LookAt::SetLookAtGuid( const Asura_Guid uLookAtGuid )
{
	m_uLookAtGuid = uLookAtGuid;
	OnNewLookAtGuid();
}

//-----------------------------------------------------

void Axon_Behaviour_LookAt::OnNewLookAtGuid()
{
	const Asura_ServerEntity_PhysicalObject* const pxLookAt = Asura_ServerEntity_PhysicalObject::FindEntity( m_uLookAtGuid );
	if ( pxLookAt )
	{
		SwitchToState( SEARCHING_FOR_LOOK_AT_TARGET );
	}
	else
	{
		Asura_Assert( false, "Axon_Behaviour_LookAt::OnNewLookAtGuid - could not find LookAt Entity" );
		SwitchToState( WAITING_FOR_LOOK_AT );
	}
}

//-----------------------------------------------------

bool Axon_Behaviour_LookAt::UpdateLookAtPhyObjPointer()
{
	m_pxLookAtPhyObj = Asura_ServerEntity_PhysicalObject::FindEntity( m_uLookAtGuid );
	return ( m_pxLookAtPhyObj != NULL );
}

//-----------------------------------------------------

bool Axon_Behaviour_LookAt::UpdateOwnerPhyObjPtr() 
{
	m_pxOwnerPhyObj = Asura_ServerEntity_PhysicalObject::FindEntity( GetBrain()->GetOwnerGuid() );
	Asura_Assertf( m_pxOwnerPhyObj, ( "UpdateOwnerPhyObjPtr() - Brain Owner Guid %u, is not found in the Asura_ServerEntity_PhysicalObject list.", GetBrain()->GetOwnerGuid() ) );
	return ( m_pxOwnerPhyObj != NULL ); 
}

//-----------------------------------------------------

void Axon_Behaviour_LookAt::SwitchToState( int iNextState )
{
	// Exit old state.
	switch ( m_iState )
	{
	case WAITING_FOR_LOOK_AT:
		{
			break;
		}

	case LOOKING_AT:
		{
			break;
		}

	case SEARCHING_FOR_LOOK_AT_TARGET:
		{
			break;
		}

	default:
		{
			break;
		}
	}

	// Enter new state.
	m_iState = iNextState;
	m_fWaitTimer = 0.0f;
	switch ( m_iState )
	{
	case WAITING_FOR_LOOK_AT:
		{
			break;
		}

	case LOOKING_AT:
		{
			break;
		}

	case SEARCHING_FOR_LOOK_AT_TARGET:
		{
			break;
		}

	default:
		{
			break;
		}
	}
}

#ifndef ASURA_NO_DEBUG_OUTPUT
static const char* s_aszStateStrings[] =
{
	// N.B. Keep in updated with behaviour states.
	"LookAt - WAITING_FOR_LOOK_AT",
	"LookAt - LOOKING_AT",
	"LookAt - SEARCHING_FOR_LOOK_AT_TARGET",
};

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

	// Add a box at the lookAt position and render a line from ourPos to this lookAt position.
	if ( GetLookAtPhyObj() && GetOwnerPhyObj() )
	{
		Asura_Bounding_Box xBB;
		xBB.Reset();
		xBB.Set( Asura_Vector_3(0.0f,0.0f,0.0f) );
		xBB.Grow(0.5f);
		if ( GetLookAtPhyObj() )	
		Asura_DebugLineRender::AddBoundingBox( xBB, m_xLookAtPosition, GetLookAtPhyObj()->GetOrientation(), 0xff00CC99);

		Asura_DebugLineRender::AddLine( GetOwnerPhyObj()->GetPosition(), m_xLookAtPosition, 0xff00CC33 );
	}

	if ( GetBrain()->DebugTextPosOnScreen() )
	{
		Asura_Font_System::Print( GetBrain()->GetDebugTextPos().x, GetBrain()->GetDebugTextPos().y, 0.5f, 0xffffffff, 
			ASURA_TEXTFORMAT_CENTREJUSTIFIED | ASURA_TEXTFORMAT_FORMATSTRING, s_aszStateStrings[m_iState] );
		GetBrain()->IncrementDebugTextPos();
	}
}
#endif

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

#endif // #ifdef ASURA_USE_NEW_NAV_STUFF
