//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
#include "cbase.h"
#include "hl2wars/rts_point_order.h"
#include "ai_behavior.h"
#include "hl2wars/rts_shareddefs.h"

#include "ai_baseactor.h"
#include "ai_navigator.h"
#include "ai_basenpc.h"
#include "ai_hint.h"
#include "ai_memory.h"
#include "ai_senses.h"

#include "nav_mesh.h"
#include "nav_area.h"
#include "ai_pathfinder.h"
#include "nav_pathfind.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//Debug visualization
ConVar	g_debug_orders( "g_debug_orders", "0" );

BEGIN_DATADESC( CRTSPointOrder )
	// fields
	DEFINE_FIELD( m_bRun, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_iOrderType, FIELD_INTEGER ),
	DEFINE_FIELD( m_iAbility, FIELD_INTEGER ),
	DEFINE_FIELD( m_iAbilityButton, FIELD_INTEGER ),
	DEFINE_FIELD( m_hNPC, FIELD_EHANDLE ),
	DEFINE_FIELD( m_vArrivalDirection, FIELD_VECTOR ),
	DEFINE_FIELD( m_bArrivalDirectionSetted, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bMoveToGoal, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ),
	//DEFINE_FIELD( m_OldRelation, FIELD_EMBEDDED ),
	DEFINE_FIELD( m_fAbilityMinRange, FIELD_FLOAT ),
	DEFINE_FIELD( m_fAbilityMaxRange, FIELD_FLOAT ),
	DEFINE_FIELD( m_hHint, FIELD_EHANDLE ),

	// Declare our think function
    DEFINE_THINKFUNC( OnThink ),
END_DATADESC()

LINK_ENTITY_TO_CLASS( rts_point_order, CRTSPointOrder );

//=========================================================
CRTSPointOrder::CRTSPointOrder() 
{
	m_bArrivalDirectionSetted = false;
	m_hHint = NULL;
	m_pHidingSpot = NULL;
	m_bModelMinMax = false;
	m_pNext = NULL;
}

CRTSPointOrder::~CRTSPointOrder() 
{
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CRTSPointOrder::UpdateOnRemove()
{
	if( m_iOrderType == ORDER_ATTACK )
		m_hNPC.Get()->AddEntityRelationship( GetTarget(), m_OldRelation->disposition, m_OldRelation->priority );   

	if( m_iOrderType == ORDER_GROUNDATTACK )
	{
		if( GetTarget() )
		{
			m_hNPC.Get()->AddEntityRelationship( GetTarget(), m_OldRelation->disposition, m_OldRelation->priority ); 
		}
		else
		{
			m_hNPC.Get()->AddEntityRelationship( this, m_OldRelation->disposition, m_OldRelation->priority ); 
		}
	}

	if(m_iOrderType== ORDER_ATTACK || m_iOrderType == ORDER_GROUNDATTACK) {
		m_hNPC.Get()->SetEnemy(NULL);
		m_hNPC.Get()->GetEnemies()->ClearMemory(this);
		m_hNPC.Get()->GetEnemies()->ClearMemory(GetTarget());

		m_hNPC.Get()->ClearSchedule( NULL );
		m_hNPC.Get()->SelectSchedule();
	}

	if( m_hHint )
		m_hHint.Get()->Unlock();

	if( m_pHidingSpot )
		m_pHidingSpot->UnLock();

	if( m_iAbility == ABILITY_BUILDING )
	{
		if( GetTarget() )
		{
			UTIL_Remove(GetTarget());
		}
		else
		{
			Warning("CRTSPointOrder::UpdateOnRemove: No GetTarget(), ABILITY_BUILDING should always have target, which is the building!!\n");
		}
	}

	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// Purpose: Spawn
//-----------------------------------------------------------------------------
void CRTSPointOrder::Spawn()
{
	BaseClass::Spawn();

	// This is a dummy model that is never used!
	UTIL_SetSize(this, Vector(-16,-16,-16), Vector(16,16,16));
}

//-----------------------------------------------------------------------------
// Purpose: Start, depending on the order we start this order.
//-----------------------------------------------------------------------------
void CRTSPointOrder::Start()
{
	switch(m_iOrderType) 
	{
		case ORDER_IDLE:
			break;
		case ORDER_MOVE:
			{
				if( m_hNPC && m_hNPC->IsAlive() )
				{
					if( m_hNPC->GetBehavior() == BEHAVIOR_NORMAL )
					{
						if( m_hNPC->ShouldFindCover() )
						{
							// we need to search a hint node
							CAI_Hint *pHint;
							pHint = CAI_HintManager::FindHint( m_hNPC.Get(), HINT_NONE, 0, 256.0f, &(this->GetAbsOrigin()) );
							if( pHint )
							{
								m_hHint = pHint;
								SetAbsOrigin( pHint->GetAbsOrigin() );
								SetArrivalDirection( pHint->GetAbsAngles() );

								pHint->Lock( m_hNPC.Get() );	// lock, this is our hint now!
							}
							else
							{
								// find a cover spot in a radius of 256.0f from our goal
								// check each hiding spot for visibility
								HidingSpot *spot, *bestSpot;
								bestSpot = NULL;
								float distance, fBestDistance = 99999.0f;
								Vector delta;

								FOR_EACH_LL( TheHidingSpotList, it )
								{
									spot = TheHidingSpotList[ it ];

									if( !spot )
										continue;

									if( spot->IsLocked() )
										continue;

									//if (!spot->HasGoodCover())
										//continue;

									const Vector &spotPos = spot->GetPosition();
									delta = spotPos - GetAbsOrigin();

									// check if in range
									distance = delta.Length2D();
									if( distance > 256.0f )
										continue;

									// consider the z seperated, allow less because of multilevel buildings
									if( fabs(delta.z) > 48.0f )
										continue;

									if( distance > fBestDistance )
										continue;

									// last check: can we reach it?
									CNavArea *startArea = TheNavMesh->GetNearestNavAreaB(m_hNPC->GetAbsOrigin() );
									CNavArea *goalArea = TheNavMesh->GetNearestNavAreaB(spotPos);
									ShortestPathCost costfunc;
									if( !m_hNPC->GetPathfinder()->NavAreaBuildPathRTS( startArea, goalArea, &spotPos, costfunc, 0, 32.0f ) )
										continue;
									
									fBestDistance = distance;
									bestSpot = spot;
								}

								if( bestSpot )
								{
									m_pHidingSpot = bestSpot;
									SetAbsOrigin( bestSpot->GetPosition() );
									m_pHidingSpot->Lock();
								}
							}
						}
					}	
				}

				MoveOrderNormal( );
				break;
			}
		case ORDER_ATTACK:
			SetParent( GetTarget() );	// this target might move probably
			m_OldRelation = m_hNPC.Get()->FindEntityRelationship( GetTarget() );
			m_hNPC.Get()->AddEntityRelationship( GetTarget(), D_HT, 1000 );

			// ----------------------------------------------------------------
			//  Enemy in memory?
			// ----------------------------------------------------------------
			if ( !m_hNPC.Get()->GetEnemies()->HasMemory( GetTarget() )	)
			{
				m_hNPC.Get()->UpdateEnemyMemory(GetTarget(), GetTarget()->GetAbsOrigin(), GetTarget());
			}
			// ----------------------------------------------------------------
			//  If I already know about this enemy, update his position
			// ----------------------------------------------------------------
			else
			{
				m_hNPC.Get()->UpdateEnemyMemory( GetTarget(), GetTarget()->GetAbsOrigin());
			}

			m_hNPC.Get()->ClearSchedule( NULL );
			m_hNPC.Get()->SelectSchedule();
			
			break;
		case ORDER_GROUNDATTACK:
			{
				if( GetTarget() )
				{
					SetParent( GetTarget() );	// this target might move probably
					m_OldRelation = m_hNPC.Get()->FindEntityRelationship( GetTarget() );
					m_hNPC.Get()->AddEntityRelationship( GetTarget(), D_HT, 1000 );

					GetTarget()->AddFlag( FL_OBJECT );
					GetTarget()->AddFlag( FL_AIMTARGET );
					g_AI_SensedObjectsManager.AddEntity(GetTarget());

					if ( !m_hNPC.Get()->GetEnemies()->HasMemory( GetTarget() )	)
					{
						m_hNPC.Get()->UpdateEnemyMemory(GetTarget(), GetTarget()->GetAbsOrigin(), GetTarget());
					}
					// ----------------------------------------------------------------
					//  If I already know about this enemy, update his position
					// ----------------------------------------------------------------
					else
					{
						m_hNPC.Get()->UpdateEnemyMemory( GetTarget(), GetTarget()->GetAbsOrigin());
					}

				}
				else
				{
					m_OldRelation = m_hNPC.Get()->FindEntityRelationship( this );
					m_hNPC.Get()->AddEntityRelationship( this, D_HT, 1000 );

					AddFlag( FL_OBJECT );
					AddFlag( FL_AIMTARGET );
					g_AI_SensedObjectsManager.AddEntity(this);

					if ( !m_hNPC.Get()->GetEnemies()->HasMemory( this )	)
					{
						m_hNPC.Get()->UpdateEnemyMemory(this, GetAbsOrigin(), this);
					}
					// ----------------------------------------------------------------
					//  If I already know about this enemy, update his position
					// ----------------------------------------------------------------
					else
					{
						m_hNPC.Get()->UpdateEnemyMemory( this, GetAbsOrigin());
					}
				}
			}
			break;
		case ORDER_ACTION:
			MoveOrderNormal( );
			break;
		case ORDER_DO_ABILITY:
			m_hNPC.Get()->SetTarget( this );
			m_hNPC.Get()->ClearSchedule( NULL );
			m_hNPC.Get()->SelectSchedule();
			break;
		default:
			MoveOrderNormal( );
			break;
	}

	if ( g_debug_orders.GetInt() == 1 )
	{
		NDebugOverlay::Cross3D( GetAbsOrigin(), -Vector(32,32,32), Vector(32,32,32), 255, 255, 0, true, 2.0f );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Set the npc that is using this order
//-----------------------------------------------------------------------------
void CRTSPointOrder::SetNPC( CAI_BaseNPC *pNPC )
{
	m_hNPC = pNPC;
}

//-----------------------------------------------------------------------------
// Purpose: Set the order type ( move to, defense... )
//-----------------------------------------------------------------------------
void CRTSPointOrder::SetOrderType( int order, bool bRun ) 
{
    m_iOrderType = order;
	m_bRun = bRun;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CRTSPointOrder::SetAbility( int ability ) 
{
	m_iAbility = ability;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CRTSPointOrder::SetAbilityButton( int ability_button ) 
{
	m_iAbilityButton = ability_button;
}

//-----------------------------------------------------------------------------
// Purpose: Get the order type ( move to, defense... )
//-----------------------------------------------------------------------------
int CRTSPointOrder::GetOrderType( ) 
{
    return m_iOrderType;
}

//-----------------------------------------------------------------------------
// Purpose: Think!
//-----------------------------------------------------------------------------
void CRTSPointOrder::OnThink() 
{
	if( !m_hNPC || !m_hNPC->IsAlive() )
		return;

	switch(m_iOrderType) 
	{
		case ORDER_MOVE:
			break;
		default:
			break;
	}

	SetNextThink( gpGlobals->curtime + 0.5f );
}

//-----------------------------------------------------------------------------
// Purpose: Move order
//-----------------------------------------------------------------------------
void CRTSPointOrder::MoveOrderNormal( )
{    	
	// see ai_default for sheduletypes, see ai_activy.h for acts			
	if( m_bRun ) {				    
		ScheduleMoveOrder( ACT_RUN );
		m_hNPC->SelectSchedule();
	}		
	else {			 				
		ScheduleMoveOrder( ACT_WALK );
		m_hNPC->SelectSchedule();
	}	
}

//-----------------------------------------------------------------------------
// Purpose: Order defense
//-----------------------------------------------------------------------------
void CRTSPointOrder::OrderDefense() 
{   
	if( m_bRun ) {				    
		ScheduleMoveOrder( ACT_RUN );
		m_hNPC->SelectSchedule();
	}		
	else {			 				
		ScheduleMoveOrder( ACT_WALK );
		m_hNPC->SelectSchedule();
	}	
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : 
//-----------------------------------------------------------------------------
void CRTSPointOrder::SetArrivalDirection( QAngle arrivalDirection)
{
	m_bArrivalDirectionSetted = true;
	m_vArrivalDirection = arrivalDirection;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : 
//-----------------------------------------------------------------------------
void CRTSPointOrder::SetMoveToGoal( bool move_to_goal )
{	
	m_bMoveToGoal = move_to_goal;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CRTSPointOrder::SetTarget( CBaseEntity *pTarget )
{
	m_hTarget = pTarget;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CRTSPointOrder::SetAbilityRange( float min, float max )
{
	m_fAbilityMinRange = min;
	m_fAbilityMaxRange = max;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CRTSPointOrder::SetAbilityModelMinMax( Vector min, Vector max )
{
	m_bModelMinMax = true;
	m_vMin = min;
	m_vMax = max;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : 
//-----------------------------------------------------------------------------
bool CRTSPointOrder::ScheduleMoveOrder( Activity movementActivity ) 
{	
	m_hNPC.Get()->ClearSchedule( NULL );
	SetMoveToGoal( true );
	return true;
}


