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

#include "engine/IEngineSound.h"
#include "tier0/dbg.h"
#include "quakedef.h"
#include "vox.h"
#include "server.h"
#include "sv_main.h"
#include "edict.h"
#include "sound.h"
#include "host.h"
#include "vengineserver_impl.h"
#include "enginesingleuserfilter.h"
#include "snd_audio_source.h"
#include "soundchars.h"
#include "tier0/vprof.h"

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

//-----------------------------------------------------------------------------
//
// Server-side implementation of the engine sound interface
//
//-----------------------------------------------------------------------------
class CEngineSoundServer : public IEngineSound
{
public:
	// constructor, destructor
	CEngineSoundServer();
	virtual ~CEngineSoundServer();

	virtual bool PrecacheSound( const char *pSample, bool bPreload, bool bIsUISound );
	virtual bool IsSoundPrecached( const char *pSample );
	virtual void PrefetchSound( const char *pSample );

	virtual float GetSoundDuration( const char *pSample );  

	virtual void EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, 
		float flVolume, float flAttenuation, int iFlags, int iPitch, int iSpecialDSP, 
		const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );

	virtual void EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, 
		float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP, 
		const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );

	virtual void EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel, int iSentenceIndex, 
		float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP, 
		const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );

	virtual void StopSound( int iEntIndex, int iChannel, const char *pSample );

	virtual void StopAllSounds( bool bClearBuffers );

	// Set the room type for a player
	virtual void SetRoomType( IRecipientFilter& filter, int roomType );
	virtual void SetPlayerDSP( IRecipientFilter& filter, int dspType, bool fastReset );

	// emit an "ambient" sound that isn't spatialized - specify left/right volume
	// only available on the client, assert on server
	virtual void EmitAmbientSound( const char *pSample, float flVolume, int iPitch, int flags, float soundtime = 0.0f );

	virtual float GetDistGainFromSoundLevel( soundlevel_t soundlevel, float dist );

	// Client .dll only functions
	virtual int		GetGuidForLastSoundEmitted()
	{ 
		Warning( "Can't call GetGuidForLastSoundEmitted from server\n" );
		return 0; 
	}
	virtual bool	IsSoundStillPlaying( int guid )
	{ 
		Warning( "Can't call IsSoundStillPlaying from server\n" );
		return false; 
	}
	virtual void	StopSoundByGuid( int guid )	
	{ 
		Warning( "Can't call StopSoundByGuid from server\n" );
		return; 
	}

	// Retrieves list of all active sounds
	virtual void	GetActiveSounds( CUtlVector< SndInfo_t >& sndlist )
	{ 
		Warning( "Can't call GetActiveSounds from server\n" );
		return; 
	}

	// Set's master volume (0.0->1.0)
	virtual void	SetVolumeByGuid( int guid, float fvol )
	{
		Warning( "Can't call SetVolumeByGuid from server\n" );
		return; 
	}
	virtual void	PrecacheSentenceGroup( const char *pGroupName )
	{
		VOX_PrecacheSentenceGroup( this, pGroupName );
	}
	virtual void	NotifyBeginMoviePlayback()
	{
		AssertMsg( 0, "Not supported" );
	}

	virtual void	NotifyEndMoviePlayback()
	{
		AssertMsg( 0, "Not supported" );
	}


private:
	void EmitSoundInternal( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, 
		float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP, 
		const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );
};


//-----------------------------------------------------------------------------
// Client-server neutral sound interface accessor
//-----------------------------------------------------------------------------
static CEngineSoundServer s_EngineSoundServer;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEngineSoundServer, IEngineSound, 
	IENGINESOUND_SERVER_INTERFACE_VERSION, s_EngineSoundServer );

IEngineSound *EngineSoundServer()
{
	return &s_EngineSoundServer;
}



//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CEngineSoundServer::CEngineSoundServer()
{
}

CEngineSoundServer::~CEngineSoundServer()
{
}


//-----------------------------------------------------------------------------
// Precache a particular sample
//-----------------------------------------------------------------------------
bool CEngineSoundServer::PrecacheSound( const char *pSample, bool bPreload, bool bIsUISound )
{
	int		i;

	if ( pSample && TestSoundChar( pSample, CHAR_SENTENCE ) )
	{
		return true;
	}

	if ( pSample[0] <= ' ' )
	{
		Host_Error( "CEngineSoundServer::PrecacheSound:  Bad string: %s", pSample );
	}
	
	// add the sound to the precache list
	// Start at 1, since 0 is used to indicate an error in the sound precache
	i = SV_FindOrAddSound( pSample, bPreload );
	if ( i >= 0 )
		return true;

	Host_Error( "CEngineSoundServer::PrecacheSound: '%s' overflow", pSample );
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pSample - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CEngineSoundServer::IsSoundPrecached( const char *pSample )
{
	if ( pSample && TestSoundChar(pSample, CHAR_SENTENCE) )
	{
		return true;
	}

	int idx = SV_SoundIndex( pSample );
	if ( idx == -1 )
	{
		return false;
	}

	return true;
}

void CEngineSoundServer::PrefetchSound( const char *pSample )
{
	if ( pSample && TestSoundChar(pSample, CHAR_SENTENCE) )
	{
		return;
	}

	int idx = SV_SoundIndex( pSample );
	if ( idx == -1 )
	{
		return;
	}

	// Tell clients to prefetch the sound
	SVC_Prefetch msg;
	msg.m_fType = SVC_Prefetch::SOUND;
	msg.m_nSoundIndex = idx;

	sv.BroadcastMessage( msg, true, false );
}

//-----------------------------------------------------------------------------
// Stops a sound
//-----------------------------------------------------------------------------
void CEngineSoundServer::EmitSoundInternal( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, 
	float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP, 
	const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime /*= 0.0f*/, int speakerentity /*=-1*/ )
{
	AssertMsg( pDirection == NULL, "Direction specification not currently supported on server sounds" );
	AssertMsg( bUpdatePositions, "Non-updated positions not currently supported on server sounds" );

	if (flVolume < 0 || flVolume > 1)
	{
		Warning ("EmitSound: volume out of bounds = %f\n", flVolume);
		return;
	}

	if ( ( iSoundLevel < soundlevel_t(MIN_SNDLVL_VALUE) ) || ( iSoundLevel > soundlevel_t(MAX_SNDLVL_VALUE) ) )
	{
		Warning ("EmitSound: soundlevel out of bounds = %d\n", iSoundLevel);
		return;
	}

	if (iPitch < 0 || iPitch > 255)
	{
		Warning ("EmitSound: pitch out of bounds = %i\n", iPitch);
		return;
	}

	edict_t *pEdict = (iEntIndex >= 0) ? &sv.edicts[iEntIndex] : NULL; 
	SV_StartSound( filter, pEdict, iChannel, pSample, flVolume, iSoundLevel, 
		iFlags, iPitch, iSpecialDSP, pOrigin, soundtime, speakerentity, pUtlVecOrigins );
}


//-----------------------------------------------------------------------------
// Plays a sentence
//-----------------------------------------------------------------------------
void CEngineSoundServer::EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel, 
	int iSentenceIndex, float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP, 
	const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime /*= 0.0f*/, int speakerentity /*= -1*/ )
{
	if ( iSentenceIndex >= 0 )
	{
		char pName[8];
		Q_snprintf( pName, sizeof(pName), "!%d", iSentenceIndex );
		EmitSoundInternal( filter, iEntIndex, iChannel, pName, flVolume, iSoundLevel, 
			iFlags, iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );
	}
}


//-----------------------------------------------------------------------------
// Emits a sound
//-----------------------------------------------------------------------------
void CEngineSoundServer::EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, 
	float flVolume, float flAttenuation, int iFlags, int iPitch, int iSpecialDSP,
	const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime /*= 0.0f*/, int speakerentity /*= -1*/ )
{
	VPROF( "CEngineSoundServer::EmitSound" );
	EmitSound( filter, iEntIndex, iChannel, pSample, flVolume, ATTN_TO_SNDLVL( flAttenuation ), iFlags, 
		iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );
}
	
	
void CEngineSoundServer::EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample, 
	float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
	const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime /*= 0.0f*/, int speakerentity /*= -1*/ )
{
	VPROF( "CEngineSoundServer::EmitSound" );
	if ( pSample && TestSoundChar(pSample, CHAR_SENTENCE) )
	{
		int iSentenceIndex = -1;
		VOX_LookupString( PSkipSoundChars(pSample), &iSentenceIndex );
		if (iSentenceIndex >= 0)
		{
			EmitSentenceByIndex( filter, iEntIndex, iChannel, iSentenceIndex, flVolume,
				iSoundLevel, iFlags, iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );
		}
		else
		{
			DevWarning( 2, "Unable to find %s in sentences.txt\n", PSkipSoundChars(pSample) );
		}
	}
	else
	{
		EmitSoundInternal( filter, iEntIndex, iChannel, pSample, flVolume, iSoundLevel,
			iFlags, iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );
	}
}

void BuildRecipientList( CUtlVector< edict_t * >& list, const IRecipientFilter& filter )
{
	int c = filter.GetRecipientCount();
	for ( int i = 0; i < c; i++ )
	{
		int playerindex = filter.GetRecipientIndex( i );
		if ( playerindex < 1 || playerindex > sv.GetClientCount() )
			continue;

		CGameClient *cl = sv.Client( playerindex - 1 );
		// Never output to bots
		if ( cl->IsFakeClient() )
			continue;

		if ( !cl->IsSpawned() )
			continue;

		list.AddToTail( cl->edict );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : filter - 
//			roomType - 
//-----------------------------------------------------------------------------
void CEngineSoundServer::SetRoomType( IRecipientFilter& filter, int roomType )
{
	CUtlVector< edict_t * > players;
	BuildRecipientList( players, filter );

	for ( int i = 0 ; i < players.Count(); i++ )
	{
		g_pVEngineServer->ClientCommand( players[ i ], "room_type %i\n", roomType );
	}
}

// Set the dsp preset for a player (client only)
//-----------------------------------------------------------------------------
// Purpose: 
// Input  : filter - 
//			dspType - 
//-----------------------------------------------------------------------------
void CEngineSoundServer::SetPlayerDSP( IRecipientFilter& filter, int dspType, bool fastReset )
{
	Assert( !fastReset );
	if ( fastReset )
	{
		Warning( "SetPlayerDSP:  fastReset only valid from client\n" );
	}

	CUtlVector< edict_t * > players;
	BuildRecipientList( players, filter );

	for ( int i = 0 ; i < players.Count(); i++ )
	{
		g_pVEngineServer->ClientCommand( players[ i ], "dsp_player %i\n", dspType );
	}
}

void CEngineSoundServer::StopAllSounds(bool bClearBuffers)
{
	AssertMsg( 0, "Not supported" );
}

void CEngineSoundServer::EmitAmbientSound( const char *pSample, float flVolume, int iPitch, int flags, float soundtime /*= 0.0f*/ )
{
	AssertMsg( 0, "Not supported" );
}

//-----------------------------------------------------------------------------
// Stops a sound
//-----------------------------------------------------------------------------
void CEngineSoundServer::StopSound( int iEntIndex, int iChannel, const char *pSample )
{
	CEngineRecipientFilter filter;
	filter.AddAllPlayers();
	filter.MakeReliable();

	EmitSound( filter, iEntIndex, iChannel, pSample, 0, SNDLVL_NONE, SND_STOP, PITCH_NORM, 0, 
		NULL, NULL, NULL, true );
}

float SV_GetSoundDuration( const char *pSample )
{
#ifdef SWDS
	return 0; // TODO: make this return a real value (i.e implement an OS independent version of the sound code)
#else
	return AudioSource_GetSoundDuration( PSkipSoundChars( pSample ) );
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pSample - 
// Output : float
//-----------------------------------------------------------------------------
float CEngineSoundServer::GetSoundDuration( const char *pSample )
{
	return Host_GetSoundDuration( pSample );
}

float CEngineSoundServer::GetDistGainFromSoundLevel( soundlevel_t soundlevel, float dist )
{
	return S_GetGainFromSoundLevel( soundlevel, dist );
}

/*
//-----------------------------------------------------------------------------
// FIXME: Move into the CEngineSoundServer class?
//-----------------------------------------------------------------------------
void Host_RestartAmbientSounds()
{
	if (!sv.active)
	{
		return;
	}
	
	
#ifndef SWDS
	const int 	NUM_INFOS = 64;
	SoundInfo_t soundInfo[NUM_INFOS];

	int nSounds = S_GetCurrentStaticSounds( soundInfo, NUM_INFOS, CHAN_STATIC );
	
	for ( int i = 0; i < nSounds; i++)
	{
		if (soundInfo[i].looping && 
			soundInfo[i].entity != -1 )
		{
			Msg("Restarting sound %s...\n", soundInfo[i].name);
			S_StopSound(soundInfo[i].entity, soundInfo[i].channel);
			CEngineRecipientFilter filter;
			filter.AddAllPlayers();

			SV_StartSound( filter, EDICT_NUM(soundInfo[i].entity), 
						   CHAN_STATIC, 
						   soundInfo[i].name, 
						   soundInfo[i].volume,
						   soundInfo[i].soundlevel,
						   0,                    // @Q (toml 05-09-02): Is this correct, or will I need to squirrel away the original flags?
						   soundInfo[i].pitch );
		}
	}
#endif
} */