//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Entity that controls the strategic ai. Build units depending on the resource
//			amount and attack strategic points.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "hl2wars/ai_rts_strategic.h"
#include "hl2wars/rts_globalresource.h"
#include "ai_basenpc.h"
#include "hl2wars/rts_buildings.h"
#include "hl2wars/rts_point_order.h"
#include "hl2wars/rts_capture_point.h"
#include "hl2wars/util_hl2wars.h"
#include "saverestore_utlvector.h"
#include "hl2wars/rts_parsedata.h"

#include <ctime>    // For time()
#include <cstdlib>  // For srand() and rand()

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

ConVar  ai_debug_strategic( "ai_debug_strategic", "0" );

#ifndef _RETAIL
#define DbgStrAIMsg if ( !ai_debug_strategic.GetBool() ) ; else DevMsg
#else
#define DbgStrAIMsg if ( 0 ) ; else DevMsg
#endif

BEGIN_SIMPLE_DATADESC( unit_group_t )
	// fields
	DEFINE_FIELD( m_iID,			FIELD_INTEGER ),
	DEFINE_FIELD( m_iSize,			FIELD_INTEGER ),
	DEFINE_FIELD( m_iDesiredSize,	FIELD_INTEGER ),
	DEFINE_FIELD( m_iType,			FIELD_INTEGER ),
	DEFINE_FIELD( m_iTargetType,	FIELD_INTEGER ),
	DEFINE_FIELD( m_iState,			FIELD_INTEGER ),
	DEFINE_FIELD( pTarget,			FIELD_EHANDLE ),
	DEFINE_ARRAY( m_Group,			FIELD_EHANDLE, GROUP_MAXSIZE ),
END_DATADESC()

BEGIN_DATADESC( CAI_StrategicManager )
	// inputs
	DEFINE_INPUTFUNC(FIELD_INTEGER, "EnableStrategicAI", InputEnableStrategicAI),
	DEFINE_INPUTFUNC(FIELD_INTEGER, "DisableStrategicAI", InputDisableStrategicAI),
END_DATADESC()

BEGIN_DATADESC( CAI_Strategic )
	// Function Pointers
	DEFINE_FUNCTION( StrategicThink ),

	// fields
	DEFINE_UTLVECTOR( m_UnitGroups,			FIELD_EMBEDDED ),
	DEFINE_UTLVECTOR( m_UnitGroupsBuilding, FIELD_EMBEDDED ),

	DEFINE_FIELD( m_bEnabled,				FIELD_BOOLEAN ),
	DEFINE_FIELD( m_iTeamID,				FIELD_INTEGER ),
	DEFINE_FIELD( m_iCheapestUnit,			FIELD_INTEGER ),
	DEFINE_FIELD( m_iMostExpensiveUnit,		FIELD_INTEGER ),

	DEFINE_FIELD( m_bBuildPlan,				FIELD_BOOLEAN ),
	DEFINE_FIELD( m_iDesiredSize,			FIELD_INTEGER ),
	DEFINE_FIELD( m_iDesiredCost,			FIELD_INTEGER ),
	DEFINE_FIELD( m_iDesiredCostTotal,		FIELD_INTEGER ),

	DEFINE_FIELD( m_iUnitsQueued,			FIELD_INTEGER ),
	DEFINE_FIELD( m_fNextTryTime,			FIELD_FLOAT ),

	DEFINE_FIELD( m_iID,					FIELD_INTEGER ),
	DEFINE_FIELD( m_iLastID,				FIELD_INTEGER ),
END_DATADESC()

LINK_ENTITY_TO_CLASS( point_ai_strategic, CAI_StrategicManager );
LINK_ENTITY_TO_CLASS( ai_strategic, CAI_Strategic );

CAI_Strategic *g_StrategicAI[MAX_TEAMS_ID_RTS];

// ##################################################################################
//	>> Strategic ai entry
// ##################################################################################
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CAI_Strategic::CAI_Strategic()
{
	m_bEnabled = false;
	m_bBuildPlan = false;

	m_iUnitsQueued = 0;
	m_fNextTryTime = 0.0f;
	m_iID = 0;
}

int CAI_Strategic::Restore( IRestore &restore )
{
	int status = BaseClass::Restore(restore);

	g_StrategicAI[m_iTeamID - DEFAULT_TEAMS] = this;	// link up again

	return status;
}

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

	ReconsiderCosts();
}

void CAI_Strategic::Activate()
{
	BaseClass::Activate();

	ReconsiderCosts();
}

void CAI_Strategic::ReconsiderCosts()
{
	m_iCheapestUnit = 99999;
	// determine cheapest and most expensive unit
	for( int i = 0; i < g_pRTSResource->m_pBuildingList->GetCount( m_iTeamID ); i++ )
	{
		CBuildingBrush *pBuilding = dynamic_cast<CBuildingBrush *>( g_pRTSResource->m_pBuildingList->GetFromList( m_iTeamID, i) );
		if( pBuilding )
		{
			for( int i = 1; i < MAXABILITIESSLOTS + 1; i++ ) {
				if( pBuilding->GetTypeAbility( i - 1 ) == 0 )
					continue;

				FileUnitInfo_t *pFUI = GetFileUnitInfoFromHandle( LookupUnitInfoSlot( STRING( g_pRTSResource->GetUnitType( pBuilding->GetTypeAbility( i - 1 ) ) ) ) );
				if( !pFUI )
					continue;

				int cost = pFUI->m_iCost;
				
				if( cost < m_iCheapestUnit )
					m_iCheapestUnit = cost;

				if( cost > m_iMostExpensiveUnit )
					m_iMostExpensiveUnit = cost;
			}	
		}
	}

	for( int i = 0; i < g_pRTSResource->m_pBuildingModelList->GetCount( m_iTeamID ); i++ )
	{
		CBaseBuildingModel *pBuildingM = dynamic_cast<CBaseBuildingModel *>( g_pRTSResource->m_pBuildingModelList->GetFromList( m_iTeamID, i) );
		if( pBuildingM )
		{
			for( int i = 1; i < MAXABILITIESSLOTS + 1; i++ ) {
				if( pBuildingM->GetTypeAbility( i - 1 ) == 0 )
					continue;

				FileUnitInfo_t *pFUI = GetFileUnitInfoFromHandle( LookupUnitInfoSlot( STRING( g_pRTSResource->GetUnitType( pBuildingM->GetTypeAbility( i - 1 ) ) ) ) );
				if( !pFUI )
					continue;

				int cost = pFUI->m_iCost;
				
				if( cost < m_iCheapestUnit )
					m_iCheapestUnit = cost;

				if( cost > m_iMostExpensiveUnit )
					m_iMostExpensiveUnit = cost;
			}	
		}
	}

	m_iMostExpensiveUnit = (int)((float)m_iMostExpensiveUnit * 1.1f);

	DbgStrAIMsg(1, "AI Teamid: %d, cheapest unit: %d, most expensive unit: %d, model buildings: %d\n", m_iTeamID, m_iCheapestUnit, m_iMostExpensiveUnit, g_pRTSResource->m_pBuildingModelList->GetCount( m_iTeamID ));
}

//-----------------------------------------------------------------------------
// Purpose: Main entry for the strategic ai
//-----------------------------------------------------------------------------
void CAI_Strategic::StrategicThink( void )
{
	MaintainGroups();
	BuildGroups();

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

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_Strategic::CheckAlive()
{
	for( int i = 0; i < m_UnitGroups.Count(); i++ )
	{
		m_UnitGroups.Element( i ).m_iSize = 0;
		for( int j = 0; j < GROUP_MAXSIZE; j++ )
		{
			if( m_UnitGroups.Element( i ).m_Group[j].Get() && m_UnitGroups.Element( i ).m_Group[j].Get()->IsAlive() )
			{
				m_UnitGroups.Element( i ).m_iSize++;
			}
			else
			{
				m_UnitGroups.Element( i ).m_Group[j] = NULL;
			}
		}

		if( m_UnitGroups.Element( i ).m_iSize == 0 )
			m_UnitGroups.Remove(i);
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_Strategic::MaintainGroups()
{
	CheckAlive();

	for( int i = 0; i < m_UnitGroups.Count(); i++ )
	{
		switch( m_UnitGroups.Element( i ).m_iState )
		{
		case GROUP_IDLING:
			{
				m_UnitGroups.Element( i ).pTarget = PickTarget( i );
				if( m_UnitGroups.Element( i ).pTarget )
				{
					AIScheduleMoveOrder( i );
					m_UnitGroups.Element( i ).m_iState = GROUP_MOVING;
				}
			}
			break;
		case GROUP_MOVING:
			{
				if( m_UnitGroups.Element( i ).pTarget )
				{
					CCapturePoint *pTarget = dynamic_cast<CCapturePoint *>(m_UnitGroups.Element( i ).pTarget.Get());
					if( pTarget )
					{
						if( pTarget->GetOwnerShip() == m_iTeamID )	// captured, done, pick new target
							m_UnitGroups.Element( i ).m_iState = GROUP_IDLING;
					}
				}
			}
			break;
		default:
			break;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_Strategic::AIScheduleMoveOrder( int element )
{
	float radius = 32.0f;
	for(int i = 0; i < GROUP_MAXSIZE; i++) 
	{		
		if( !m_UnitGroups.Element( element ).m_Group[i])
			continue;

		CAI_BaseNPC *pNPC = m_UnitGroups.Element( element ).m_Group[i].Get();
		if( !pNPC || !pNPC->IsAlive() )
			continue;

		radius += 32.0f;
		MoveOrderNormal( pNPC, m_UnitGroups.Element( element ).pTarget->GetAbsOrigin(), radius );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Create a move order.
//-----------------------------------------------------------------------------
void CAI_Strategic::MoveOrderNormal( CAI_BaseNPC *pNPC, Vector origin, float radius )
{    
	if( !pNPC )
		return;

	// first create the target	
	CRTSPointOrder *pTarget = (CRTSPointOrder *)CreateEntityByName( "rts_point_order" );	
	if( pTarget ) {
		// find a spot
		origin[2] += 24.0f;
		Vector result;
		if( UTIL_CheckForOrderInSphere( origin, 16.0f ) )	
		    UTIL_FindSpotForOrderInRadius( &result, origin, radius );
		else
			VectorCopy( origin, result );

		pTarget->SetAbsOrigin( result );   
		pTarget->Spawn(); 
		pTarget->SetNPC( pNPC );
		pTarget->SetOrderType( ORDER_MOVE, true );
		pTarget->Start();
		pNPC->ClearAllOrders();
		pNPC->QueueNewOrder(pTarget);
	}	  
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CAI_Strategic::BuildGroupScheduleBrush()
{
	CBuildingBrush *pBuilding;
	for( int i = 0; i < g_pRTSResource->m_pBuildingList->GetCount( m_iTeamID ); i++ )
	{
		if( m_iUnitsQueued >= m_iDesiredSize )
			return true;

		pBuilding = dynamic_cast<CBuildingBrush *>( g_pRTSResource->m_pBuildingList->GetFromList( m_iTeamID, i) );
		if( pBuilding )
		{
			for( int j = 1; j < MAXABILITIESSLOTS + 1; j++ ) {
				if( pBuilding->GetTypeAbility( j - 1 ) == 0 )
					continue;

				FileUnitInfo_t *pFUI = GetFileUnitInfoFromHandle( LookupUnitInfoSlot( STRING( g_pRTSResource->GetUnitType( pBuilding->GetTypeAbility( j - 1 ) ) ) ) );
				if( !pFUI )
					continue;

				int cost = pFUI->m_iCost;
				if( cost <= m_iDesiredCost )
				{
					if( pBuilding->AddToQueue( j, m_iLastID ) )
					{
						m_iDesiredCostTotal -= cost;
						m_iUnitsQueued++;
					}
				}

				if( m_iUnitsQueued == m_iDesiredSize )
					break;
			}	
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CAI_Strategic::BuildGroupScheduleModel()
{
	CBaseBuildingModel *pBuilding;
	for( int i = 0; i < g_pRTSResource->m_pBuildingModelList->GetCount( m_iTeamID ); i++ )
	{
		if( m_iUnitsQueued >= m_iDesiredSize )
			return true;

		pBuilding = dynamic_cast<CBaseBuildingModel *>( g_pRTSResource->m_pBuildingModelList->GetFromList( m_iTeamID, i) );
		if( pBuilding )
		{
			for( int j = 1; j < MAXABILITIESSLOTS + 1; j++ ) {
				if( pBuilding->GetTypeAbility( j - 1 ) == 0 )
					continue;

				FileUnitInfo_t *pFUI = GetFileUnitInfoFromHandle( LookupUnitInfoSlot( STRING( g_pRTSResource->GetUnitType( pBuilding->GetTypeAbility( j - 1 ) ) ) ) );
				if( !pFUI )
					continue;

				int cost = pFUI->m_iCost;
				if( cost <= m_iDesiredCost )
				{
					if( pBuilding->AddToQueue( j, m_iLastID ) )
					{
						m_iDesiredCostTotal -= cost;
						m_iUnitsQueued++;
					}
				}

				if( m_iUnitsQueued == m_iDesiredSize )
					break;
			}	
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_Strategic::BuildGroups()
{
	if( g_pRTSResource->m_pBuildingList->GetCount( m_iTeamID ) == 0 && g_pRTSResource->m_pBuildingModelList->GetCount( m_iTeamID ) == 0 )
		return;		// we don't have buildings, so we can't build anything

	if( m_bBuildPlan )
	{
		if( m_iDesiredCostTotal < g_pRTSResource->GetResources( m_iTeamID ) && m_fNextTryTime < gpGlobals->curtime )
		{
			if( BuildGroupScheduleBrush() || BuildGroupScheduleModel() )
			{
				m_iUnitsQueued = 0;
				m_bBuildPlan = false;
				return;
			}

			m_fNextTryTime = gpGlobals->curtime + 1.0f;
		}
	}
	else 
	{
		// cheapest units shouldn't be more expensive then the most expensive unit.
		// they can be equal
		if( m_iCheapestUnit > m_iMostExpensiveUnit )
			return;

		// build info
		m_iDesiredSize = random->RandomInt( 3, GROUP_MAXSIZE);
		m_iDesiredCost =  random->RandomInt( m_iCheapestUnit, m_iMostExpensiveUnit);
		m_iDesiredCostTotal = m_iDesiredCost * m_iDesiredSize;

		// group info
		unit_group_t group;
		group.m_iID = GetNewID();
		group.m_iDesiredSize = m_iDesiredSize;
		group.m_iSize = 0;
		group.m_iType = rand() % 2;
		group.m_iState = GROUP_IDLING;

		m_UnitGroupsBuilding.AddToTail( group );
		m_iLastID = group.m_iID;

		// start queuing
		m_bBuildPlan = true;
		m_fNextTryTime = 0.0f;

		DbgStrAIMsg(1, "Build plan generated, desiredcost %d, teamid %d, resources: %d\n", m_iDesiredCostTotal, m_iTeamID, 
			g_pRTSResource->GetResources( m_iTeamID ));
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_Strategic::AddToGroup( CAI_BaseNPC *pNPC ) 
{
	for( int i = 0; i < m_UnitGroupsBuilding.Count(); i++ )
	{
		if( m_UnitGroupsBuilding.Element( i ).m_iID == pNPC->GetAIGroup() )
		{
			m_UnitGroupsBuilding.Element( i ).m_Group[ m_UnitGroupsBuilding.Element( i ).m_iSize ] = pNPC;
			m_UnitGroupsBuilding.Element( i ).m_iSize++;

			if( m_UnitGroupsBuilding.Element( i ).m_iSize == m_UnitGroupsBuilding.Element( i ).m_iDesiredSize )
			{
				m_UnitGroups.AddToTail( m_UnitGroupsBuilding.Element( i ) );
				m_UnitGroupsBuilding.Remove( i );

				for( int j = 0; j < GROUP_MAXSIZE; j++ )
				{
					if( m_UnitGroups.Element( i ).m_Group[j].Get() )
						m_UnitGroups.Element( i ).m_Group[j].Get()->SetAIReady(true);
				}
			}
			break;
		}
	}		
}

//-----------------------------------------------------------------------------
// Purpose: This looks annoying.
//-----------------------------------------------------------------------------
/*void CAI_Strategic::RemoveFromGroup( CAI_BaseNPC *pNPC, bool ai_ready )
{
	if( ai_ready )
	{
		if( m_UnitGroups.Count() == 0 )
			return;

		for( int i = 0; i < m_UnitGroups.Count(); i++ )
		{
			if( m_UnitGroups.Element( i ).m_iID == pNPC->GetAIGroup() )
			{
				for( int j = 0; j < GROUP_MAXSIZE; j++ )
				{
					if( m_UnitGroups.Element( i ).m_Group[j].Get() == pNPC )
					{
						m_UnitGroups.Element( i ).m_Group[j] = NULL;
						break;
					}
				}
				m_UnitGroups.Element( i ).m_iSize--;

				if( m_UnitGroups.Element( i ).m_iSize == 0 )
					m_UnitGroups.Remove(i);
			}
		}	
	}
	else
	{
		if( m_UnitGroupsBuilding.Count() == 0 )
			return;

		for( int i = 0; i < m_UnitGroupsBuilding.Count(); i++ )
		{
			if( m_UnitGroupsBuilding.Element( i ).m_iID == pNPC->GetAIGroup() )
			{
				for( int j = 0; j < GROUP_MAXSIZE; j++ )
				{
					if( m_UnitGroupsBuilding.Element( i ).m_Group[j].Get() == pNPC )
					{
						m_UnitGroupsBuilding.Element( i ).m_Group[j] = NULL;
						break;
					}
				}
				m_UnitGroupsBuilding.Element( i ).m_iSize--;
				m_UnitGroupsBuilding.Element( i ).m_iDesiredSize--;

				if( m_UnitGroupsBuilding.Element( i ).m_iDesiredSize == 0 )
					m_UnitGroupsBuilding.Remove(i);
			}
		}	
	}
}*/

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CBaseEntity *CAI_Strategic::PickTarget( int element )
{
	CBaseEntity *pTarget = NULL, *pNewTarget;
	int closest_distance = 0, distance = 0;

	switch( m_UnitGroups.Element(element).m_iType )
	{
		case GROUP_DEFENSIVE:
		case GROUP_OFFENSIVE:
			{
				CUtlVector<CBaseEntity *> target_list;

				if( !m_UnitGroups.Element(element).m_Group[0] )
					return NULL;

				// retrieve distance of closest capture point
				for( int i = 0; i < MAX_TEAMS_ID_RTS + DEFAULT_TEAMS; i++ )
				{
					if( !IsValidTarget( i ) )
						continue;

					for( int j = 0; j < g_pRTSResource->m_pCapturePointList->GetCount( i ); j++ )
					{
						if( !pTarget )
						{
							pTarget = g_pRTSResource->m_pCapturePointList->GetFromList( i, j );
							closest_distance = ( pTarget->GetAbsOrigin() - m_UnitGroups.Element(element).m_Group[0]->GetAbsOrigin() ).Length2D();
						}
						else
						{
							pNewTarget = g_pRTSResource->m_pCapturePointList->GetFromList( i, j );
							distance = ( pNewTarget->GetAbsOrigin() - m_UnitGroups.Element(element).m_Group[0]->GetAbsOrigin() ).Length2D();
							if( distance < closest_distance )
							{
								closest_distance = distance;
								pTarget = pNewTarget;
							}
						}
					}
				}

				// now build a list
				for( int i = 0; i < MAX_TEAMS_ID_RTS + DEFAULT_TEAMS; i++ )
				{
					if( !IsValidTarget( i ) )
						continue;

					for( int j = 0; j < g_pRTSResource->m_pCapturePointList->GetCount( i ); j++ )
					{
						pNewTarget = g_pRTSResource->m_pCapturePointList->GetFromList( i, j );
						distance = ( pNewTarget->GetAbsOrigin() - m_UnitGroups.Element(element).m_Group[0]->GetAbsOrigin() ).Length2D();
						if(distance < closest_distance*2)	// allow to be double so far
						{
							target_list.AddToTail(pNewTarget);
						}
					}
				}

				// do we have targets?
				if(target_list.Count() == 0 )
					break;

				// choose a point randomly
				pTarget = target_list.Element(random->RandomInt(0,target_list.Count()-1));
			}
			break;
		default:
			break;
	}

	return pTarget;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CAI_Strategic::IsValidTarget( int tid_target )
{
	if( tid_target == m_iTeamID )
	{
		return false;
	}
	else if( tid_target < DEFAULT_TEAMS )
	{
		return true;
	}
	else 
	{
		return !( g_RelationshipsRTS[m_iTeamID - DEFAULT_TEAMS][tid_target - DEFAULT_TEAMS] ==  D_LI ||
				g_RelationshipsRTS[m_iTeamID - DEFAULT_TEAMS][tid_target - DEFAULT_TEAMS] == D_FR || 
				g_RelationshipsRTS[m_iTeamID - DEFAULT_TEAMS][tid_target - DEFAULT_TEAMS] == D_NU 
				);
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_Strategic::InputEnable()
{
	if( m_bEnabled )
		return;

	m_bEnabled = true;

	ReconsiderCosts();

	SetThink( &CAI_Strategic::StrategicThink );
	SetNextThink( gpGlobals->curtime );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_Strategic::InputDisable()
{
	if( !m_bEnabled )
		return;

	m_bEnabled = false;

	SetThink( NULL );
}

// ##################################################################################
//	>> Strategic manager
// ##################################################################################
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CAI_StrategicManager::CAI_StrategicManager()
{
	srand(time(0));  // Initialize random number generator.
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAI_StrategicManager::Spawn( void )
{
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CAI_StrategicManager::InputEnableStrategicAI( inputdata_t &inputdata ) 
{
	int teamid;

	// make sure it's an int, if it can't be converted just throw it away
	if ( !inputdata.value.Convert(FIELD_INTEGER) )
		return;

	teamid = inputdata.value.Int(); 
	
	g_StrategicAI[ teamid - DEFAULT_TEAMS ]->InputEnable();
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CAI_StrategicManager::InputDisableStrategicAI( inputdata_t &inputdata ) 
{
	int teamid;

	// make sure it's an int, if it can't be converted just throw it away
	if ( !inputdata.value.Convert(FIELD_INTEGER) )
		return;

	teamid = inputdata.value.Int(); 
	
	g_StrategicAI[ teamid - DEFAULT_TEAMS ]->InputDisable();
}
