//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#ifndef SOUNDINFO_H
#define SOUNDINFO_H

#ifdef _WIN32
#pragma once
#endif

#include "bitbuf.h"
#include "const.h"
#include "soundflags.h"
#include "coordsize.h"
#include "mathlib/vector.h"


#define WRITE_DELTA_UINT( name, length )	\
	if ( name == delta->name )		\
		buffer.WriteOneBit(0);		\
	else					\
	{					\
		buffer.WriteOneBit(1);		\
		buffer.WriteUBitLong( name, length ); \
	}

#define READ_DELTA_UINT( name, length )			\
	if ( buffer.ReadOneBit() != 0 )			\
	{ name = buffer.ReadUBitLong( length ); }\
	else { name = delta->name; }

#define WRITE_DELTA_SINT( name, length )	\
	if ( name == delta->name )		\
		buffer.WriteOneBit(0);		\
	else					\
	{					\
		buffer.WriteOneBit(1);		\
		buffer.WriteSBitLong( name, length ); \
	}

#define WRITE_DELTA_SINT_SCALE( name, scale, length )	\
	if ( name == delta->name )		\
	buffer.WriteOneBit(0);		\
	else					\
	{					\
	buffer.WriteOneBit(1);		\
	buffer.WriteSBitLong( name / scale, length ); \
	}

#define READ_DELTA_SINT( name, length )			\
	if ( buffer.ReadOneBit() != 0 )			\
	{ name = buffer.ReadSBitLong( length ); }	\
	else { name = delta->name; }

#define READ_DELTA_SINT_SCALE( name, scale, length )			\
	if ( buffer.ReadOneBit() != 0 )			\
	{ name = scale * buffer.ReadSBitLong( length ); }	\
	else { name = delta->name; }

#define SOUND_SEQNUMBER_BITS	10
#define SOUND_SEQNUMBER_MASK	( (1<<SOUND_SEQNUMBER_BITS) - 1 )

// offset sound delay encoding by 60ms since we encode negative sound delays with less precision
// This means any negative sound delay greater than -100ms will get encoded at full precision
#define SOUND_DELAY_OFFSET					(0.100f)

//-----------------------------------------------------------------------------
struct SoundInfo_t
{
	int				nSequenceNumber;
	int				nEntityIndex;
	int				nChannel;
	const char		*pszName;		// UNDONE: Make this a FilenameHandle_t to avoid bugs with arrays of these
	Vector			vOrigin;
	Vector			vDirection;
	float			fVolume;
	soundlevel_t	Soundlevel;
	bool			bLooping;
	int				nPitch;
	Vector			vListenerOrigin;
	int				nFlags;
	int 			nSoundNum;
	float			fDelay;
	bool			bIsSentence;
	bool			bIsAmbient;
	int				nSpeakerEntity;
	
	//---------------------------------
	
	SoundInfo_t()
	{
		SetDefault();
	}

	void Set(int newEntity, int newChannel, const char *pszNewName, const Vector &newOrigin, const Vector& newDirection, 
			float newVolume, soundlevel_t newSoundLevel, bool newLooping, int newPitch, const Vector &vecListenerOrigin, int speakerentity )
	{
		nEntityIndex = newEntity;
		nChannel = newChannel;
		pszName = pszNewName;
		vOrigin = newOrigin;
		vDirection = newDirection;
		fVolume = newVolume;
		Soundlevel = newSoundLevel;
		bLooping = newLooping;
		nPitch = newPitch;
		vListenerOrigin = vecListenerOrigin;
		nSpeakerEntity = speakerentity;
	}

	void SetDefault()
	{
		fDelay = DEFAULT_SOUND_PACKET_DELAY;
		fVolume = DEFAULT_SOUND_PACKET_VOLUME;
		Soundlevel = SNDLVL_NORM;
		nPitch = DEFAULT_SOUND_PACKET_PITCH;

		nEntityIndex = 0;
		nSpeakerEntity = -1;
		nChannel = CHAN_STATIC;
		nSoundNum = 0;
		nFlags = 0;
		nSequenceNumber = 0;

		pszName = NULL;
	
		bLooping = false;
		bIsSentence = false;
		bIsAmbient = false;
		
		vOrigin.Init();
		vDirection.Init();
		vListenerOrigin.Init();
	}

	void ClearStopFields()
	{
		fVolume = 0;
		Soundlevel = SNDLVL_NONE;
		nPitch = PITCH_NORM;
		pszName = NULL;
		fDelay = 0.0f;
		nSequenceNumber = 0;

		vOrigin.Init();
		nSpeakerEntity = -1;
	}

	// this cries for Send/RecvTables:
	void WriteDelta( SoundInfo_t *delta, bf_write &buffer)
	{
		if ( nEntityIndex == delta->nEntityIndex )
		{
			buffer.WriteOneBit( 0 );
		}
		else
		{
			buffer.WriteOneBit( 1 );
		
			if ( nEntityIndex <= 31)
			{
				buffer.WriteOneBit( 1 );
				buffer.WriteUBitLong( nEntityIndex, 5 );
			}
			else
			{
				buffer.WriteOneBit( 0 );
				buffer.WriteUBitLong( nEntityIndex, MAX_EDICT_BITS );
			}
		}

		WRITE_DELTA_UINT( nSoundNum, MAX_SOUND_INDEX_BITS );

		WRITE_DELTA_UINT( nFlags, SND_FLAG_BITS_ENCODE );

		WRITE_DELTA_UINT( nChannel, 3 );

		buffer.WriteOneBit( bIsAmbient?1:0 );
		buffer.WriteOneBit( bIsSentence?1:0 ); // NOTE: SND_STOP behavior is different depending on this flag

		if ( nFlags != SND_STOP )
		{
			if ( nSequenceNumber == delta->nSequenceNumber )
			{
				// didn't change, most often case
				buffer.WriteOneBit( 1 );
			}
			else if ( nSequenceNumber == (delta->nSequenceNumber+1) )
			{
				// increased by one
				buffer.WriteOneBit( 0 );
				buffer.WriteOneBit( 1 );
			}
			else
			{
				// send full seqnr
				buffer.WriteUBitLong( 0, 2 ); // 2 zero bits
				buffer.WriteUBitLong( nSequenceNumber, SOUND_SEQNUMBER_BITS ); 
			}
						
			if ( fVolume == delta->fVolume )
			{
				buffer.WriteOneBit( 0 );
			}
			else
			{
				buffer.WriteOneBit( 1 );
				buffer.WriteUBitLong( (unsigned int)(fVolume*127.0f), 7 );
			}

			WRITE_DELTA_UINT( Soundlevel, MAX_SNDLVL_BITS );

			WRITE_DELTA_UINT( nPitch, 8 );

			if ( fDelay == delta->fDelay )
			{
				buffer.WriteOneBit( 0 );
			}
			else
			{
				buffer.WriteOneBit( 1 );

				// skipahead works in 10 ms increments
				// bias results so that we only incur the precision loss on relatively large skipaheads
				fDelay += SOUND_DELAY_OFFSET;

				// Convert to msecs
				int iDelay = fDelay * 1000.0f;

				iDelay = clamp( iDelay, (int)(-10 * MAX_SOUND_DELAY_MSEC), (int)(MAX_SOUND_DELAY_MSEC) );

				if ( iDelay < 0 )
				{
					iDelay /=10;	
				}
				
				buffer.WriteSBitLong( iDelay , MAX_SOUND_DELAY_MSEC_ENCODE_BITS );
			}

			// don't transmit sounds with high precision
			WRITE_DELTA_SINT_SCALE( vOrigin.x, 8.0f, COORD_INTEGER_BITS - 2 );
			WRITE_DELTA_SINT_SCALE( vOrigin.y, 8.0f, COORD_INTEGER_BITS - 2  );
			WRITE_DELTA_SINT_SCALE( vOrigin.z, 8.0f, COORD_INTEGER_BITS - 2 );

			WRITE_DELTA_SINT( nSpeakerEntity, MAX_EDICT_BITS + 1 );
		}
		else
		{
			ClearStopFields();
		}
	};

	void ReadDelta( SoundInfo_t *delta, bf_read &buffer)
	{
		if ( !buffer.ReadOneBit() )
		{
			nEntityIndex = delta->nEntityIndex;
		}
		else
		{
			if ( buffer.ReadOneBit() )
			{
				nEntityIndex = buffer.ReadUBitLong( 5 );
			}
			else
			{
				nEntityIndex = buffer.ReadUBitLong( MAX_EDICT_BITS );
			}
		}

		READ_DELTA_UINT( nSoundNum, MAX_SOUND_INDEX_BITS );

		READ_DELTA_UINT( nFlags, SND_FLAG_BITS_ENCODE );

		READ_DELTA_UINT( nChannel, 3 );

		bIsAmbient = buffer.ReadOneBit() != 0;
		bIsSentence = buffer.ReadOneBit() != 0; // NOTE: SND_STOP behavior is different depending on this flag

		if ( nFlags != SND_STOP )
		{
			if ( buffer.ReadOneBit() != 0 )
			{
				nSequenceNumber = delta->nSequenceNumber;
			}
			else if ( buffer.ReadOneBit() != 0 )
			{
				nSequenceNumber = delta->nSequenceNumber + 1;
			}
			else
			{
				nSequenceNumber = buffer.ReadUBitLong( SOUND_SEQNUMBER_BITS );
			}
				
			if ( buffer.ReadOneBit() != 0 )
			{
				fVolume = (float)buffer.ReadUBitLong( 7 )/127.0f;
			}
			else
			{
				fVolume = delta->fVolume;
			}

			if ( buffer.ReadOneBit() != 0 )
			{
				Soundlevel = (soundlevel_t)buffer.ReadUBitLong( MAX_SNDLVL_BITS );
			}
			else
			{
				Soundlevel = delta->Soundlevel;
			}

			READ_DELTA_UINT( nPitch, 8 );


			if ( buffer.ReadOneBit() != 0 )
			{
				// Up to 4096 msec delay
				fDelay = (float)buffer.ReadSBitLong( MAX_SOUND_DELAY_MSEC_ENCODE_BITS ) / 1000.0f; ;
				
				if ( fDelay < 0 )
				{
					fDelay *= 10.0f;
				}
				// bias results so that we only incur the precision loss on relatively large skipaheads
				fDelay -= SOUND_DELAY_OFFSET;
			}
			else
			{
				fDelay = delta->fDelay;
			}

			READ_DELTA_SINT_SCALE( vOrigin.x, 8.0f, COORD_INTEGER_BITS - 2 );
			READ_DELTA_SINT_SCALE( vOrigin.y, 8.0f, COORD_INTEGER_BITS - 2 );
			READ_DELTA_SINT_SCALE( vOrigin.z, 8.0f, COORD_INTEGER_BITS - 2 );

			READ_DELTA_SINT( nSpeakerEntity, MAX_EDICT_BITS + 1 );
		}
		else
		{
			ClearStopFields();
		}
	}
};

struct SpatializationInfo_t
{
	typedef enum
	{
		SI_INCREATION = 0,
		SI_INSPATIALIZATION
	} SPATIALIZATIONTYPE;

	// Inputs
	SPATIALIZATIONTYPE	type;
	// Info about the sound, channel, origin, direction, etc.
	SoundInfo_t			info;

	// Requested Outputs ( NULL == not requested )
	Vector				*pOrigin;
	QAngle				*pAngles;
	float				*pflRadius;
};

#endif // SOUNDINFO_H
