//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "c_rts_globalresource.h"
#include <KeyValues.h>
#include <filesystem.h>

#include "vgui_controls/Controls.h"
#include "vgui/ILocalize.h"

// fow
#include "materialsystem/ITexture.h"
#include "hl2wars/c_hl2wars_player.h"
#include "c_ai_basenpc.h"
#include "tier0/vprof.h"
#include "hl2wars/fx_quad2.h"
#include "hl2wars/c_rts_basebuilding.h"
#include "hl2wars/hl2wars_convars.h"
#include "hl2wars/map_bounderies.h"

#include "networkstringtable_clientdll.h"

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

IMPLEMENT_CLIENTCLASS_DT(C_RTSResource, DT_RTSResource, CRTSResource)
	RecvPropArray3( RECVINFO_ARRAY(m_vTeamColorTable), RecvPropVector( RECVINFO( m_vTeamColorTable[0] ) ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_bColorInUse), RecvPropInt( RECVINFO( m_bColorInUse[0] ) ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_bPositionInUse), RecvPropInt( RECVINFO( m_bPositionInUse[0] ) ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_iFaction), RecvPropInt( RECVINFO( m_iFaction[0] ) ) ),

	// receive gameroom info
	RecvPropEHandle( RECVINFO( m_hGameMaster ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_iIndex), RecvPropInt( RECVINFO( m_iIndex[0] ) ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_iPosition), RecvPropInt( RECVINFO( m_iPosition[0] ) ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_iColor), RecvPropInt( RECVINFO( m_iColor[0] ) ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_bReady), RecvPropBool( RECVINFO( m_bReady[0] ) ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_iFaction), RecvPropInt( RECVINFO( m_iFaction[0] ) ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_iType), RecvPropInt( RECVINFO( m_iType[0] ) ) ),
END_RECV_TABLE()

C_RTSResource *g_RTSR;

// ##################################################################################
//	>> Global data
// ##################################################################################
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
C_RTSResource::C_RTSResource()
{
	g_RTSR = this;

	// load hl2wars localize files here
	//g_pVGuiLocalize->AddFile( "resource/hl2wars_%language%.txt" );

	m_iSizeFow = FOW_SIZE / FOW_TILESIZE;
	m_pTex = NULL;

	m_fFogOfWarHeight = 0.0f;

	/*m_pTex = materials->FindTexture("fow/fow", TEXTURE_GROUP_CLIENT_EFFECTS, true);
	if( m_pTex )
		m_pTex->Download();
	else
		Warning("C_RTSResource::C_RTSResource(): Unable to find texture fow/fow!!\n");*/

	// --- end updating texture

	// test
	//m_pTexBuffer = materials->CreateProceduralTexture( "fowbuffer", "", 512, 512, IMAGE_FORMAT_A8, 
	//	TEXTUREFLAGS_NOCOMPRESS|TEXTUREFLAGS_NOMIP|TEXTUREFLAGS_NOLOD|TEXTUREFLAGS_PROCEDURAL|
	//	TEXTUREFLAGS_ONEBITALPHA );
	//CFOWTextureRegen *pTexReg = new CFOWTextureRegen( new CFOWMaterialProxy() );
	//m_pTexBuffer->SetTextureRegenerator( pTexReg );
}

//-----------------------------------------------------------------------------
// Spawn!
//-----------------------------------------------------------------------------
/*void C_RTSResource::Spawn()
{
	BaseClass::Spawn();

	Msg("C_RTSResource::Spawn()\n");
	// get the lowest fog of war height. The quad should be lower then the player or it could cause problems.
	for( CMapBounderies *pEnt = GetMapBounderiesList(); pEnt != NULL; pEnt = pEnt->m_pNext )
	{
		Vector mins, maxs;
		pEnt->CollisionProp()->WorldSpaceAABB( &mins, &maxs );
		Msg("Checking z: %f\n", mins.z );
		if( mins.z < m_fFogOfWarHeight )
			m_fFogOfWarHeight = mins.z;
	}

	m_pTex = materials->FindTexture("fow/fow", TEXTURE_GROUP_CLIENT_EFFECTS, true);
	if( m_pTex )
		m_pTex->Download();
	else
		Warning("C_RTSResource::C_RTSResource(): Unable to find texture fow/fow!!\n");
}*/

//-----------------------------------------------------------------------------
// Activate! This is not being called on the client side :S?!
//-----------------------------------------------------------------------------
void C_RTSResource::Activate()
{
	BaseClass::Activate();

	/*Msg("C_RTSResource::Activate()\n");
	// get the lowest fog of war height. The quad should be lower then the player or it could cause problems.
	for( CMapBounderies *pEnt = GetMapBounderiesList(); pEnt != NULL; pEnt = pEnt->m_pNext )
	{
		Vector mins, maxs;
		pEnt->CollisionProp()->WorldSpaceAABB( &mins, &maxs );
		Msg("Checking z: %f\n", mins.z );
		if( mins.z < m_fFogOfWarHeight )
			m_fFogOfWarHeight = mins.z;
	}

	m_pTex = materials->FindTexture("fow/fow", TEXTURE_GROUP_CLIENT_EFFECTS, true);
	if( m_pTex )
		m_pTex->Download();
	else
		Warning("C_RTSResource::C_RTSResource(): Unable to find texture fow/fow!!\n");*/
}

//-----------------------------------------------------------------------------
// Purpose: Clean up
//-----------------------------------------------------------------------------
void C_RTSResource::UpdateOnRemove()
{
	m_pTex = NULL;
	g_RTSR = NULL;

	if( m_pFogOfWarQuad )
	{
		m_pFogOfWarQuad->DeActivate();
		m_pFogOfWarQuad = NULL;
	}

	m_hGameMaster = NULL;

	for( int i = 0; i < MAX_TEAMS_ID_RTS; i++ )
	{
		m_UnitInfoList[i].Purge();
	}

	m_utlFogInfo.Purge();

	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// handler to do stuff after you are restored
//-----------------------------------------------------------------------------
void C_RTSResource::OnRestore()
{
	g_RTSR = this;

	BaseClass::OnRestore();
}

//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
/*C_RTSAvailableAbilities *C_RTSResource::GetAvailableAbilities( int unit_index )
{
	if( unit_index < 0 || unit_index >= m_iUnitTypePointer )
		return NULL;

	return dynamic_cast<C_RTSAvailableAbilities *>(m_AbilitiesState[unit_index].Get());
}*/

//-----------------------------------------------------------------------------
// Try to get the view distance of this unit. Default to 1024.0f
//-----------------------------------------------------------------------------
float C_RTSResource::GetUnitViewDistance( int unit_index )
{
	FileUnitInfo_t *pFUI = GetFileUnitInfoFromHandle( LookupUnitInfoSlot( STRING(GetUnitType(unit_index) ) ) );
	if( !pFUI )
		return 1024.0f;	

	return pFUI->m_fViewDistance;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_RTSResource::ClientThink()
{
	if( NeedsUpdate() )
	{
		Update();
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
extern CMapBounderies *GetMapBounderiesList();
void C_RTSResource::CalculateFOWHeight()
{
	// get the lowest fog of war height. The quad should be lower then the player or it could cause problems.
	for( CMapBounderies *pEnt = GetMapBounderiesList(); pEnt != NULL; pEnt = pEnt->m_pNext )
	{
		Vector mins, maxs;
		pEnt->CollisionProp()->WorldSpaceAABB( &mins, &maxs );
		if( mins.z < m_fFogOfWarHeight )
			m_fFogOfWarHeight = mins.z;
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_RTSResource::Update()
{
	VPROF_BUDGET( "C_RTSResource::Update", VPROF_BUDGETGROUP_FOGOFWAR );

	m_fNeedsUpdate = gpGlobals->curtime + 0.2f;

	// look if fog of war is disabled, and if we need to clear the fog then
	if( !sv_fogofwar.GetBool() )
	{
		if( m_pFogOfWarQuad )
		{
			m_pFogOfWarQuad->DeActivate();
			m_pFogOfWarQuad = NULL;
		}

		return;
	}

	//int xRecMin = 512, yRecMin = 512, xRecMax = 0, yRecMax = 0;
	if( !m_pFogOfWarQuad )
	{
		if( !m_pTex )
			m_pTex = materials->FindTexture("fow/fow", TEXTURE_GROUP_CLIENT_EFFECTS, true);

		CalculateFOWHeight();
		m_pFogOfWarQuad = FX_AddQuad2( Vector( 0, 0, m_fFogOfWarHeight ), Vector( 0, 0, -1), FOW_SIZE, 1.0f, 270.0f, 0.0f, Vector( 1.0f, 1.0f, 1.0f),
		NULL, "fow/fow", 0, false );	
	}

	// --------- Update fow 
	float view_distance;
	int x, y, radius;
	// get player
	C_HL2Wars_Player *pPlayer = C_HL2Wars_Player::GetLocalHL2WarsPlayer();
	if( !pPlayer )
		return;

	Vector origin = pPlayer->GetAbsOrigin();

	// clear old visibility of units
	for( int i = 0; i < GetSize( pPlayer->GetTeamRTSID() ); i++ )
	{
		C_AI_BaseNPC *pNPC = GetUnit( pPlayer->GetTeamRTSID(), i );
		if( !pNPC || !pNPC->IsAlive())
			continue;

		view_distance = GetUnitViewDistance( pNPC->GetUnitIndex() );

		x = (pNPC->m_vecOldPos.x + (FOW_SIZE / 2)) / (float)FOW_TILESIZE;
		y = (pNPC->m_vecOldPos.y + (FOW_SIZE / 2)) / (float)FOW_TILESIZE;
		radius = view_distance / FOW_TILESIZE;

		if( x < 0 || x >= m_iSizeFow || y < 0 || y >= m_iSizeFow )
			continue;


		for( int i = x - radius; i < x + radius; i++ )
		{
			for( int j = y - radius; j < y + radius; j++ )
			{	
				if( (i - x)*(i - x) + (j - y)*(j - y) < radius * radius )
				{
					m_pFogOfWar[i][j] = false;
				}	
			}		
		}
	}

	// add/clear fog again for positions in foginfo vector. Eventually remove after this is done
	for( int i = 0; i < m_utlFogInfo.Count(); i++ )
	{
		if( m_utlFogInfo.Element( i).delay > gpGlobals->curtime )
			continue;

		x = (m_utlFogInfo.Element( i ).position.x + (FOW_SIZE / 2)) / (float)FOW_TILESIZE;
		y = (m_utlFogInfo.Element( i ).position.y + (FOW_SIZE / 2)) / (float)FOW_TILESIZE;
		radius = m_utlFogInfo.Element( i ).radius / FOW_TILESIZE;

		switch( m_utlFogInfo.Element(i).type )
		{
		case FOW_CLEARREMOVE:
			m_utlFogInfo.Remove( i );
		case FOW_CLEAR:
			for( int k = x - radius; k < x + radius; k++ )
			{
				for( int j = y - radius; j < y + radius; j++ )
				{
					if( (k - x)*(k - x) + (j - y)*(j - y) < radius * radius )
					{
						g_RTSR->m_pFogOfWar[k][j] = true;
					}	
				}		
			}
			break;
		case FOW_ADDREMOVE:
			m_utlFogInfo.Remove( i );
		case FOW_ADD:
			for( int k = x - radius; k < x + radius; k++ )
			{
				for( int j = y - radius; j < y + radius; j++ )
				{
					if( (k - x)*(k - x) + (j - y)*(j - y) < radius * radius )
					{
						m_pFogOfWar[k][j] = false;
					}	
				}		
			}
			
			break;
		default:
			DevMsg("C_RTSResource::Update(): Invalid fog of war info struct!\n");
			m_utlFogInfo.Remove( i );
			break;
		}
	}

	// update unit visibility
	for( int k = 0; k < GetSize( pPlayer->GetTeamRTSID() ); k++ )
	{
		C_AI_BaseNPC *pNPC = GetUnit( pPlayer->GetTeamRTSID(), k );
		if( !pNPC || !pNPC->IsAlive())
			continue;

		view_distance = GetUnitViewDistance( pNPC->GetUnitIndex() );

		Vector origin = pNPC->GetAbsOrigin();
		x = (origin.x + (FOW_SIZE / 2)) / (float)FOW_TILESIZE;
		y = (origin.y + (FOW_SIZE / 2)) / (float)FOW_TILESIZE;
		radius = view_distance / FOW_TILESIZE;

		if( x < 0 || x >= m_iSizeFow || y < 0 || y >= m_iSizeFow )
			continue;

		for( int i = x - radius; i < x + radius; i++ )
		{
			for( int j = y - radius; j < y + radius; j++ )
			{	
				if( (i - x)*(i - x) + (j - y)*(j - y) < radius * radius )
				{
					m_pFogOfWar[i][j] = true;
				}	
			}		
		}

		pNPC->m_vecOldPos.x = origin.x;
		pNPC->m_vecOldPos.y = origin.y;
	}

	// update building visibility
	for( C_BaseBuildingModel *pEnt = GetBuildingModelList(); pEnt != NULL; pEnt = pEnt->m_pNext )
	{
		if( pEnt )
		{
			if( pEnt->GetOwnerShip() != pPlayer->GetTeamRTSID() )
				continue;

			x = (pEnt->GetAbsOrigin().x + (FOW_SIZE / 2)) / (float)FOW_TILESIZE;
			y = (pEnt->GetAbsOrigin().y + (FOW_SIZE / 2)) / (float)FOW_TILESIZE;
			radius = pEnt->GetViewDistance() / FOW_TILESIZE;

			for( int i = x - radius; i < x + radius; i++ )
			{
				for( int j = y - radius; j < y + radius; j++ )
				{	
					if( (i - x)*(i - x) + (j - y)*(j - y) < radius * radius )
					{
						m_pFogOfWar[i][j] = true;
					}	
				}		
			}
		}	
	}

	// --- update texture
	if( m_pTex )
		m_pTex->Download();

	// --- end updating texture
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_RTSResource::NeedsUpdate()
{
	if( m_fNeedsUpdate < gpGlobals->curtime )
		return true;

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : updateType - 
//-----------------------------------------------------------------------------
void C_RTSResource::OnDataChanged( DataUpdateType_t updateType )
{
	BaseClass::OnDataChanged( updateType );

	if ( updateType == DATA_UPDATE_CREATED )
	{
		SetNextClientThink( CLIENT_THINK_ALWAYS );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int C_RTSResource::GetUnitIndex( char *unit_type )
{
	int unit_index;
	for( int i = 0; i < g_pStringTableUnitInfo->GetNumStrings(); i++ )
	{
		if( !stricmp( unit_type, g_pStringTableUnitInfo->GetString(i) ) )
		{
			unit_index = i;
			return unit_index;
		}
	}

	DevMsg("Unit %s not found. Defaulting to 1. \n", unit_type);
	return 1;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *C_RTSResource::GetUnitType( int unit_index )
{
	Assert( unit_index < g_pStringTableUnitInfo->GetMaxStrings() );
	Assert( 1 < g_pStringTableUnitInfo->GetMaxStrings() ); // there should always be two strings in the table, unit_none and unit_unknown.
	
	if( unit_index < g_pStringTableUnitInfo->GetMaxStrings() )
		return g_pStringTableUnitInfo->GetString( unit_index );
	else 
		return g_pStringTableUnitInfo->GetString( 1 );
}

// getters for unit types
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int C_RTSResource::GetAbilityType( int selected_ability, int unit_index )
{
	char ability_entry[MAX_PATH];
	Q_snprintf( ability_entry, sizeof( ability_entry ), "ability_%d", selected_ability );

	FileUnitInfo_t *pFUI = GetFileUnitInfoFromHandle( LookupUnitInfoSlot( STRING( GetUnitType( unit_index ) ) ) );
	if( !pFUI )
		return 1;

	FileAbilityInfo_t *pFAI = GetFileAbilityInfoFromHandle( pFUI->m_iAbilities[selected_ability-1] );
	if( !pFAI )
		return 1;

	return pFAI->m_iAbilityType;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
/*bool C_RTSResource::IsAbilityAvailable( int teamid, ABILITY_FILE_INFO_HANDLE handle )
{
	// first, find the entry
	int index;
	if( m_AbilityStates[teamid-DEFAULT_TEAMS].IsValidIndex( index = m_AbilityStates[teamid-DEFAULT_TEAMS].Find(handle) ) )
	{
		return m_AbilityStates[teamid-DEFAULT_TEAMS].Element(index).m_bAvailable;
	}
	else
	{
		// No entry yet. Means this one is available, since the tech tree didn't do anything with it.
		//NewAbilityState(teamid, true, true, 0, handle );

		return true;
	}	
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool C_RTSResource::IsUnitAvailable( int teamid, UNIT_FILE_INFO_HANDLE handle )
{
	return true;
}*/

//-----------------------------------------------------------------------------
// Purpose: each color value is in a range of 0.0 to 1.0 (float). This function 
//			converts them to a range of 0 to 255 (integer).
//-----------------------------------------------------------------------------
Vector C_RTSResource::GetColor255( int team_id )
{
	Vector color = GetColor( team_id );
	color[0] *= 255;
	color[1] *= 255;
	color[2] *= 255;
	return color;
}


// --------- get/add/remove units
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
C_AI_BaseNPC *C_RTSResource::GetUnit( int team, int index )
{
	// valid team?
	if( !IsValidTeam( team ) )
		return NULL;

	// in the list?
	if( m_UnitInfoList[team  - DEFAULT_TEAMS ].Count() == 0 || index >= m_UnitInfoList[team  - DEFAULT_TEAMS ].Count())
		return NULL;

	// valid pointer?
	C_AI_BaseNPC *pNPC = m_UnitInfoList[team  - DEFAULT_TEAMS ].Element( index ).Get();
	if( !pNPC )
		return NULL;

	return pNPC;
}

//-----------------------------------------------------------------------------
// Purpose: Adds a unit
//-----------------------------------------------------------------------------
void C_RTSResource::AddUnit( int team, C_AI_BaseNPC *pNPC )
{
	// don't add units to team default, neutral and enemy. Players don't control them.
	if( !IsValidTeam( team ) )
		return;

	m_UnitInfoList[ team - DEFAULT_TEAMS ].AddToTail( pNPC );
}

//-----------------------------------------------------------------------------
// Purpose: Removes a unit. The unit behind in the line moves to the front 
//			position, so we don't have gabs.
//-----------------------------------------------------------------------------
void C_RTSResource::RemoveUnit( int team, C_AI_BaseNPC *pNPC )
{
	// don't remove units from team default, neutral and enemy. Players don't control them.
	if( !IsValidTeam( team ) )
		return;

	m_UnitInfoList[ team - DEFAULT_TEAMS ].FindAndRemove( pNPC );
}

//-----------------------------------------------------------------------------
// Purpose: Sets over a unit. Team id still need to be changed!
//-----------------------------------------------------------------------------
void C_RTSResource::ChangeTeamUnit( int oldTeam, int newTeam, C_AI_BaseNPC *pNPC )
{
	if( oldTeam == newTeam ) 
		return;

	RemoveUnit( oldTeam, pNPC );
	AddUnit( newTeam, pNPC );

	// if this npc is putted to the team of the local player, blink, draw the attention.
	C_HL2Wars_Player *pPlayer = C_HL2Wars_Player::GetLocalHL2WarsPlayer();
	if( !pPlayer )
		return;

	if( pPlayer->GetTeamRTSID() == newTeam )
		pNPC->Blink();
}

//-----------------------------------------------------------------------------
// Purpose: Returns the size
//-----------------------------------------------------------------------------
int C_RTSResource::GetSize( int team )
{ 
	return m_UnitInfoList[ team - DEFAULT_TEAMS ].Count();
}

bool C_RTSResource::IsValidTeam( int team_id )
{
	if( team_id < DEFAULT_TEAMS || team_id >= ( MAX_TEAMS_ID_RTS + DEFAULT_TEAMS) )
		return false;

	return true;
}

// ---------------- fog of war related stuff
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool C_RTSResource::IsUnitVisible( C_AI_BaseNPC *pNPC )
{
	VPROF_BUDGET( "C_RTSResource::IsUnitVisible", VPROF_BUDGETGROUP_FOGOFWAR );

	Vector origin = pNPC->GetAbsOrigin();
	int sizeadjust = FOW_SIZE / 2;
	int size = FOW_SIZE / FOW_TILESIZE;
	int x = (origin.x + sizeadjust) / FOW_TILESIZE;
	int y = (origin.y + sizeadjust) / FOW_TILESIZE;

	//Msg("Origin npc: x: %f, y: %f, z: %f. Pos in array: x: %d, y: %d \n", origin.x, origin.y, origin.z, x, y );
	if( x < 0 || x >= size || y < 0 || y >= size )
		return false;

	return m_pFogOfWar[x][y];
}