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

#include "engine/IEngineSound.h"
#include "tier0/dbg.h"
#include "sound.h"
#include "client.h"
#include "vox.h"
#include "icliententity.h"
#include "icliententitylist.h"
#include "enginesingleuserfilter.h"
#include "snd_audio_source.h"
#if defined(_X360)
#include "xmp.h"
#endif
#include "tier0/vprof.h"

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

// HACK:  expose in sound.h maybe?
void DSP_FastReset(int dsp);

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

	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);

	virtual void SetRoomType( IRecipientFilter& filter, int roomType );
	virtual void SetPlayerDSP( IRecipientFilter& filter, int dspType, bool fastReset );

	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();
	virtual bool	IsSoundStillPlaying( int guid );
	virtual void	StopSoundByGuid( int guid );
	// Set's master volume (0.0->1.0)
	virtual void	SetVolumeByGuid( int guid, float fvol );

	// Retrieves list of all active sounds
	virtual void	GetActiveSounds( CUtlVector< SndInfo_t >& sndlist );

	virtual void	PrecacheSentenceGroup( const char *pGroupName );
	virtual void	NotifyBeginMoviePlayback();
	virtual void	NotifyEndMoviePlayback();

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 CEngineSoundClient s_EngineSoundClient;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEngineSoundClient, IEngineSound, 
	IENGINESOUND_CLIENT_INTERFACE_VERSION, s_EngineSoundClient );

IEngineSound *EngineSoundClient()
{
	return &s_EngineSoundClient;
}


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

CEngineSoundClient::~CEngineSoundClient()
{
}


//-----------------------------------------------------------------------------
// Precache a particular sample
//-----------------------------------------------------------------------------
bool CEngineSoundClient::PrecacheSound( const char *pSample, bool bPreload, bool bIsUISound )
{
	CSfxTable *pTable = S_PrecacheSound( pSample );
	if ( pTable )
	{
		if ( bIsUISound )
		{
			S_MarkUISound( pTable );
		}
		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pSample - 
//-----------------------------------------------------------------------------
void CEngineSoundClient::PrefetchSound( const char *pSample )
{
	S_PrefetchSound( pSample, true );
}

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

	int idx = cl.LookupSoundIndex( pSample );
	if ( idx == -1 )
		return false;
	return true;
}

//-----------------------------------------------------------------------------
// Actually does the work of emitting a sound
//-----------------------------------------------------------------------------
void CEngineSoundClient::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*/ )
{
	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
	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;
	}

	int iSoundSource = iEntIndex;

	// Handle client UI sounds
	if ( iSoundSource != SOUND_FROM_UI_PANEL )
	{
		if (iSoundSource < 0)
			iSoundSource = cl.m_nViewEntity;

		// See if local player is a recipient
		int i = 0;
		int c = filter.GetRecipientCount();
		for ( ; i < c ; i++ )
		{
			int index = filter.GetRecipientIndex( i );
			if ( index == cl.m_nPlayerSlot + 1 )
				break;
		}

		// Local player not receiving sound
		if ( i >= c )
			return;
	}

	CSfxTable *pSound = S_PrecacheSound(pSample);
	if (!pSound)
		return;

	Vector vecDummyOrigin;
	Vector vecDirection;
	if ( iSoundSource == SOUND_FROM_UI_PANEL )
	{
		vecDummyOrigin.Init();
		vecDirection.Init();
		pOrigin = &vecDummyOrigin;
		pDirection = &vecDirection;
	}
	else
	{
		// Point at origin if they didn't specify a sound source.
		if (!pOrigin)
		{
			// Try to use the origin of the entity
			IClientEntity *pEnt = entitylist->GetClientEntity( iEntIndex );
			// don't update position if we stop this sound
			if (pEnt && !(iFlags & SND_STOP) )
			{
				vecDummyOrigin = pEnt->GetRenderOrigin();
			}
			else
			{
				vecDummyOrigin.Init();
			}

			pOrigin = &vecDummyOrigin;
		}

		if (!pDirection)
		{
			IClientEntity *pEnt = entitylist->GetClientEntity( iEntIndex );
			if (pEnt && !(iFlags & SND_STOP))
			{
				QAngle angles;
				angles = pEnt->GetAbsAngles();
				AngleVectors( angles, &vecDirection );
			}
			else
			{
				vecDirection.Init();
			}

			pDirection = &vecDirection;
		}
	}

	if ( pUtlVecOrigins )
	{
		(*pUtlVecOrigins).AddToTail( *pOrigin );
	}

	float delay = 0.0f;
	if ( soundtime != 0.0f )
	{
		// this sound was played directly on the client, use its clock sync
		delay = S_ComputeDelayForSoundtime( soundtime, CLOCK_SYNC_CLIENT );
#if 0
		static float lastSoundTime = 0;
		Msg("[%.3f] Play %s at %.3f %.1fsms delay\n", soundtime - lastSoundTime, pSample, soundtime, delay * 1000.0f );
		lastSoundTime = soundtime;
#endif
		// anything over 250ms is assumed to be intentional skipping
		if ( delay <= 0 && delay > -0.250f )
		{
			// leave a little delay to flag the channel in the low-level sound system
			delay = 1e-6f;
		}
	}

	StartSoundParams_t params;
	params.staticsound = iChannel == CHAN_STATIC;
	params.soundsource = iSoundSource;
	params.entchannel = iChannel;
	params.pSfx = pSound;
	params.origin = *pOrigin;
	params.direction = *pDirection;
	params.bUpdatePositions = bUpdatePositions;
	params.fvol = flVolume;
	params.soundlevel = iSoundLevel;
	params.flags = iFlags;
	params.pitch = iPitch;
	params.specialdsp = iSpecialDSP;
	params.fromserver = false;
	params.delay = delay;
	params.speakerentity = speakerentity;

	S_StartSound( params );
}


//-----------------------------------------------------------------------------
// Plays a sentence
//-----------------------------------------------------------------------------
void CEngineSoundClient::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 bUpdatePosition, 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, bUpdatePosition, soundtime, speakerentity );
	}
}


//-----------------------------------------------------------------------------
// Emits a sound
//-----------------------------------------------------------------------------
void CEngineSoundClient::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( "CEngineSoundClient::EmitSound" );
	EmitSound( filter, iEntIndex, iChannel, pSample, flVolume, ATTN_TO_SNDLVL( flAttenuation ), iFlags, 
		iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );

}


void CEngineSoundClient::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( "CEngineSoundClient::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 );
	}
}


//-----------------------------------------------------------------------------
// Stops a sound
//-----------------------------------------------------------------------------
void CEngineSoundClient::StopSound( int iEntIndex, int iChannel, const char *pSample )
{
	CEngineSingleUserFilter filter( cl.m_nPlayerSlot + 1 );
	EmitSound( filter, iEntIndex, iChannel, pSample, 0, SNDLVL_NONE, SND_STOP, PITCH_NORM, 0, 
		NULL, NULL, NULL, true );
}


void CEngineSoundClient::SetRoomType( IRecipientFilter& filter, int roomType )
{
	extern ConVar dsp_room;
	dsp_room.SetValue( roomType );
}

void CEngineSoundClient::SetPlayerDSP( IRecipientFilter& filter, int dspType, bool fastReset )
{
	extern ConVar dsp_player;
	dsp_player.SetValue( dspType );
	if ( fastReset )
	{
		DSP_FastReset( dspType );
	}
}


void CEngineSoundClient::EmitAmbientSound( const char *pSample, float flVolume, 
										  int iPitch, int flags, float soundtime /*= 0.0f*/ )
{
	float delay = 0.0f;
	if ( soundtime != 0.0f )
	{
		delay = soundtime - cl.m_flLastServerTickTime;
	}

	CSfxTable *pSound = S_PrecacheSound(pSample);

	StartSoundParams_t params;
	params.staticsound = true;
	params.soundsource = SOUND_FROM_LOCAL_PLAYER;
	params.entchannel = CHAN_STATIC;
	params.pSfx = pSound;
	params.origin = vec3_origin;
	params.fvol = flVolume;
	params.soundlevel = SNDLVL_NONE;
	params.flags = flags;
	params.pitch = iPitch;
	params.specialdsp = 0;
	params.fromserver = false;
	params.delay = delay;

	S_StartSound( params );
}

void CEngineSoundClient::StopAllSounds(bool bClearBuffers)
{
	S_StopAllSounds( bClearBuffers );
}

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

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

// Client .dll only functions
//-----------------------------------------------------------------------------
// Purpose: 
// Input  :  - 
// Output : int	
//-----------------------------------------------------------------------------
int CEngineSoundClient::GetGuidForLastSoundEmitted()
{
	return S_GetGuidForLastSoundEmitted();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : guid - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CEngineSoundClient::IsSoundStillPlaying( int guid )
{
	return S_IsSoundStillPlaying( guid );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : guid - 
//-----------------------------------------------------------------------------
void CEngineSoundClient::StopSoundByGuid( int guid )
{
	S_StopSoundByGuid( guid );
}

//-----------------------------------------------------------------------------
// Purpose: Retrieves list of all active sounds
// Input  : sndlist - 
//-----------------------------------------------------------------------------
void CEngineSoundClient::GetActiveSounds( CUtlVector< SndInfo_t >& sndlist )
{
	S_GetActiveSounds( sndlist );
}

//-----------------------------------------------------------------------------
// Purpose: Set's master volume (0.0->1.0)
// Input  : guid - 
//			fvol - 
//-----------------------------------------------------------------------------
void CEngineSoundClient::SetVolumeByGuid( int guid, float fvol )
{
	S_SetVolumeByGuid( guid, fvol );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CEngineSoundClient::PrecacheSentenceGroup( const char *pGroupName )
{
	VOX_PrecacheSentenceGroup( this, pGroupName );
}

void CEngineSoundClient::NotifyBeginMoviePlayback()
{
	StopAllSounds(true);
#if _X360
	XMPOverrideBackgroundMusic();
#endif
}

void CEngineSoundClient::NotifyEndMoviePlayback()
{
#if _X360
	XMPRestoreBackgroundMusic();
#endif
}