#include "global.h"
#include "RageSoundDriver.h"

#include "RageLog.h"
#include "RageSound.h"
#include "RageUtil.h"
#include "RageSoundMixBuffer.h"
#include "RageSoundReader.h"

static const int channels = 2;

static int frames_to_buffer;

/* 512 is about 10ms, which is big enough for the tolerance of most schedulers. */
static int chunksize() { return 512; }

static int underruns = 0, logged_underruns = 0;

RageSoundDriver::Sound::Sound()
{
	m_pSound = NULL;
	m_State = AVAILABLE;
	m_bPaused = false;
}

void RageSoundDriver::Sound::Allocate( int iFrames )
{
	/* Reserve enough blocks in the buffer to hold the buffer.  Add one, to account for
	 * the fact that we may have a partial block due to a previous Mix() call. */
	const int iFramesPerBlock = samples_per_block / channels;
	const int iBlocksToPrebuffer = iFrames / iFramesPerBlock;
	m_Buffer.reserve( iBlocksToPrebuffer + 1 );
	m_PosMapQueue.reserve( 32 );
}

void RageSoundDriver::Sound::Deallocate()
{
	m_Buffer.reserve( 0 );
	m_PosMapQueue.reserve( 0 );
}

int RageSoundDriver::DecodeThread_start( void *p )
{
	((RageSoundDriver *) p)->DecodeThread();
	return 0;
}

static int g_iTotalAhead = 0;
static int g_iTotalAheadCount = 0;

RageSoundMixBuffer &RageSoundDriver::MixIntoBuffer( int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
{
	ASSERT_M( m_DecodeThread.IsCreated(), "RageSoundDriver::StartDecodeThread() was never called" );

	if( iFrameNumber - iCurrentFrame + iFrames > 0 )
	{
		g_iTotalAhead += (int) (iFrameNumber - iCurrentFrame + iFrames);
		++g_iTotalAheadCount;
	}

	static RageSoundMixBuffer mix;

	for( unsigned i = 0; i < ARRAYLEN(m_Sounds); ++i )
	{
		/* s.m_pSound can not safely be accessed from here. */
		Sound &s = m_Sounds[i];
		if( s.m_State == Sound::HALTING )
		{
			/* This indicates that this stream can be reused. */
			s.m_bPaused = false;
			s.m_State = Sound::STOPPED;

//			LOG->Trace("set %p from HALTING to STOPPED", m_Sounds[i].m_pSound);
			continue;
		}

		if( s.m_State != Sound::STOPPING && s.m_State != Sound::PLAYING )
			continue;

		/* STOPPING or PLAYING.  Read sound data. */
		if( m_Sounds[i].m_bPaused )
			continue;

		int iGotFrames = 0;
		int iFramesLeft = iFrames;

		/* Does the sound have a start time? */
		if( !s.m_StartTime.IsZero() && iCurrentFrame != -1 )
		{
			/* If the sound is supposed to start at a time past this buffer, insert silence. */
			const int64_t iFramesUntilThisBuffer = iFrameNumber - iCurrentFrame;
			const float fSecondsBeforeStart = -s.m_StartTime.Ago();
			const int64_t iFramesBeforeStart = int64_t(fSecondsBeforeStart * GetSampleRate());
			const int iSilentFramesInThisBuffer = clamp( int(iFramesBeforeStart-iFramesUntilThisBuffer), 0, iFramesLeft );

			iGotFrames += iSilentFramesInThisBuffer;
			iFramesLeft -= iSilentFramesInThisBuffer;

			/* If we didn't completely fill the buffer, then we've written all of the silence. */
			if( iFramesLeft )
				s.m_StartTime.SetZero();
		}

		/* Fill actual data. */
		sound_block *p[2];
		unsigned pSize[2];
		s.m_Buffer.get_read_pointers( p, pSize );

		while( iFramesLeft && pSize[0] )
		{
			if( !p[0]->m_FramesInBuffer )
			{
				/* We've processed all of the sound in this block.  Mark it read. */
				s.m_Buffer.advance_read_pointer( 1 );
				++p[0];
				--pSize[0];

				/* If we have more data in p[0], keep going. */
				if( pSize[0] )
					continue; // more data

				/* We've used up p[0].  Try p[1]. */
				swap( p[0], p[1] );
				swap( pSize[0], pSize[1] );
				continue;
			}

			/* Note that, until we call advance_read_pointer, we can safely write to p[0]. */
			const int frames_to_read = min( iFramesLeft, p[0]->m_FramesInBuffer );
			mix.SetWriteOffset( iGotFrames*channels );
			mix.write( p[0]->m_BufferNext, frames_to_read * channels );

			{
				Sound::QueuedPosMap pos;
				pos.iStreamFrame = iFrameNumber+iGotFrames;
				pos.iHardwareFrame = p[0]->m_iPosition;
				pos.iFrames = frames_to_read;

				s.m_PosMapQueue.write( &pos, 1 );
			}

			p[0]->m_BufferNext += frames_to_read*channels;
			p[0]->m_FramesInBuffer -= frames_to_read;
			p[0]->m_iPosition += frames_to_read;

//			LOG->Trace( "incr fr rd += %i (state %i) (%p)",
//				(int) frames_to_read, s.m_State, s.m_pSound );

			iGotFrames += frames_to_read;
			iFramesLeft -= frames_to_read;
		}

		/* If we don't have enough to fill the buffer, we've underrun. */
		if( iGotFrames < iFrames && s.m_State == Sound::PLAYING )
			++underruns;
	}

	return mix;
}

void RageSoundDriver::Mix( int16_t *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
{
	memset( pBuf, 0, iFrames*channels*sizeof(int16_t) );
	MixIntoBuffer( iFrames, iFrameNumber, iCurrentFrame ).read( pBuf );
}

void RageSoundDriver::Mix( float *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
{
	memset( pBuf, 0, iFrames*channels*sizeof(float) );
	MixIntoBuffer( iFrames, iFrameNumber, iCurrentFrame ).read( pBuf );
}

void RageSoundDriver::DecodeThread()
{
	SetupDecodingThread();

	while( !m_bShutdownDecodeThread )
	{
		/* Fill each playing sound, round-robin. */
		{
			int iSampleRate = GetSampleRate();
			ASSERT_M( iSampleRate > 0, ssprintf("%i", iSampleRate) );
			int iUsecs = 1000000*chunksize() / iSampleRate;
			usleep( iUsecs );
		}

		LockMut( m_Mutex );
//		LOG->Trace("begin mix");

		for( unsigned i = 0; i < ARRAYLEN(m_Sounds); ++i )
		{
			if( m_Sounds[i].m_State != Sound::PLAYING )
				continue;

			Sound *pSound = &m_Sounds[i];

			CHECKPOINT;
			while( pSound->m_Buffer.num_writable() )
			{
				int iWrote = GetDataForSound( *pSound );
				if( iWrote == RageSoundReader::WOULD_BLOCK )
					break;
				if( iWrote < 0 )
				{
					/* This sound is finishing. */
					pSound->m_State = Sound::STOPPING;
					break;
//					LOG->Trace("mixer: (#%i) eof (%p)", i, pSound->m_pSound );
				}
			}
		}
//		LOG->Trace("end mix");
	}
}

/* Buffer a block of sound data for the given sound.  Return the number of
 * frames buffered, or a RageSoundReader return code. */
int RageSoundDriver::GetDataForSound( Sound &s )
{
	sound_block *p[2];
	unsigned psize[2];
	s.m_Buffer.get_write_pointers( p, psize );

	/* If we have no open buffer slot, we have a buffer overflow. */
	ASSERT( psize[0] > 0 );

	sound_block *pBlock = p[0];
	int size = ARRAYLEN(pBlock->m_Buffer)/channels;
	int iRet = s.m_pSound->GetDataToPlay( pBlock->m_Buffer, size, pBlock->m_iPosition, pBlock->m_FramesInBuffer );
	if( iRet > 0 )
	{
		pBlock->m_BufferNext = pBlock->m_Buffer;
		s.m_Buffer.advance_write_pointer( 1 );
	}

//	LOG->Trace( "incr fr wr %i (state %i) (%p)",
//		(int) pBlock->m_FramesInBuffer, s.m_State, s.m_pSound );

	return iRet;
}


void RageSoundDriver::Update()
{
	m_Mutex.Lock();
	for( unsigned i = 0; i < ARRAYLEN(m_Sounds); ++i )
	{
		{
			Sound::QueuedPosMap p;
			while( m_Sounds[i].m_PosMapQueue.read( &p, 1 ) )
			{
				RageSoundBase *pSound = m_Sounds[i].m_pSound;
				if( pSound != NULL )
					pSound->CommitPlayingPosition( p.iStreamFrame, p.iHardwareFrame, p.iFrames );
			}
		}

		switch( m_Sounds[i].m_State )
		{
		case Sound::STOPPED:
			m_Sounds[i].Deallocate();
			m_Sounds[i].m_State = Sound::AVAILABLE;
			continue;
		case Sound::STOPPING:
			break;
		default:
			continue;
		}

		if( m_Sounds[i].m_Buffer.num_readable() != 0 )
			continue;

//		LOG->Trace("finishing sound %i", i);

		m_Sounds[i].m_pSound->SoundIsFinishedPlaying();
		m_Sounds[i].m_pSound = NULL;

		/* This sound is done.  Set it to HALTING, since the mixer thread might
		 * be accessing it; it'll change it back to STOPPED once it's ready to
		 * be used again. */
		m_Sounds[i].m_State = Sound::HALTING;
//		LOG->Trace("set (#%i) %p from STOPPING to HALTING", i, m_Sounds[i].m_pSound);
	}

	static float fNext = 0;
	if( RageTimer::GetTimeSinceStart() >= fNext )
	{
		/* Lockless: only Mix() can write to underruns. */
		int current_underruns = underruns;
		if( current_underruns > logged_underruns )
		{
			LOG->MapLog( "GenericMixingUnderruns", "Mixing underruns: %i", current_underruns - logged_underruns );
			LOG->Trace( "Mixing underruns: %i", current_underruns - logged_underruns );
			logged_underruns = current_underruns;

			/* Don't log again for at least a second, or we'll burst output
			 * and possibly cause more underruns. */
			fNext = RageTimer::GetTimeSinceStart() + 1;
		}
	}

	m_Mutex.Unlock();
}

void RageSoundDriver::StartMixing( RageSoundBase *pSound )
{
	/* Lock available m_Sounds[], and reserve a slot. */
	m_SoundListMutex.Lock();

	unsigned i;
	for( i = 0; i < ARRAYLEN(m_Sounds); ++i )
		if( m_Sounds[i].m_State == Sound::AVAILABLE )
			break;
	if( i == ARRAYLEN(m_Sounds) )
	{
		m_SoundListMutex.Unlock();
		return;
	}

	Sound &s = m_Sounds[i];
	s.m_State = Sound::BUFFERING;

	/* We've reserved our slot; we can safely unlock now.  Don't hold onto it longer
	 * than needed, since prebuffering might take some time. */
	m_SoundListMutex.Unlock();

	s.m_pSound = pSound;
	s.m_StartTime = pSound->GetStartTime();
	s.m_Buffer.clear();

	/* Initialize the sound buffer. */
	int BufferSize = frames_to_buffer;

	s.Allocate( BufferSize );

//	LOG->Trace("StartMixing(%s) (%p)", s.m_pSound->GetLoadedFilePath().c_str(), s.m_pSound );

	/* Prebuffer some frames before changing the sound to PLAYING. */
	while( s.m_Buffer.num_writable() )
	{
//		LOG->Trace("StartMixing: (#%i) buffering %i (%i writable) (%p)", i, (int) frames_to_buffer, s.buffer.num_writable(), s.m_pSound );
		int iWrote = GetDataForSound( s );
		if( iWrote < 0 )
			break;
	}

	s.m_State = Sound::PLAYING;

//	LOG->Trace("StartMixing: (#%i) finished prebuffering(%s) (%p)", i, s.m_pSound->GetLoadedFilePath().c_str(), s.m_pSound );
}

void RageSoundDriver::StopMixing( RageSoundBase *pSound )
{
	/* Lock, to make sure the decoder thread isn't running on this sound while we do this. */
	m_Mutex.Lock();

	/* Find the sound. */
	unsigned i;
	for( i = 0; i < ARRAYLEN(m_Sounds); ++i )
		if( m_Sounds[i].m_State != Sound::AVAILABLE && m_Sounds[i].m_pSound == pSound )
			break;
	if( i == ARRAYLEN(m_Sounds) )
	{
		m_Mutex.Unlock();
		LOG->Trace( "not stopping a sound because it's not playing" );
		return;
	}

	/* If we're already in STOPPED, there's nothing to do. */
	if( m_Sounds[i].m_State == Sound::STOPPED )
	{
		m_Mutex.Unlock();
		LOG->Trace( "not stopping a sound because it's already in STOPPED" );
		return;
	}

//	LOG->Trace("StopMixing: set %p (%s) to HALTING", m_Sounds[i].m_pSound, m_Sounds[i].m_pSound->GetLoadedFilePath().c_str());

	/* Tell the mixing thread to flush the buffer.  We don't have to worry about
	 * the decoding thread, since we've locked m_Mutex. */
	m_Sounds[i].m_State = Sound::HALTING;

	/* Invalidate the m_pSound pointer to guarantee we don't make any further references to
	 * it.  Once this call returns, the sound may no longer exist. */
	m_Sounds[i].m_pSound = NULL;
//	LOG->Trace("end StopMixing");

	m_Mutex.Unlock();

	pSound->SoundIsFinishedPlaying();
}


bool RageSoundDriver::PauseMixing( RageSoundBase *pSound, bool bStop )
{
	LockMut( m_Mutex );

	/* Find the sound. */
	unsigned i;
	for( i = 0; i < ARRAYLEN(m_Sounds); ++i )
		if( m_Sounds[i].m_State != Sound::AVAILABLE && m_Sounds[i].m_pSound == pSound )
			break;

	/* A sound can be paused in PLAYING or STOPPING.  (STOPPING means the sound
	 * has been decoded to the end, and we're waiting for that data to finish, so
	 * externally it looks and acts like PLAYING.) */
	if( i == ARRAYLEN(m_Sounds) ||
		(m_Sounds[i].m_State != Sound::PLAYING && m_Sounds[i].m_State != Sound::STOPPING) )
	{
		LOG->Trace( "not pausing a sound because it's not playing" );
		return false;
	}

	m_Sounds[i].m_bPaused = bStop;

	return true;
}

void RageSoundDriver::StartDecodeThread()
{
	ASSERT( !m_DecodeThread.IsCreated() );

	m_DecodeThread.Create( DecodeThread_start, this );
}

void RageSoundDriver::SetDecodeBufferSize( int iFrames )
{
	ASSERT( !m_DecodeThread.IsCreated() );

	frames_to_buffer = iFrames;
}

RageSoundDriver::RageSoundDriver():
	m_Mutex("RageSoundDriver"),
	m_SoundListMutex("SoundListMutex")
{
	m_bShutdownDecodeThread = false;
	m_iMaxHardwareFrame = 0;
	SetDecodeBufferSize( 4096 );
	
	m_DecodeThread.SetName("Decode thread");
}

RageSoundDriver::~RageSoundDriver()
{
	/* Signal the decoding thread to quit. */
	if( m_DecodeThread.IsCreated() )
	{
		m_bShutdownDecodeThread = true;
		LOG->Trace("Shutting down decode thread ...");
		LOG->Flush();
		m_DecodeThread.Wait();
		LOG->Trace("Decode thread shut down.");
		LOG->Flush();

		LOG->Info( "Mixing %f ahead in %i Mix() calls",
			float(g_iTotalAhead) / max( g_iTotalAheadCount, 1 ), g_iTotalAheadCount );
	}
}

int64_t RageSoundDriver::ClampHardwareFrame( int64_t iHardwareFrame ) const
{
	/* It's sometimes possible for the hardware position to move backwards, usually
	 * on underrun.  We can try to prevent this in each driver, but it's an obscure
	 * error, so let's clamp the result here instead. */
	if( iHardwareFrame < m_iMaxHardwareFrame )
	{
		/* Clamp the output to one per second, so one underruns don't cascade due to
		 * output spam. */
		static RageTimer last(RageZeroTimer);
		if( last.IsZero() || last.Ago() > 1.0f )
		{
			LOG->Trace( "RageSoundDriver: driver returned a lesser position (%d < %d)",
				    (int)iHardwareFrame, (int)m_iMaxHardwareFrame );
			last.Touch();
		}
		return m_iMaxHardwareFrame;
	}
	m_iMaxHardwareFrame = iHardwareFrame = max( iHardwareFrame, m_iMaxHardwareFrame );
	return iHardwareFrame;
}

int64_t RageSoundDriver::GetHardwareFrame( RageTimer *pTimestamp ) const
{
	if( pTimestamp == NULL )
		return ClampHardwareFrame( GetPosition() );

	/*
	 * We may have unpredictable scheduling delays between updating the timestamp
	 * and reading the sound position.  If we're preempted while doing this and
	 * it may have caused the timestamp to not match the returned time, retry.
	 *
	 * As a failsafe, only allow a few attempts.  If this has to try more than
	 * a few times, then probably we have thread contention that's causing more
	 * severe performance problems, anyway.
	 */
	int iTries = 3;
	int64_t iPositionFrames;
	do
	{
		pTimestamp->Touch();
		iPositionFrames = GetPosition();
	} while( --iTries && pTimestamp->Ago() > 0.002f );

	if( iTries == 0 )
	{
		static bool bLogged = false;
		if( !bLogged )
		{
			bLogged = true;
			LOG->Warn( "RageSoundDriver::GetHardwareFrame: too many tries" );
		}
	}

	return ClampHardwareFrame( iPositionFrames );
}

/*
 * (c) 2002-2004 Glenn Maynard
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons to
 * whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
 * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
 * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
 * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
