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

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

//-----------------------------------------------------------------------------
// Purpose: This replaces the old sentence logic that was integrated with the
//			sound code.  Now it is a hierarchical mixer.
//-----------------------------------------------------------------------------
class CSentenceMixer : public CAudioMixer
{
public:
	CSentenceMixer( voxword_t *pWords );
	~CSentenceMixer( void );

	// return number of samples mixed
	virtual int			MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset );
	virtual int			SkipSamples( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset );
	virtual bool		ShouldContinueMixing( void );

	virtual CAudioSource*	GetSource( void );
	
	// get the current position (next sample to be mixed)
	virtual int			GetSamplePosition( void );
	virtual float		ModifyPitch( float pitch );
	virtual float		GetVolumeScale( void );

	// BUGBUG: These are only applied to the current word, not the whole sentence!!!!
	virtual void		SetSampleStart( int newPosition );
	virtual void		SetSampleEnd( int newEndPosition );

	virtual void		SetStartupDelaySamples( int delaySamples );
	virtual int			GetMixSampleSize() { return m_pCurrentWordMixer ? m_pCurrentWordMixer->GetMixSampleSize() : 0; }

	virtual bool		IsReadyToMix();

	virtual int			GetPositionForSave() { return GetSamplePosition(); }
	virtual void		SetPositionFromSaved( int savedPosition ) { SetSampleStart( savedPosition ); }

private:
	CAudioMixer			*LoadWord( int nWordIndex );
	void				FreeWord( int nWordIndex );

	// identifies the active word
	int					m_currentWordIndex;
	CAudioMixer			*m_pCurrentWordMixer;

	// set when a transition to a new word occurs
	bool				m_bNewWord;

	voxword_t			m_VoxWords[CVOXWORDMAX];
	CAudioMixer			*m_pWordMixers[CVOXWORDMAX];
	int					m_nNumWords;
};

CAudioMixer *CreateSentenceMixer( voxword_t *pWords )
{
	if ( pWords )
	{
		return new CSentenceMixer( pWords );
	}

	return NULL;
}

CSentenceMixer::CSentenceMixer( voxword_t *pWords )
{
	// count the expected number of words
	m_nNumWords = 0;
	while ( pWords[m_nNumWords].sfx != NULL )
	{
		// get a private copy of the words
		m_VoxWords[m_nNumWords] = pWords[m_nNumWords];
		m_nNumWords++;
		if ( m_nNumWords >= ARRAYSIZE( m_VoxWords ) )
		{
			// very long sentence, prevent overflow
			break;
		}
	}	

	// startup all the mixers now, this serves as a hint to the audio streamer
	// actual mixing will commence when they are ALL ready
	for ( int nWord = 0; nWord < m_nNumWords; nWord++ )
	{
		// it is possible to get a null mixer (due to wav error, etc)
		// the sentence will skip these words
		m_pWordMixers[nWord] = LoadWord( nWord );
	}
	Assert( m_nNumWords < ARRAYSIZE( m_pWordMixers ) );

	// find first valid word mixer
	m_currentWordIndex = 0;
	m_pCurrentWordMixer = NULL;
	for ( int nWord = 0; nWord < m_nNumWords; nWord++ )
	{
		if ( m_pWordMixers[nWord] )
		{
			m_currentWordIndex = nWord;
			m_pCurrentWordMixer = m_pWordMixers[nWord];
			break;
		}
	}

	m_bNewWord = ( m_pCurrentWordMixer != NULL );	
}

CSentenceMixer::~CSentenceMixer( void )
{
	// free all words
	for ( int nWord = 0; nWord < m_nNumWords; nWord++ )
	{
		FreeWord( nWord );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true if mixing can commence, false otherwise
//-----------------------------------------------------------------------------
bool CSentenceMixer::IsReadyToMix()
{
	if ( !m_pCurrentWordMixer )
	{
		// no word, but mixing has to commence in order to shutdown
		return true;
	}

	// all the words should be available before mixing the sentence
	for ( int nWord = m_currentWordIndex; nWord < m_nNumWords; nWord++ )
	{
		if ( m_pWordMixers[nWord] && !m_pWordMixers[nWord]->IsReadyToMix() )
		{
			// Still waiting for async data to arrive
			return false;
		}
	}

	if ( m_bNewWord )
	{
		m_bNewWord = false;

		int start = m_VoxWords[m_currentWordIndex].start;
		int end = m_VoxWords[m_currentWordIndex].end;
		
		// don't allow overlapped ranges
		if ( end <= start )
		{
			end = 0;
		}

		if ( start || end )
		{
			int sampleCount = m_pCurrentWordMixer->GetSource()->SampleCount();
			if ( start > 0 && start < 100 )
			{
				m_pCurrentWordMixer->SetSampleStart( (int)(sampleCount * 0.01f * start) );
			}
			if ( end > 0 && end < 100 )
			{
				m_pCurrentWordMixer->SetSampleEnd( (int)(sampleCount * 0.01f * end) );
			}
		}
	}

	return true;
}

bool CSentenceMixer::ShouldContinueMixing( void )
{
	if ( m_pCurrentWordMixer )
	{
		// keep mixing until the words run out
		return true;
	}

	return false;
}

CAudioSource *CSentenceMixer::GetSource( void )
{
	if ( m_pCurrentWordMixer )
	{
		return m_pCurrentWordMixer->GetSource();
	}

	return NULL;
}
	
// get the current position (next sample to be mixed)
int CSentenceMixer::GetSamplePosition( void )
{
	if ( m_pCurrentWordMixer )
	{
		return m_pCurrentWordMixer->GetSamplePosition();
	}

	return 0;
}

void CSentenceMixer::SetSampleStart( int newPosition )
{
	if ( m_pCurrentWordMixer )
	{
		m_pCurrentWordMixer->SetSampleStart( newPosition );
	}
}

// End playback at newEndPosition
void CSentenceMixer::SetSampleEnd( int newEndPosition )
{
	if ( m_pCurrentWordMixer )
	{
		m_pCurrentWordMixer->SetSampleEnd( newEndPosition );
	}
}

void CSentenceMixer::SetStartupDelaySamples( int delaySamples )
{
	if ( m_pCurrentWordMixer )
	{
		m_pCurrentWordMixer->SetStartupDelaySamples( delaySamples );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Free a word
//-----------------------------------------------------------------------------
void CSentenceMixer::FreeWord( int nWord )
{
	if ( m_pWordMixers[nWord] )
	{
		delete m_pWordMixers[nWord];
		m_pWordMixers[nWord] = NULL;
	}	

	if ( m_VoxWords[nWord].sfx )
	{
		// If this wave wasn't precached by the game code
		if ( !m_VoxWords[nWord].fKeepCached )
		{
			// If this was the last mixer that had a reference
			if ( m_VoxWords[nWord].sfx->pSource->CanDelete() )
			{
				// free the source
				delete m_VoxWords[nWord].sfx->pSource;
				m_VoxWords[nWord].sfx->pSource = NULL;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Load a word
//-----------------------------------------------------------------------------
CAudioMixer *CSentenceMixer::LoadWord( int nWord )
{
	CAudioMixer *pMixer = NULL;
	if ( m_VoxWords[nWord].sfx )
	{
		CAudioSource *pSource = S_LoadSound( m_VoxWords[nWord].sfx, NULL );
		if ( pSource )
		{
			pSource->SetSentenceWord( true );
			pMixer = pSource->CreateMixer();
		}
	}

	return pMixer;
}

float CSentenceMixer::ModifyPitch( float pitch )
{
	if ( m_pCurrentWordMixer )
	{
		if ( m_VoxWords[m_currentWordIndex].pitch > 0 )
		{
			pitch += (m_VoxWords[m_currentWordIndex].pitch - 100) * 0.01f;
		}
	}
	return pitch;
}

float CSentenceMixer::GetVolumeScale( void )
{
	if ( m_pCurrentWordMixer )
	{
		if ( m_VoxWords[m_currentWordIndex].volume )
		{
			float volume = m_VoxWords[m_currentWordIndex].volume * 0.01;
			if ( volume < 1.0f )
				return volume;
		}
	}
	return 1.0f;
}

int  CSentenceMixer::SkipSamples( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset )
{
	Assert( 0 );
	return 0;
}

// return number of samples mixed
int CSentenceMixer::MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset )
{
	if ( !m_pCurrentWordMixer )
	{
		return 0;
	}

	// save this to compute total output
	int startingOffset = outputOffset;

	while ( sampleCount > 0 && m_pCurrentWordMixer )
	{
		int outputCount = m_pCurrentWordMixer->MixDataToDevice( pDevice, pChannel, sampleCount, outputRate, outputOffset );

		outputOffset += outputCount;
		sampleCount -= outputCount;

		if ( !m_pCurrentWordMixer->ShouldContinueMixing() )
		{
			bool bMouth = SND_IsMouth( pChannel );
			if ( bMouth )
			{
				SND_ClearMouth( pChannel );
			}

			// advance to next valid word mixer
			do 
			{
				m_currentWordIndex++;
				if ( m_currentWordIndex >= m_nNumWords )
				{
					// end of sentence
					m_pCurrentWordMixer = NULL;
					break;
				}
				m_pCurrentWordMixer = m_pWordMixers[m_currentWordIndex];
			}
			while ( m_pCurrentWordMixer == NULL );

			if ( m_pCurrentWordMixer )
			{
				m_bNewWord = true;

				pChannel->sfx = m_VoxWords[m_currentWordIndex].sfx;
				if ( bMouth )
				{
					SND_UpdateMouth( pChannel );
				}
				if ( !IsReadyToMix() )
				{
					// current word isn't ready, stop mixing
					break;
				}
			}
		}
	}

	return outputOffset - startingOffset;
}