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


#include "cbase.h"
#include "soundscape_system.h"
#include "soundscape.h"
#include "KeyValues.h"
#include "filesystem.h"
#include "game.h"

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

#define SOUNDSCAPE_MANIFEST_FILE				"scripts/soundscapes_manifest.txt"

CON_COMMAND(soundscape_flush, "Flushes the server & client side soundscapes")
{
	CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
	if ( engine->IsDedicatedServer() )
	{
		// If it's a dedicated server, only the server console can run this.
		if ( pPlayer )
			return;
	}
	else
	{
		// If it's a listen server, only the listen server host can run this.
		if ( !pPlayer || pPlayer != UTIL_GetListenServerHost() )
			return;
	}

	g_SoundscapeSystem.FlushSoundscapes();	// don't bother forgetting about the entities
	g_SoundscapeSystem.Init();

	
	if ( engine->IsDedicatedServer() )
	{
		// If the ds console typed it, send it to everyone.
		for ( int i = 1; i <= gpGlobals->maxClients; i++ )
		{
			CBasePlayer	*pSendToPlayer = UTIL_PlayerByIndex( i );
			if ( pSendToPlayer )
				engine->ClientCommand( pSendToPlayer->edict(), "cl_soundscape_flush\n" );
		}
	}
	else
	{
		engine->ClientCommand( pPlayer->edict(), "cl_soundscape_flush\n" );
	}
}

CSoundscapeSystem g_SoundscapeSystem( "CSoundscapeSystem" );

extern ConVar soundscape_debug;

void CSoundscapeSystem::AddSoundscapeFile( const char *filename )
{
	MEM_ALLOC_CREDIT();
	// Open the soundscape data file, and abort if we can't
	KeyValues *pKeyValuesData = new KeyValues( filename );
	if ( pKeyValuesData->LoadFromFile( filesystem, filename, "GAME" ) )
	{
		// parse out all of the top level sections and save their names
		KeyValues *pKeys = pKeyValuesData;
		while ( pKeys )
		{
			if ( pKeys->GetFirstSubKey() )
			{
				if ( g_pDeveloper->GetBool() )
				{
					if ( strstr( pKeys->GetName(), "{" ) )
					{
						Msg("Error parsing soundscape file %s after %s\n", filename, m_soundscapeCount>0 ?m_soundscapes.GetStringText( m_soundscapeCount-1 ) : "FIRST" );
					}
				}
				m_soundscapes.AddString( pKeys->GetName(), m_soundscapeCount );

				if ( IsX360() )
				{
					AddSoundscapeSounds( pKeys, m_soundscapeCount );
				}
				m_soundscapeCount++;
			}
			pKeys = pKeys->GetNextKey();
		}
	}
	pKeyValuesData->deleteThis();
}

CON_COMMAND_F( sv_soundscape_printdebuginfo, "print soundscapes", FCVAR_DEVELOPMENTONLY )
{
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	g_SoundscapeSystem.PrintDebugInfo();
}


void CSoundscapeSystem::PrintDebugInfo()
{
	Msg( "\n------- SERVER SOUNDSCAPES -------\n" );
	for ( int key=m_soundscapes.First(); key != m_soundscapes.InvalidIndex(); key = m_soundscapes.Next( key ) )
	{
		int id = m_soundscapes.GetIDForKey( key );
		const char *pName = m_soundscapes.GetStringForKey( key );

		Msg( "- %d: %s\n", id, pName );
	}
	Msg( "-------- SOUNDSCAPE ENTITIES -----\n" );
	for( int entityIndex = 0; entityIndex < m_soundscapeEntities.Size(); ++entityIndex )
	{
		CEnvSoundscape *currentSoundscape = m_soundscapeEntities[entityIndex];
		Msg("- %d: %s x:%.4f y:%.4f z:%.4f\n", 
			entityIndex, 
			STRING(currentSoundscape->GetSoundscapeName()), 
			currentSoundscape->GetAbsOrigin().x,
			currentSoundscape->GetAbsOrigin().y,
			currentSoundscape->GetAbsOrigin().z
			);
	}
	Msg( "----------------------------------\n\n" );
}

bool CSoundscapeSystem::Init()
{
	m_soundscapeCount = 0;

	const char *mapname = STRING( gpGlobals->mapname );
	const char *mapSoundscapeFilename = NULL;
	if ( mapname && *mapname )
	{
		mapSoundscapeFilename = UTIL_VarArgs( "scripts/soundscapes_%s.txt", mapname );
	}

	KeyValues *manifest = new KeyValues( SOUNDSCAPE_MANIFEST_FILE );
	if( manifest->LoadFromFile( filesystem, SOUNDSCAPE_MANIFEST_FILE, "GAME" ) )
	{
		for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() )
		{
			if ( !Q_stricmp( sub->GetName(), "file" ) )
			{
				// Add
				AddSoundscapeFile( sub->GetString() );
				if ( mapSoundscapeFilename && FStrEq( sub->GetString(), mapSoundscapeFilename ) )
				{
					mapSoundscapeFilename = NULL; // we've already loaded the map's soundscape
				}
				continue;
			}

			Warning( "CSoundscapeSystem::Init:  Manifest '%s' with bogus file type '%s', expecting 'file'\n", 
				SOUNDSCAPE_MANIFEST_FILE, sub->GetName() );
		}

		if ( mapSoundscapeFilename && filesystem->FileExists( mapSoundscapeFilename ) )
		{
			AddSoundscapeFile( mapSoundscapeFilename );
		}
	}
	else
	{
		Error( "Unable to load manifest file '%s'\n", SOUNDSCAPE_MANIFEST_FILE );
	}
	manifest->deleteThis();
	m_activeIndex = 0;

	return true;
}

void CSoundscapeSystem::FlushSoundscapes( void )
{
	m_soundscapeCount = 0;
	m_soundscapes.ClearStrings();
}

void CSoundscapeSystem::Shutdown()
{
	FlushSoundscapes();
	m_soundscapeEntities.RemoveAll();
	m_activeIndex = 0;

	if ( IsX360() )
	{
		m_soundscapeSounds.Purge();
	}
}

void CSoundscapeSystem::LevelInitPreEntity()
{
	g_SoundscapeSystem.Shutdown();
	g_SoundscapeSystem.Init();
}

void CSoundscapeSystem::LevelInitPostEntity()
{
	if ( IsX360() )
	{
		m_soundscapeSounds.Purge();
	}
	CUtlVector<bbox_t> clusterbounds;
	int clusterCount = engine->GetClusterCount();
	clusterbounds.SetCount( clusterCount );
	engine->GetAllClusterBounds( clusterbounds.Base(), clusterCount );
	m_soundscapesInCluster.SetCount(clusterCount);
	for ( int i = 0; i < clusterCount; i++ )
	{
		m_soundscapesInCluster[i].soundscapeCount = 0;
		m_soundscapesInCluster[i].firstSoundscape = 0;
	}
	unsigned char myPVS[16 * 1024];
	CUtlVector<short> clusterIndexList;
	CUtlVector<short> soundscapeIndexList;

	// find the clusters visible from each soundscape
	// add this soundscape to the list of soundscapes for that cluster, clip cluster bounds to radius
	for ( int i = 0; i < m_soundscapeEntities.Count(); i++ )
	{
		Vector position = m_soundscapeEntities[i]->GetAbsOrigin();
		float radius = m_soundscapeEntities[i]->m_flRadius;
		float radiusSq = radius * radius;
		engine->GetPVSForCluster( engine->GetClusterForOrigin( position ), sizeof( myPVS ), myPVS );
		for ( int j = 0; j < clusterCount; j++ )
		{
			if ( myPVS[ j >> 3 ] & (1<<(j&7)) )
			{
				float distSq = CalcSqrDistanceToAABB( clusterbounds[j].mins, clusterbounds[j].maxs, position );
				if ( distSq < radiusSq || radius < 0 )
				{
					m_soundscapesInCluster[j].soundscapeCount++;
					clusterIndexList.AddToTail(j);
					// UNDONE: Technically you just need a soundscape index and a count for this list.
					soundscapeIndexList.AddToTail(i);
				}
			}
		}
	}

	// basically this part is like a radix sort
	// this is how many entries we need in the soundscape index list
	m_soundscapeIndexList.SetCount(soundscapeIndexList.Count());

	// now compute the starting index of each cluster
	int firstSoundscape = 0;
	for ( int i = 0; i < clusterCount; i++ )
	{
		m_soundscapesInCluster[i].firstSoundscape = firstSoundscape;
		firstSoundscape += m_soundscapesInCluster[i].soundscapeCount;
		m_soundscapesInCluster[i].soundscapeCount = 0;
	}
	// now add each soundscape index to the appropriate cluster's list
	// The resulting list is precomputing all soundscapes that need to be checked for a player
	// in each cluster.  This is used to accelerate the per-frame operations
	for ( int i = 0; i < soundscapeIndexList.Count(); i++ )
	{
		int cluster = clusterIndexList[i];
		int outIndex = m_soundscapesInCluster[cluster].soundscapeCount + m_soundscapesInCluster[cluster].firstSoundscape;
		m_soundscapesInCluster[cluster].soundscapeCount++;
		m_soundscapeIndexList[outIndex] = soundscapeIndexList[i];
	}
}

int	CSoundscapeSystem::GetSoundscapeIndex( const char *pName )
{
	return m_soundscapes.GetStringID( pName );
}

bool CSoundscapeSystem::IsValidIndex( int index )
{
	if ( index >= 0 && index < m_soundscapeCount )
		return true;
	return false;
}

void CSoundscapeSystem::AddSoundscapeEntity( CEnvSoundscape *pSoundscape )
{
	if ( m_soundscapeEntities.Find( pSoundscape ) == -1 )
	{
		int index = m_soundscapeEntities.AddToTail( pSoundscape );
		pSoundscape->m_soundscapeEntityId = index + 1;
	}
}

void CSoundscapeSystem::RemoveSoundscapeEntity( CEnvSoundscape *pSoundscape )
{
	m_soundscapeEntities.FindAndRemove( pSoundscape );
	pSoundscape->m_soundscapeEntityId = -1;
}

void CSoundscapeSystem::FrameUpdatePostEntityThink()
{
	int total = m_soundscapeEntities.Count();
	if ( total > 0 )
	{
		int traceCount = 0;
		int playerCount = 0;
		// budget tuned for TF.  Do a max of 20 traces.  That's going to happen anyway because a bunch of the maps
		// use radius -1 for all soundscapes.  So to trace one player you'll often need that many and this code must
		// always trace one player's soundscapes.
		// If the map has been optimized, then allow more players to update per frame.
		int maxPlayers = gpGlobals->maxClients / 2;
		// maxPlayers has to be at least 1
		maxPlayers = MAX( 1, maxPlayers );
		int maxTraces = 20;
		if ( soundscape_debug.GetBool() )
		{
			maxTraces = 9999;
			maxPlayers = MAX_PLAYERS;
		}

		// load balance across server ticks a bit by limiting the numbers of players (get cluster for origin)
		// and traces processed in a single tick.  In single player this will update the player every tick
		// because it always does at least one player's full load of work
		for ( int i = 0; i < gpGlobals->maxClients && traceCount <= maxTraces && playerCount <= maxPlayers; i++ )
		{
			m_activeIndex = (m_activeIndex+1) % gpGlobals->maxClients;
			CBasePlayer *pPlayer = UTIL_PlayerByIndex( m_activeIndex + 1 );
			if ( pPlayer && pPlayer->IsNetClient() )
			{
				// check to see if this is the sound entity that is 
				// currently affecting this player
				audioparams_t &audio = pPlayer->GetAudioParams();

				// if we got this far, we're looking at an entity that is contending
				// for current player sound. the closest entity to player wins.
				CEnvSoundscape *pCurrent = (CEnvSoundscape *)( audio.ent.Get() );
				if ( pCurrent )
				{
					int nEntIndex = pCurrent->m_soundscapeEntityId - 1;
					NOTE_UNUSED( nEntIndex );
					Assert( m_soundscapeEntities[nEntIndex] == pCurrent );
				}
				ss_update_t update;
				update.pPlayer = pPlayer;
				update.pCurrentSoundscape = pCurrent;
				update.playerPosition = pPlayer->EarPosition();
				update.bInRange = false;
				update.currentDistance = 0;
				update.traceCount = 0;
				if ( pCurrent )
				{
					pCurrent->UpdateForPlayer(update);
				}

				int clusterIndex = engine->GetClusterForOrigin( update.playerPosition );
			
				if ( clusterIndex >= 0 && clusterIndex < m_soundscapesInCluster.Count() )
				{
					// find all soundscapes that could possibly attach to this player and update them
					for ( int j = 0; j < m_soundscapesInCluster[clusterIndex].soundscapeCount; j++ )
					{
						int ssIndex = m_soundscapeIndexList[m_soundscapesInCluster[clusterIndex].firstSoundscape + j];
						if ( m_soundscapeEntities[ssIndex] == update.pCurrentSoundscape )
							continue;
						m_soundscapeEntities[ssIndex]->UpdateForPlayer( update );
					}
				}
				playerCount++;
				traceCount += update.traceCount;
			}
		}
	}
}

void CSoundscapeSystem::AddSoundscapeSounds( KeyValues *pSoundscape, int soundscapeIndex )
{
	if ( !IsX360() )
	{
		return;
	}

	int i = m_soundscapeSounds.AddToTail();
	Assert( i == soundscapeIndex );

	KeyValues *pKey = pSoundscape->GetFirstSubKey();
	while ( pKey )
	{
		if ( !Q_strcasecmp( pKey->GetName(), "playlooping" ) )
		{
			KeyValues *pAmbientKey = pKey->GetFirstSubKey();
			while ( pAmbientKey )
			{
				if ( !Q_strcasecmp( pAmbientKey->GetName(), "wave" ) )
				{
					char const *pSoundName = pAmbientKey->GetString();
					m_soundscapeSounds[i].AddToTail( pSoundName );
				}
				pAmbientKey = pAmbientKey->GetNextKey();
			}
		}
		else if ( !Q_strcasecmp( pKey->GetName(), "playrandom" ) )
		{
			KeyValues *pRandomKey = pKey->GetFirstSubKey();
			while ( pRandomKey )
			{
				if ( !Q_strcasecmp( pRandomKey->GetName(), "rndwave" ) )
				{
					KeyValues *pRndWaveKey = pRandomKey->GetFirstSubKey();
					while ( pRndWaveKey )
					{
						if ( !Q_strcasecmp( pRndWaveKey->GetName(), "wave" ) )
						{
							char const *pSoundName = pRndWaveKey->GetString();
							m_soundscapeSounds[i].AddToTail( pSoundName );
						}
						pRndWaveKey = pRndWaveKey->GetNextKey();
					}
				}
				pRandomKey = pRandomKey->GetNextKey();
			}
		}
		else if ( !Q_strcasecmp( pKey->GetName(), "playsoundscape" ) )
		{
			KeyValues *pPlayKey = pKey->GetFirstSubKey();
			while ( pPlayKey )
			{
				if ( !Q_strcasecmp( pPlayKey->GetName(), "name" ) )
				{
					char const *pSoundName = pPlayKey->GetString();
					m_soundscapeSounds[i].AddToTail( pSoundName );
				}
				pPlayKey = pPlayKey->GetNextKey();
			}
		}
		pKey = pKey->GetNextKey();
	}
}

void CSoundscapeSystem::PrecacheSounds( int soundscapeIndex )
{
	if ( !IsX360() )
	{
		return;
	}

	if ( !IsValidIndex( soundscapeIndex ) )
	{
		return;
	}

	int count = m_soundscapeSounds[soundscapeIndex].Count();
	for ( int i=0; i<count; i++ )
	{
		const char *pSound = m_soundscapeSounds[soundscapeIndex][i];
		if ( Q_stristr( pSound, ".wav" ) )
		{
			CBaseEntity::PrecacheSound( pSound );
		}
		else
		{
			// recurse into new soundscape
			PrecacheSounds( GetSoundscapeIndex( pSound ) );
		}
	}
}