//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: RTS data file parsing, shared by game & client dlls.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "hl2wars/rts_parsedata.h"
#include <KeyValues.h>
#include <tier0/mem.h>
#include "filesystem.h"
#include "utldict.h"
#include "datacache/imdlcache.h"
#include "texture_group_names.h"

#ifndef CLIENT_DLL
	#include "util.h"	
	#include "hl2wars/rts_basebuilding.h"
#else
	#include "VGui_BitmapImage.h"
#endif

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

static CUtlDict< FileUnitInfo_t*, unsigned short > m_UnitInfoDatabase;
static CUtlDict< FileAbilityInfo_t*, unsigned short > m_AbilityInfoDatabase;
static CUtlDict< FileFactionInfo_t*, unsigned short > m_FactionInfoDatabase;

// this list corresponds with the ability types from the shareddefs
// this will make the mapping easier in the scripts
static const char *ability_types[] =
{
	"ATYPE_NONE",
	"ATYPE_TURRET",	
	"ATYPE_GRENADE",
	"ATYPE_HEAL",
	"ATYPE_COMB_MINE",
	"ATYPE_BUILDING",
	"ATYPE_STRIDER_CANNON",
};

void PrecacheAllLoaded()
{
	int index = m_AbilityInfoDatabase.First();
	while( m_AbilityInfoDatabase.IsValidIndex(index) )
	{
		PrecacheAbilityData( m_AbilityInfoDatabase.Element(index) );
		index = m_AbilityInfoDatabase.Next(index);
	}
}

void RTSScriptsLevelInit()
{
	PrecacheAllLoaded();
}

void RTSScriptsLevelShutDown()
{
	int index = m_AbilityInfoDatabase.First();
	while( m_AbilityInfoDatabase.IsValidIndex(index) )
	{
		m_AbilityInfoDatabase.Element(index)->m_ProjectTexture.Shutdown();
		index = m_AbilityInfoDatabase.Next(index);
	}	
}

void RTSScriptsShutDown()
{
	int index = m_AbilityInfoDatabase.First();
	while( m_AbilityInfoDatabase.IsValidIndex(index) )
	{
		m_AbilityInfoDatabase.Element(index)->m_ProjectTexture.Shutdown();
		index = m_AbilityInfoDatabase.Next(index);
	}	

	m_UnitInfoDatabase.PurgeAndDeleteElements();
	m_AbilityInfoDatabase.PurgeAndDeleteElements();
	m_FactionInfoDatabase.PurgeAndDeleteElements();
}

//-----------------------------------------------------------------------------
// Functions to read unit data
//-----------------------------------------------------------------------------
FileUnitInfo_t *GetFileUnitInfoFromHandle( UNIT_FILE_INFO_HANDLE handle )
{
	if ( handle < 0 || handle >= m_UnitInfoDatabase.Count() )
	{
		return NULL;
	}

	if ( handle == m_UnitInfoDatabase.InvalidIndex() )
	{
		return NULL;
	}

	return m_UnitInfoDatabase[ handle ];
}

bool ReadUnitInfoData( FileUnitInfo_t *insert, const char *name )
{
	// search the unit data, can be any file named "rts_units_*.txt"
	FileFindHandle_t findHandle;
	const char *pFilename = filesystem->FindFirstEx( "scripts/rts_units_*.txt", "GAME", &findHandle );
	while ( pFilename != NULL )
	{
		char unit_filepath[MAX_PATH];
		Q_snprintf( unit_filepath, sizeof( unit_filepath ), "scripts/%s", pFilename );

		KeyValues *pKV = new KeyValues( "unitsData" );
		if ( !pKV->LoadFromFile( filesystem, unit_filepath, "GAME" ) ) 
		{
			pFilename = filesystem->FindNext( findHandle );
			continue;
		}

		KeyValues *pUnitData = pKV->FindKey( name );
		if( pUnitData ) 
		{
			insert->Parse( pUnitData );
			filesystem->FindClose( findHandle );
			return true;
		}
			
		pFilename = filesystem->FindNext( findHandle );
	}
	filesystem->FindClose( findHandle );

	return false;
}

UNIT_FILE_INFO_HANDLE LookupUnitInfoSlot( const char *name )
{
	// Complain about duplicately defined metaclass names...
	unsigned short lookup = m_UnitInfoDatabase.Find( name );
	if ( lookup != m_UnitInfoDatabase.InvalidIndex() )
	{
		return lookup;
	}

	FileUnitInfo_t *insert = new FileUnitInfo_t();

	if( !ReadUnitInfoData( insert, name ) )
		DevMsg("Missing data for unit %s!\n", name);

	//PrecacheUnitData( insert );

	lookup = m_UnitInfoDatabase.Insert( name, insert );
	Assert( lookup != m_UnitInfoDatabase.InvalidIndex() );
	return lookup;
}

//-----------------------------------------------------------------------------
// FileUnitInfo_t implementation.
//-----------------------------------------------------------------------------

FileUnitInfo_t::FileUnitInfo_t()
{
	m_szImage[0] = 0;
	m_szName[0] = 0;
	m_szDescription[0] = 0;
	m_szUnit[0] = 0;
	m_iCost = 0;
	m_iRechargeTime = 0;
	m_fViewDistance = 0;

	for( int i = 1; i < MAXABILITIESSLOTS + 1; i++ )
		m_iAbilities[i-1] = LookupAbilityInfoSlot("ABILITY_NONE");

	m_pUnitKeyValues = NULL;
	m_pSoundData = NULL;
}

void FileUnitInfo_t::Parse( KeyValues *pKeyValuesData )
{
	Q_strncpy( m_szImage, pKeyValuesData->GetString("image", "vgui/units/unit_unknown.vmt"), MAX_UNIT_STRING );
	Q_strncpy( m_szName, pKeyValuesData->GetString("name"), MAX_UNIT_STRING );
	Q_strncpy( m_szDescription, pKeyValuesData->GetString("description"), MAX_UNIT_STRING );

	Q_strncpy( m_szUnit, pKeyValuesData->GetString("unit"), MAX_UNIT_STRING );
	m_iCost = pKeyValuesData->GetInt("cost", 0);
	m_iRechargeTime = pKeyValuesData->GetInt("time", 0);
	m_fViewDistance = pKeyValuesData->GetFloat("view_distance", 1024.0f );

	for( int i = 1; i < MAXABILITIESSLOTS + 1; i++ ) {
		char ability_data[MAX_PATH];
		Q_snprintf( ability_data, sizeof( ability_data ), "ability_%d", i );

		const char *ability =  pKeyValuesData->GetString(ability_data, "ABILITY_NONE");
		m_iAbilities[i-1] = LookupAbilityInfoSlot(ability);
	}

#ifdef CLIENT_DLL
	m_pImage = new BitmapImage( NULL, m_szImage );
#endif

	m_pUnitKeyValues = pKeyValuesData->FindKey( "unit_keyvalues" );
	m_pSoundData = pKeyValuesData->FindKey( "unit_sounds" );
}

//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
const char *GetFileAbilityName( ABILITY_FILE_INFO_HANDLE handle )
{
	if( handle < 0 || handle >= m_AbilityInfoDatabase.Count() )
		return NULL;

	if( handle == m_AbilityInfoDatabase.InvalidIndex() )
		return NULL;

	return m_AbilityInfoDatabase.GetElementName(handle);
}

//-----------------------------------------------------------------------------
// Functions to read ability data
//-----------------------------------------------------------------------------
FileAbilityInfo_t *GetFileAbilityInfoFromHandle( ABILITY_FILE_INFO_HANDLE handle )
{
	if ( handle < 0 || handle >= m_AbilityInfoDatabase.Count() )
	{
		return NULL;
	}

	if ( handle == m_AbilityInfoDatabase.InvalidIndex() )
	{
		return NULL;
	}

	return m_AbilityInfoDatabase[ handle ];
}

bool ReadAbilityInfoData( FileAbilityInfo_t *insert, const char *name )
{
	// search the unit data, can be any file named "rts_abilities_*.txt"
	FileFindHandle_t findHandle;
	const char *pFilename = filesystem->FindFirstEx( "scripts/rts_abilities_*.txt", "GAME", &findHandle );
	while ( pFilename != NULL )
	{	
		char ability_filepath[MAX_PATH];
		Q_snprintf( ability_filepath, sizeof( ability_filepath ), "scripts/%s", pFilename );

		KeyValues *pKV = new KeyValues( "abilitiesData" );
		if ( !pKV->LoadFromFile( filesystem, ability_filepath, "GAME" ) ) 
		{
			pFilename = filesystem->FindNext( findHandle );
			continue;
		}

		KeyValues *pAbilityData = pKV->FindKey( name );
		if( pAbilityData ) 
		{
			insert->Parse( pAbilityData );
			filesystem->FindClose( findHandle );
			return true;
		}
			
		pFilename = filesystem->FindNext( findHandle );
	}
	filesystem->FindClose( findHandle );

	return false;
}


// FIXME: None existing abilities are causing a disconnect ( Bad_String... )
ABILITY_FILE_INFO_HANDLE LookupAbilityInfoSlot( const char *name )
{
	// Complain about duplicately defined metaclass names...
    unsigned short lookup = m_AbilityInfoDatabase.Find( name );
	if ( lookup != m_AbilityInfoDatabase.InvalidIndex() )
	{
		return lookup;
	}

	FileAbilityInfo_t *insert = new FileAbilityInfo_t();

	if( !ReadAbilityInfoData( insert, name ) )
		DevMsg("Missing data for ability %s!\n", name);

	PrecacheAbilityData( insert );

	lookup = m_AbilityInfoDatabase.Insert( name, insert );
	Assert( lookup != m_AbilityInfoDatabase.InvalidIndex() );
	return lookup;
}

void PrecacheAbilityData( FileAbilityInfo_t *pFAI )
{
	if ( !pFAI->m_szModel[0] )
	{
		Q_strncpy( pFAI->m_szModel, "models/error.mdl", MAX_UNIT_STRING );
	}

#ifndef CLIENT_DLL
	MDLCACHE_CRITICAL_SECTION();

	// this is a bit hacky
	bool allowPrecache = CBaseEntity::IsPrecacheAllowed();
	CBaseEntity::SetAllowPrecache( true );

	if( pFAI->m_szModel )
		CBaseEntity::PrecacheModel(pFAI->m_szModel);

	// some ability types create entities. Precache them
	if( pFAI->m_szBuildEntity[0] )
	{
		CBaseEntity *pEnt = CreateEntityByName( pFAI->m_szBuildEntity );
		if( !pEnt )
			return;

		if( pFAI->m_pAbilityKeyValues )
		{
			for ( KeyValues *sub = pFAI->m_pAbilityKeyValues->GetFirstSubKey(); sub != NULL ; sub = sub->GetNextKey() )
			{
				pEnt->KeyValue( sub->GetName(), sub->GetString() );
			}
		}
		//pEnt->KeyValue( "model", pFAI->m_szModel );
		pEnt->SetModel( pFAI->m_szModel );

		pEnt->Precache();

		// precache components if building
		CBaseBuildingModel *pBuilding = dynamic_cast<CBaseBuildingModel *>(pEnt);
		if( pBuilding )
		{
			pBuilding->PrecacheComponents( pFAI->m_pComponentKeyValues );
		}

		UTIL_RemoveImmediate( pEnt );
	}

	CBaseEntity::SetAllowPrecache( allowPrecache );
#else
	if( pFAI->m_szModel )
		C_BaseEntity::PrecacheModel(pFAI->m_szModel);
#endif

	// precache material reference
	if( stricmp( pFAI->m_szProjectTexture , "") )
	{
		pFAI->m_ProjectTexture.Init( pFAI->m_szProjectTexture, TEXTURE_GROUP_DECAL );
	}
}

int GetAbilityType( const char *name )
{
	for( int i = 0; i < ABILITY_TYPES_COUNT; i++ )
	{
		if (stricmp( name, ability_types[i] ) == 0)
			return i;
	}

	// not found, warn
	Warning("Invalid ability type specified! %s\n", name );
	return ABILITY_NONE;
}

//-----------------------------------------------------------------------------
// FileAbilityInfo_t implementation.
//-----------------------------------------------------------------------------

FileAbilityInfo_t::FileAbilityInfo_t()
{
	m_szImage[0] = 0;
	m_szImageDis[0] = 0;
	m_szName[0] = 0;
	m_szDescription[0] = 0;

	m_iAbilityType = 0;
	m_iCost = 0;
	m_iRechargeTime = 0;
	m_iMinRange = 0;
	m_iMaxRange = 0;
	m_iVisualHelper = 0;
	m_szModel[0] = 0;
	m_bRotate = false;
	m_bSpecPosition = false;
	m_flHealth = 100;
	m_fTolerance = 0.0f;

	m_szBuildEntity[0] = 0;

	m_szProjectTexture[0] = 0;

	m_pSoundData = NULL;
	m_pAbilityKeyValues = NULL;
	m_pComponentKeyValues = NULL;
}

void FileAbilityInfo_t::Parse( KeyValues *pKeyValuesData )
{
	Q_strncpy( m_szImage, pKeyValuesData->GetString("image", "vgui/abilities/ability_unknown.vmt"), MAX_UNIT_STRING );
	Q_strncpy( m_szImageDis, pKeyValuesData->GetString("image_disabled", "vgui/abilities/ability_unknown_dis.vmt"), MAX_UNIT_STRING );
	Q_strncpy( m_szName, pKeyValuesData->GetString("name"), MAX_UNIT_STRING );
	Q_strncpy( m_szDescription, pKeyValuesData->GetString("description"), MAX_UNIT_STRING );

	//m_iAbilityType = pKeyValuesData->GetInt("ability_type", 0);
	m_iAbilityType = GetAbilityType(pKeyValuesData->GetString("ability_type", "ATYPE_NONE"));
	m_iCost = pKeyValuesData->GetInt("cost", 0);
	m_iRechargeTime = pKeyValuesData->GetInt("time", 0);
	m_iMinRange = pKeyValuesData->GetInt("min_range", 0);
	m_iMaxRange = pKeyValuesData->GetInt("max_range", 0);
	m_iVisualHelper = pKeyValuesData->GetInt("visual_helper", 0);
	Q_strncpy( m_szModel, pKeyValuesData->GetString("model", "models/error.mdl" ), MAX_UNIT_STRING );
	m_bRotate = pKeyValuesData->GetInt("specRotate") != 0;
	m_bSpecPosition = pKeyValuesData->GetInt("specPosition") != 0;

#ifdef CLIENT_DLL
	m_pImage = new BitmapImage( NULL, m_szImage );
	m_pImageDis = new BitmapImage( NULL, m_szImageDis );
#endif

	// load building data
	UTIL_StringToVector( m_vOffSet.Base(), pKeyValuesData->GetString("offset", "0 0 0") );

	m_flHealth = pKeyValuesData->GetInt("health", 100 );
	m_fTolerance = pKeyValuesData->GetFloat("tolerance", 0.0f);

	Q_strncpy( m_szBuildEntity, pKeyValuesData->GetString("build_entity", "" ), MAX_UNIT_STRING );
	m_pAbilityKeyValues = pKeyValuesData->FindKey( "ability_keyvalues" );
	m_pComponentKeyValues = pKeyValuesData->FindKey( "building_components" );
	m_pSoundData = pKeyValuesData->FindKey( "ability_sounds" );

	// load projected texture data
	Q_strncpy( m_szProjectTexture, pKeyValuesData->GetString("projected_texture", "" ), MAX_UNIT_STRING );
	if( stricmp( m_szProjectTexture , "") )
	{
		m_ProjectTexture.Init( m_szProjectTexture, TEXTURE_GROUP_DECAL );
		color32 color;
		UTIL_StringToColor32( &color, pKeyValuesData->GetString("pt_color") );
		m_cR = color.r;
		m_cG = color.g;
		m_cB = color.b;
		m_cA = color.a;
		UTIL_StringToVector( m_vPTMin.Base(), pKeyValuesData->GetString("pt_min", "0 0 0") );
		UTIL_StringToVector( m_vPTMax.Base(), pKeyValuesData->GetString("pt_max", "0 0 0") );
	}
}

//-----------------------------------------------------------------------------
// FileFactionInfo_t implementation.
//-----------------------------------------------------------------------------
FileFactionInfo_t *GetTechTree( int element )
{
	return m_FactionInfoDatabase.Element(element);
}

int GetCountTechTrees()
{
	return m_FactionInfoDatabase.Count();
}

void LoadTechTree()
{
	// search the faction data, can be any file named "rts_factions_*.txt"
	FileFindHandle_t findHandle;
	const char *pFilename = filesystem->FindFirstEx( "scripts/rts_factions_*.txt", "GAME", &findHandle );
	while ( pFilename != NULL )
	{	
		char faction_filepath[MAX_PATH];
		Q_snprintf( faction_filepath, sizeof( faction_filepath ), "scripts/%s", pFilename );

		KeyValues *pKV = new KeyValues( "factionData" );
		if ( !pKV->LoadFromFile( filesystem, faction_filepath, "GAME" ) ) 
		{
			pFilename = filesystem->FindNext( findHandle );
			continue;
		}

		// already loaded
		unsigned short lookup = m_FactionInfoDatabase.Find( pKV->GetString("name") );
		if ( lookup != m_FactionInfoDatabase.InvalidIndex() )
		{
			pFilename = filesystem->FindNext( findHandle );
			continue;
		}

		// now create a new faction techtree
		FileFactionInfo_t *insert = new FileFactionInfo_t();
		if( !insert )
		{
			pFilename = filesystem->FindNext( findHandle );
			continue;
		}

		insert->Parse( pKV );

		m_FactionInfoDatabase.Insert( insert->m_szName, insert );
			
		pFilename = filesystem->FindNext( findHandle );
	}
	filesystem->FindClose( findHandle );
}

FileFactionInfo_t::FileFactionInfo_t()
{
	m_szName[0] = 0;
	m_szStartBuilding[0] = 0;

	m_pRoot = NULL;
}

void FileFactionInfo_t::Parse( KeyValues *pKeyValuesData )
{
	Q_strncpy( m_szName, pKeyValuesData->GetString("name", "Rebels"), MAX_FACTION_STRING );
	Q_strncpy( m_szStartBuilding, pKeyValuesData->GetString("start_building", "BUILDING_REB_HQ"), MAX_FACTION_STRING );

	KeyValues *pRootKey = pKeyValuesData->FindKey("tech_tree");
	if( !pRootKey )
		return;

	m_pRoot = (techtree_t *)malloc(sizeof(techtree_t));
	if( !m_pRoot )
	{
		Warning("Failed to load the techtree for faction %s!!!\n", m_szName );
		return;
	}

	Q_strncpy( m_pRoot->m_szName, "root", MAX_FACTION_STRING );
	m_pRoot->m_pParent = NULL;
	m_pRoot->m_pSibling = NULL;
	m_pRoot->m_pChild = NULL;

	m_pRoot->m_pChild = BuildTechTree( m_pRoot, pRootKey->GetFirstSubKey() );
}

techtree_t *FileFactionInfo_t::BuildTechTree( techtree_t *pParent, KeyValues *pTree )
{
	if( !pTree )
		return NULL;

	techtree_t *pEntry = (techtree_t *)malloc(sizeof(techtree_t));
	if( !pEntry )
	{
		Warning("Failed to load the techtree for faction %s!!!\n", m_szName );
		return NULL;
	}

	Q_strncpy( pEntry->m_szName, pTree->GetName(), MAX_FACTION_STRING );
	pEntry->m_iHandle = LookupAbilityInfoSlot(pEntry->m_szName);
	pEntry->m_pParent = pParent;
	pEntry->m_pSibling = NULL;
	pEntry->m_pChild = NULL;

	//Msg("%s in tech tree, handle %d\n", pEntry->m_szName, pEntry->m_iHandle );
	
	//if( pTree->GetFirstSubKey() )
	pEntry->m_pChild = BuildTechTree( pEntry, pTree->GetFirstSubKey() );

	//if( pTree->GetNextKey() )
	pEntry->m_pSibling = BuildTechTree( pEntry, pTree->GetNextKey() );

	return pEntry;
}

techtree_t *FileFactionInfo_t::FindTechInfo( techtree_t *pCur, const char *find )
{
	if( pCur == NULL )
		pCur = m_pRoot->m_pChild;

	if( !stricmp( pCur->m_szName, find)  )
	{
		return pCur;
	}

	if( pCur->m_pChild )
		return FindTechInfo( pCur->m_pChild, find );

	if( pCur->m_pSibling )
		return FindTechInfo( pCur->m_pSibling, find );

	return NULL;
}





