#include "Mix/Private/Sound/StreamingTask.h"

#include "Mix/Plugin/Sound/Decoder.h"
#include "Mix/Private/Sound/BaseMediator.h"
#include "Mix/Private/Sound/WaveReader.h"

namespace Mix{ namespace Sound{

////////////////////////////////////////////////////////////////////////////////////////////////////
// StreamingTask::VoiceContext
////////////////////////////////////////////////////////////////////////////////////////////////////

StreamingTask::VoiceContext::VoiceContext( void )
{
	m_hEvent[0] = ::CreateEventW( NULL, FALSE, FALSE, NULL );
	m_hEvent[1] = ::CreateEventW( NULL, FALSE, FALSE, NULL );
}

StreamingTask::VoiceContext::~VoiceContext( void )
{
	if( m_hEvent[0] != NULL )
	{
		::CloseHandle( m_hEvent[0] );
	}

	if( m_hEvent[1] != NULL )
	{
		::CloseHandle( m_hEvent[1] );
	}
}

void StreamingTask::VoiceContext::Reset( void )
{
	::ResetEvent( m_hEvent[VoiceContext::BUFFERING] );
	::ResetEvent( m_hEvent[VoiceContext::END] );
}

UInt32 StreamingTask::VoiceContext::Poll( void )
{
	UInt32 ret = ::WaitForMultipleObjects( 2, m_hEvent, FALSE, 0 );
	if( ret == WAIT_TIMEOUT )
	{
		return static_cast<UInt32>( VoiceContext::NONE );
	}

	return ( ret - WAIT_OBJECT_0 );
}

void StreamingTask::VoiceContext::OnStreamEnd( void )
{
	::SetEvent( m_hEvent[VoiceContext::END] );
}

void StreamingTask::VoiceContext::OnBufferEnd( void* pBufferContext )
{
	::SetEvent( m_hEvent[VoiceContext::BUFFERING] );
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// StreamingTask
////////////////////////////////////////////////////////////////////////////////////////////////////

StreamingTask::StreamingTask( Mix::Sound::BaseMediator* pBaseMediator ) :
m_pBaseMediator( pBaseMediator ),
m_pSource( NULL ),
m_pDecoder( NULL ),
m_pVoice( NULL ),
m_CurBlock( 0 ),
m_bLoop( False ),
m_State( STATE_IDLE ),
m_SuspendCount( 0 )
{
	MIX_ADD_REF( m_pBaseMediator );

	for( UInt32 i = 0; i < MAX_BLOCK; i++ )
	{
		m_pBufferArray[i] = NULL;
	}
}

StreamingTask::~StreamingTask( void )
{
	MIX_RELEASE( m_pBaseMediator );
}

Boolean StreamingTask::Initialize( Mix::Sound::WaveReader* pSource, Mix::Plugin::Sound::IDecoder* pDecoder, IXAudio2SourceVoice* pVoice )
{
	MIX_ASSERT( pSource != NULL );
	MIX_ASSERT( pDecoder != NULL );
	MIX_ASSERT( pVoice != NULL );

	MIX_ASSERT( m_pSource == NULL );
	MIX_ASSERT( m_pDecoder == NULL );
	MIX_ASSERT( m_pVoice == NULL );

	//各種インターフェース
	m_pSource = pSource;
	m_pDecoder = pDecoder;
	m_pVoice = pVoice;

	//ブロックのメモリを確保
	for( UInt32 i = 0; i < MAX_BLOCK; i++ )
	{
		m_pBufferArray[i] = static_cast<UInt8*>( MIX_LIB_MALLOC( Mix::Memory::SECTION_SOUND, m_pDecoder->GetFormat()->nAvgBytesPerSec ) );
		if( m_pBufferArray[i] == NULL )
		{
			return False;
		}
	}

	return True;
}

IXAudio2VoiceCallback* StreamingTask::GetVoiceCallbackPtr( void )
{
	return &m_VoiceContext;
}

Boolean StreamingTask::Process( void )
{
	Boolean bContinue = True;

	//各ステータス時の処理
	switch( m_State )
	{
	case STATE_IDLE:
		bContinue = ProcIdle();
		break;

	case STATE_STOPPED:
		ProcStopped();
		break;
	}

	return bContinue;
}

Boolean StreamingTask::ProcIdle( void )
{
	Boolean bContinue = True;

	Int16 eventType;
	Int16 eventOpt;

	//イベント
	if( m_pBaseMediator->PopEvent( eventType, eventOpt ) == True )
	{
		switch( eventType )
		{
		case Mix::Sound::BaseMediator::EVENT_TYPE_PLAY:
			ProcPlay( ( eventOpt != 0x00 ) );
			break;
		case Mix::Sound::BaseMediator::EVENT_TYPE_STOP:
			ProcStop();
			break;
		case Mix::Sound::BaseMediator::EVENT_TYPE_SUSPEND:
			ProcSuspend();
			break;
		case Mix::Sound::BaseMediator::EVENT_TYPE_RESUME:
			ProcResume();
			break;
		case Mix::Sound::BaseMediator::EVENT_TYPE_TERMINATE:
			ProcTerminate();
			bContinue = False;
			break;
		}
	}

	//ボイスコンテキスト
	if( bContinue == True )
	{
		switch( m_VoiceContext.Poll() )
		{
		case VoiceContext::BUFFERING:
			ProcBuffering();
			break;

		case VoiceContext::END:
			ProcStop();
			break;
		}
	}

	return bContinue;
}

void StreamingTask::ProcStopped( void )
{
	XAUDIO2_VOICE_STATE stat;

	m_pVoice->GetState( &stat );

	if( stat.BuffersQueued == 0 )
	{
		m_pDecoder->Reset( m_pSource );
		m_VoiceContext.Reset();

		m_CurBlock = 0;
		m_SuspendCount = 0;

		m_State = STATE_IDLE;

		m_pBaseMediator->SetState( Mix::Sound::BaseMediator::STATE_STOP );
	}
}

void StreamingTask::ProcPlay( Boolean bLoop )
{
	m_bLoop = bLoop;

	for( UInt32 i = 0; i < MAX_BLOCK; i++ )
	{
		ProcBuffering();
	}

	if( m_pVoice->Start() == S_OK )
	{
		m_pBaseMediator->SetState( Mix::Sound::BaseMediator::STATE_PLAY );
	}
}

void StreamingTask::ProcStop( void )
{
	m_pVoice->Stop();
	m_pVoice->FlushSourceBuffers();

	m_State = STATE_STOPPED;
}

void StreamingTask::ProcSuspend( void )
{
	if( m_SuspendCount++ == 0 )
	{
		m_pVoice->Stop();
	}
}

void StreamingTask::ProcResume( void )
{
	if( --m_SuspendCount == 0 )
	{
		m_pVoice->Start();
	}
}

void StreamingTask::ProcBuffering( void )
{
	if( m_pDecoder->GetSize() <= m_pDecoder->GetPosition() )
	{
		return;
	}

	XAUDIO2_BUFFER buffer = {0};
	UInt32 readSize;

	if( m_bLoop == TRUE )
	{
		BYTE* pDst = &( m_pBufferArray[m_CurBlock][0] );
		int readCountDown;

		readCountDown = readSize = m_pDecoder->GetFormat()->nAvgBytesPerSec;

		do
		{
			UInt32 size = m_pDecoder->Read( m_pSource, pDst, readCountDown );
			if( size == 0 )
			{
				m_pDecoder->Reset( m_pSource );
			}
			else
			{
				readCountDown -= size;
				pDst += size;
			}
		}
		while( readCountDown > 0 );
	}
	else
	{
		readSize = static_cast<UInt32>( min( m_pDecoder->GetFormat()->nAvgBytesPerSec, ( m_pDecoder->GetSize() - m_pDecoder->GetPosition() ) ) );

		Mix::Memory::Zero( m_pBufferArray[m_CurBlock], m_pDecoder->GetFormat()->nAvgBytesPerSec );
		m_pDecoder->Read( m_pSource, m_pBufferArray[m_CurBlock], readSize );
	}

	buffer.AudioBytes = m_pDecoder->GetFormat()->nAvgBytesPerSec;
	buffer.pAudioData = m_pBufferArray[m_CurBlock];
	if( m_pDecoder->GetSize() <= m_pDecoder->GetPosition() )
	{
		buffer.Flags = XAUDIO2_END_OF_STREAM;
	}

	m_pVoice->SubmitSourceBuffer( &buffer );

	m_CurBlock++;
	m_CurBlock %= MAX_BLOCK;
}

void StreamingTask::ProcTerminate( void )
{
	for( UInt32 i = 0; i < MAX_BLOCK; i++ )
	{
		MIX_LIB_FREE( m_pBufferArray[i] );
	}

	if( m_pVoice != NULL )
	{
		m_pVoice->DestroyVoice();
	}

	if( m_pDecoder != NULL )
	{
		m_pDecoder->Destroy();
	}

	if( m_pSource != NULL )
	{
		m_pSource->Destroy();
	}

	m_pBaseMediator->Terminated();
}

}}
