//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//


#include "server_pch.h"
#include "sv_precache.h"
#include "host.h"
#include "tier0/icommandline.h"
#include "MapReslistGenerator.h"
#include "DownloadListGenerator.h"
#include "soundchars.h"
#ifndef SWDS
#include "vgui_baseui_interface.h"
#endif

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

static ConVar sv_forcepreload( "sv_forcepreload", "0", FCVAR_ARCHIVE, "Force server side preloading.");

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : int SV_ModelIndex
//-----------------------------------------------------------------------------
int SV_ModelIndex (const char *name)
{
	return sv.LookupModelIndex( name );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//			preload - 
// Output : int
//-----------------------------------------------------------------------------
int SV_FindOrAddModel(const char *name, bool preload )
{
	// Look for a match or an empty slot...
	int flags = RES_FATALIFMISSING;
	if ( preload )
	{
		flags |= RES_PRELOAD;
	}

	return sv.PrecacheModel( name, flags );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : int
//-----------------------------------------------------------------------------
int SV_SoundIndex(const char *name)
{
	return sv.LookupSoundIndex( name );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//			preload - 
// Output : int
//-----------------------------------------------------------------------------
int SV_FindOrAddSound(const char *name, bool preload )
{
	// Look for a match or an empty slot...
	int flags = RES_FATALIFMISSING;
	if ( preload )
	{
		flags |= RES_PRELOAD;
	}

	return sv.PrecacheSound( name, flags );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : int
//-----------------------------------------------------------------------------
int SV_GenericIndex(const char *name)
{
	return sv.LookupGenericIndex( name );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//			preload - 
// Output : int
//-----------------------------------------------------------------------------
int SV_FindOrAddGeneric(const char *name, bool preload )
{
	// Look for a match or an empty slot...
	int flags = RES_FATALIFMISSING;
	if ( preload )
	{
		flags |= RES_PRELOAD;
	}

	return sv.PrecacheGeneric( name, flags );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : int
//-----------------------------------------------------------------------------
int SV_DecalIndex(const char *name)
{
	return sv.LookupDecalIndex( name );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//			preload - 
// Output : int
//-----------------------------------------------------------------------------
int SV_FindOrAddDecal(const char *name, bool preload )
{
	// Look for a match or an empty slot...
	int flags = RES_FATALIFMISSING;
	if ( preload )
	{
		flags |= RES_PRELOAD;
	}

	return sv.PrecacheDecal( name, flags );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//-----------------------------------------------------------------------------
void SV_ForceSimpleMaterial( const char *name )
{
	DownloadListGenerator().ForceSimpleMaterial( name );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//			&mins - 
//			&maxs - 
//-----------------------------------------------------------------------------
void SV_ForceModelBounds( const char *name, const Vector &mins, const Vector &maxs )
{
	DownloadListGenerator().ForceModelBounds( name, mins, maxs );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : TABLEID
//-----------------------------------------------------------------------------
INetworkStringTable *CGameServer::GetModelPrecacheTable( void ) const
{
	return m_pModelPrecacheTable;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//			flags - 
//			*model - 
// Output : int
//-----------------------------------------------------------------------------
int CGameServer::PrecacheModel( char const *name, int flags, model_t *model /*=NULL*/ )
{
	if ( !m_pModelPrecacheTable )
		return -1;

	int idx = m_pModelPrecacheTable->AddString( true, name );
	if ( idx == INVALID_STRING_INDEX )
	{
		return -1;
	}

	CPrecacheUserData p;

	// first time, set file size & flags
	CPrecacheUserData const *pExisting = (CPrecacheUserData const *)m_pModelPrecacheTable->GetStringUserData( idx, NULL );
	if ( !pExisting )
	{
		p.flags = flags;
	}
	else
	{
		// Just or in any new flags
		p = *pExisting;
		p.flags |= flags;
	}

	m_pModelPrecacheTable->SetStringUserData( idx, sizeof( p ), &p );

	CPrecacheItem *slot = &model_precache[ idx ];

	if ( model )
	{
		slot->SetModel( model );
	}

	bool bLoadNow;
	bLoadNow = ( !slot->GetModel() && ( ( flags & RES_PRELOAD ) || IsX360() ) );
	if ( CommandLine()->FindParm( "-nopreload" ) ||	CommandLine()->FindParm( "-nopreloadmodels" ))
	{
		bLoadNow = false;
	}
	else if ( sv_forcepreload.GetInt() || CommandLine()->FindParm( "-preload" ) )
	{
		bLoadNow = true;
	}

	if ( idx != 0 )
	{
		if ( bLoadNow )
		{
			slot->SetModel( modelloader->GetModelForName( name, IModelLoader::FMODELLOADER_SERVER ) );
#ifndef SWDS
			EngineVGui()->UpdateProgressBar(PROGRESS_PRECACHE); 
#endif
			MapReslistGenerator().OnModelPrecached(name);
		}
		else
		{
			modelloader->ReferenceModel( name, IModelLoader::FMODELLOADER_SERVER );
			slot->SetModel( NULL );
		}
	}

	return idx;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
// Output : model_t
//-----------------------------------------------------------------------------
model_t *CGameServer::GetModel( int index )
{
	if ( index <= 0 || !m_pModelPrecacheTable )
		return NULL;

	if ( index >= m_pModelPrecacheTable->GetNumStrings() )
	{
		return NULL;
	}

	CPrecacheItem *slot = &model_precache[ index ];
	model_t *m = slot->GetModel();
	if ( m )
	{
		return m;
	}

	char const *modelname = m_pModelPrecacheTable->GetString( index );
	Assert( modelname );

	if ( host_showcachemiss.GetBool() )
	{
		ConDMsg( "server model cache miss on %s\n", modelname );
	}

	m = modelloader->GetModelForName( modelname, IModelLoader::FMODELLOADER_SERVER );
	slot->SetModel( m );

	return m;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : int
//-----------------------------------------------------------------------------
int CGameServer::LookupModelIndex( char const *name )
{
	if ( !m_pModelPrecacheTable )
		return -1;

	int idx = m_pModelPrecacheTable->FindStringIndex( name );
	return ( idx == INVALID_STRING_INDEX ) ? -1 : idx;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : TABLEID
//-----------------------------------------------------------------------------
INetworkStringTable *CGameServer::GetSoundPrecacheTable( void ) const
{
	return m_pSoundPrecacheTable;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//			flags - 
// Output : int
//-----------------------------------------------------------------------------
int CGameServer::PrecacheSound( char const *name, int flags )
{
	if ( !m_pSoundPrecacheTable )
		return -1;

	int idx = m_pSoundPrecacheTable->AddString( true, name );
	if ( idx == INVALID_STRING_INDEX )
	{
		return -1;
	}

	// mark the sound as being precached, but check first that reslist generation is enabled to save on the va() call
	if (MapReslistGenerator().IsEnabled() && name[0])
	{
		MapReslistGenerator().OnResourcePrecached( va( "sound/%s", PSkipSoundChars( name ) ) );
	}

	// first time, set file size & flags
	CPrecacheUserData p;
	CPrecacheUserData const *pExisting = (CPrecacheUserData const *)m_pSoundPrecacheTable->GetStringUserData( idx, NULL );
	if ( !pExisting )
	{
		p.flags = flags;
	}
	else
	{
		// Just or in any new flags
		p = *pExisting;
		p.flags |= flags;
	}

	m_pSoundPrecacheTable->SetStringUserData( idx, sizeof( p ), &p );

	CPrecacheItem *slot = &sound_precache[ idx ];
	slot->SetName( name );

	return idx;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
// Output : char const
//-----------------------------------------------------------------------------
char const *CGameServer::GetSound( int index )
{
	if ( index <= 0 || !m_pSoundPrecacheTable )
	{
		return NULL;
	}

	if ( index >= m_pSoundPrecacheTable->GetNumStrings() )
	{
		return NULL;
	}

	CPrecacheItem *slot = &sound_precache[ index ];
	return slot->GetName();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : int
//-----------------------------------------------------------------------------
int CGameServer::LookupSoundIndex( char const *name )
{
	if ( !m_pSoundPrecacheTable )
		return 0;

	int idx = m_pSoundPrecacheTable->FindStringIndex( name );
	return ( idx == INVALID_STRING_INDEX ) ? 0 : idx;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : TABLEID
//-----------------------------------------------------------------------------
INetworkStringTable *CGameServer::GetGenericPrecacheTable( void ) const
{
	return m_pGenericPrecacheTable;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//			flags - 
// Output : int
//-----------------------------------------------------------------------------
int CGameServer::PrecacheGeneric( char const *name, int flags )
{
	if ( !m_pGenericPrecacheTable )
		return -1;

	int idx = m_pGenericPrecacheTable->AddString( true, name );

	if ( idx == INVALID_STRING_INDEX )
	{
		return -1;
	}

	MapReslistGenerator().OnResourcePrecached( name );

	CPrecacheUserData p;

	// first time, set file size & flags
	CPrecacheUserData const *pExisting = (CPrecacheUserData const *)m_pGenericPrecacheTable->GetStringUserData( idx, NULL );
	if ( !pExisting )
	{
		p.flags = flags;

	}
	else
	{
		// Just or in any new flags
		p = *pExisting;
		p.flags |= flags;
	}

	m_pGenericPrecacheTable->SetStringUserData( idx, sizeof( p ), &p );

	CPrecacheItem *slot = &generic_precache[ idx ];
	slot->SetGeneric( name );
	return idx;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : index - 
// Output : char const
//-----------------------------------------------------------------------------
char const *CGameServer::GetGeneric( int index )
{
	// Bogus index
	if ( index < 0 || !m_pGenericPrecacheTable )
		return "";

	if ( index >= m_pGenericPrecacheTable->GetNumStrings() )
	{
		return "";
	}

	CPrecacheItem *slot = &generic_precache[ index ];
	return slot->GetGeneric();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : int
//-----------------------------------------------------------------------------
int CGameServer::LookupGenericIndex( char const *name )
{
	if ( !m_pGenericPrecacheTable )
		return 0;

	int idx = m_pGenericPrecacheTable->FindStringIndex( name );

	return ( idx == INVALID_STRING_INDEX ) ? 0 : idx;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : TABLEID
//-----------------------------------------------------------------------------
INetworkStringTable *CGameServer::GetDecalPrecacheTable( void ) const
{
	return m_pDecalPrecacheTable;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//			flags - 
// Output : int
//-----------------------------------------------------------------------------
int CGameServer::PrecacheDecal( char const *name, int flags )
{
	if ( !m_pDecalPrecacheTable )
		return -1;

	int idx = m_pDecalPrecacheTable->AddString( true, name );
	if ( idx == INVALID_STRING_INDEX )
	{
		return -1;
	}

	MapReslistGenerator().OnResourcePrecached(name);

	CPrecacheUserData p;

	// first time, set file size & flags
	CPrecacheUserData const *pExisting = (CPrecacheUserData const *)m_pDecalPrecacheTable->GetStringUserData( idx, NULL );
	if ( !pExisting )
	{
		p.flags = flags;
	}
	else
	{
		// Just or in any new flags
		p = *pExisting;
		p.flags |= flags;
	}

	m_pDecalPrecacheTable->SetStringUserData( idx, sizeof( p ), &p );

	CPrecacheItem *slot = &decal_precache[ idx ];
	slot->SetDecal( name );
	return idx;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
// Output : int
//-----------------------------------------------------------------------------
int CGameServer::LookupDecalIndex( char const *name )
{
	if ( !m_pDecalPrecacheTable )
		return -1;

	int idx = m_pDecalPrecacheTable->FindStringIndex( name );
	return ( idx == INVALID_STRING_INDEX ) ? -1 : idx;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CGameServer::DumpPrecacheStats( INetworkStringTable *table )
{
	if ( table == NULL )
	{
		ConMsg( "Can only dump stats when active in a level\n" );
		return;
	}

	CPrecacheItem *items = NULL;
	if ( table == m_pModelPrecacheTable )
	{
		items = model_precache;
	}
	else if ( table == m_pGenericPrecacheTable )
	{
		items = generic_precache;
	}
	else if ( table == m_pSoundPrecacheTable )
	{
		items = sound_precache;
	}
	else if ( table == m_pDecalPrecacheTable )
	{
		items = decal_precache;
	}

	if ( !items )
		return;

	int count = table->GetNumStrings();
	int maxcount = table->GetMaxStrings();

	ConMsg( "\n" );
	ConMsg( "Precache table %s:  %i of %i slots used\n", table->GetTableName(),
		count, maxcount );

	for ( int i = 0; i < count; i++ )
	{
		char const *name = table->GetString( i );
		CPrecacheItem *slot = &items[ i ];
		
		int testLength;
		const CPrecacheUserData *p = ( const CPrecacheUserData * )table->GetStringUserData( i, &testLength );
		ErrorIfNot( testLength == sizeof( *p ),
			("CGameServer::DumpPrecacheStats: invalid CPrecacheUserData length (%d)", testLength)
		);

		if ( !name || !slot || !p )
			continue;

		ConMsg( "%03i:  %s (%s):   ",
			i,
			name, 
			GetFlagString( p->flags ) );

		if ( slot->GetReferenceCount() == 0 )
		{
			ConMsg( " never used\n" );
		}
		else
		{
			ConMsg( " %i refs, first %.2f mru %.2f\n",
				slot->GetReferenceCount(), 
				slot->GetFirstReference(), 
				slot->GetMostRecentReference() );
		}
	}

	ConMsg( "\n" );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CON_COMMAND( sv_precacheinfo, "Show precache info." )
{
	if ( args.ArgC() == 2 )
	{
		char const *table = args[ 1 ];

		bool dumped = true;
		if ( !Q_strcasecmp( table, "generic" ) )
		{
			sv.DumpPrecacheStats( sv.GetGenericPrecacheTable() );
		}
		else if ( !Q_strcasecmp( table, "sound" ) )
		{
			sv.DumpPrecacheStats( sv.GetSoundPrecacheTable() );
		}
		else if ( !Q_strcasecmp( table, "decal" ) )
		{
			sv.DumpPrecacheStats( sv.GetDecalPrecacheTable() );
		}
		else if ( !Q_strcasecmp( table, "model" ) )
		{
			sv.DumpPrecacheStats( sv.GetModelPrecacheTable() );
		}
		else
		{
			dumped = false;
		}

		if ( dumped )
		{
			return;
		}
	}
	
	// Show all data
	sv.DumpPrecacheStats( sv.GetGenericPrecacheTable() );
	sv.DumpPrecacheStats( sv.GetDecalPrecacheTable() );
	sv.DumpPrecacheStats( sv.GetSoundPrecacheTable() );
	sv.DumpPrecacheStats( sv.GetModelPrecacheTable() );
}