#include"CSoundBufferStreamDS8.h"

#include"../../../../Template/Mathematics.h"
#include"../../../../Auxiliary/Debug/CTrace.h"


namespace Maid
{

static inline unt CalcInterval( unt Old, unt Now, unt BufferSize )
{
	unt PlayDelta;

	if( Old<=Now ){ PlayDelta = Now-Old; }
	else		  { PlayDelta = BufferSize-Old + Now; }

	return PlayDelta;
}



void CSoundBufferStreamDS8::Setup( const SPDSBUFFER& p, const SPSOUNDDECODER& pDecoder )
{
	m_pBuffer = p;
	m_pDecoder= pDecoder;
	m_IsLoop  = false;

	m_PlayOffset    = 0;
	m_PlayCursorLog = 0;
	m_WriteOffset   = 0;
	m_WriteCursorLog= 0;
	m_LoopPositon = 0;

	{
		DWORD size;

		m_pBuffer->GetFormat( NULL, NULL, &size );
		m_FormatData.resize( size );

		m_pBuffer->GetFormat( (WAVEFORMATEX*)&(m_FormatData[0]), size, NULL );
	}

	m_Caps.dwSize = sizeof(m_Caps);
	m_pBuffer->GetCaps( &m_Caps );

	m_Buff.resize( m_Caps.dwBufferBytes );
	ZERO( &m_Buff[0], m_Caps.dwBufferBytes );

	DWORD freq;
	const WAVEFORMATEX* pFormat = GetFormat();
	m_pBuffer->SetFrequency( pDecoder->GetSamplesPerSec() );
	HRESULT ret = m_pBuffer->GetFrequency( &freq );


	//	とりあえず100ミリぐらいのバッファをデコードしておく
	DecodeMiri(500);
}

void	CSoundBufferStreamDS8::Play()
{
	DWORD flag = 0;

	flag |= DSBPLAY_LOOPING;

	const HRESULT ret = m_pBuffer->Play( 0, 0, flag );
	if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSoundBuffer8::Play()") ); }
}

void	CSoundBufferStreamDS8::Stop()
{
	const HRESULT ret = m_pBuffer->Stop();
	if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSoundBuffer8::Stop()") ); }
}

unt		CSoundBufferStreamDS8::GetBytePosition () const
{
	const unt32 BufferSize	= m_Caps.dwBufferBytes;
	DWORD PlayCursor;
	m_pBuffer->GetCurrentPosition( &PlayCursor, NULL );
	const unt32 PlayDelta = CalcInterval( m_PlayCursorLog, PlayCursor, BufferSize );

	unt32 pos;

	unt32 len = m_pDecoder->GetLength();

	if( len < m_PlayOffset+PlayDelta )
	{
		if( m_IsLoop )	{ pos = (m_PlayOffset+PlayDelta)-len + m_LoopPositon; }
		else			{ pos = 0; }

	}else
	{
		pos = m_PlayOffset+PlayDelta;
	}

	return pos;
}

void	CSoundBufferStreamDS8::SetBytePosition ( unt pos )
{
	const unt32 BufferSize = m_Caps.dwBufferBytes;
	const bool  playing     = IsPlay();

	Stop();
	{
		m_pBuffer->SetCurrentPosition( 0 );
		m_pDecoder->SetPosition( pos, ISoundDecoder::BEGIN );

		m_PlayCursorLog = 0;
		m_PlayOffset = pos;
		m_WriteCursorLog = 0;
		m_WriteOffset= pos;

		DecodeMiri(500);
	}

	if( playing ) { Play(); }
}

void CSoundBufferStreamDS8::SetByteLoopPosition ( unt pos )
{
	m_LoopPositon = pos;
}


void	CSoundBufferStreamDS8::SetVolume( int vol )
{

	if( vol==0 )
	{
		const HRESULT ret = m_pBuffer->SetVolume( DSBVOLUME_MIN );
		if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSoundBuffer8::SetVolume(DSBVOLUME_MIN)") ); }
	}else
	{
//	MAID_TRACE( "CSoundBufferStreamDS8::SetVolume1" << vol );
		const float p = float(vol)/10000.0f;
		const int   v = (int)( 20.0f * Math<float>::log10(p) * 100.0f );
		const HRESULT ret = m_pBuffer->SetVolume( max(DSBVOLUME_MIN,v) );
		if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSoundBuffer8::SetVolume()") ); }
	}
}

int		CSoundBufferStreamDS8::GetVolume() const
{
	LONG vol;
	const HRESULT ret = m_pBuffer->GetVolume( &vol );
	if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSoundBuffer8::GetVolume()") ); }
	if( vol==DSBVOLUME_MIN ) { return VOLUME_MIN; }
	
	return int(Math<float>::pow(10.0f,float(vol)/100.0f/20.0f)*10000.0f);

}

bool	CSoundBufferStreamDS8::IsPlay() const
{
	DWORD dw;
	const HRESULT ret = m_pBuffer->GetStatus( &dw );
	if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSoundBuffer8::GetStatus()") ); }

	return IsFlag( dw, DSBSTATUS_PLAYING );
}

void	CSoundBufferStreamDS8::SetLoopState( bool IsLoop )
{
	m_IsLoop = IsLoop;
}

int	CSoundBufferStreamDS8::GetSamplePerSec()const
{
	return GetFormat()->nSamplesPerSec;
}

int	CSoundBufferStreamDS8::GetBitPerSample()const
{
	return GetFormat()->wBitsPerSample;
}

int	CSoundBufferStreamDS8::GetChannel()const
{
	return GetFormat()->nChannels;
}

int	CSoundBufferStreamDS8::GetCurrentSample() const
{
	if( !IsPlay() )		{ return 0; }

	const unt32 SampleSize = GetBitPerSample()/8 * GetChannel();

	DWORD offset;
	m_pBuffer->GetCurrentPosition( &offset, NULL );

	int16*	pData = (int16*)(&m_Buff[offset]);

	int ret = *pData;

	return ret;
}


void	CSoundBufferStreamDS8::Update( unt time )
{
	const unt32 BufferSize	= m_Caps.dwBufferBytes;

	//	現在の再生場所から、再生時間を測る
	unt PlayDelta;
	{
		DWORD play;
		m_pBuffer->GetCurrentPosition( &play, NULL );

		unt t = CalcInterval( m_PlayCursorLog, play, BufferSize );

		//	前回の Update() から t byte再生された

		m_PlayOffset += t;
		m_PlayCursorLog = play;
		PlayDelta = t;
	}

	if( m_pDecoder->GetLength()<=(int)m_PlayOffset &&  !m_IsLoop )
	{	//	デコードが最後まで行っていて、ループ再生しない設定だったら
		//	バッファの停止。
		m_pBuffer->Stop();
		SetPosition(0);
		return;
	}



	//	そうでないなら更新
	DecodeByte( PlayDelta );
}
void  CSoundBufferStreamDS8::DecodeMiri( unt time )
{
	const WAVEFORMATEX* pFormat = GetFormat();
	const unt len = time * pFormat->nAvgBytesPerSec / 1000;

	DecodeByte( len );
}

void  CSoundBufferStreamDS8::DecodeByte( unt DecodeSize )
{
	if( DecodeSize==0 ) { return ; }

	const unt32 BufferSize	= m_Caps.dwBufferBytes;
	const WAVEFORMATEX* pFormat = GetFormat();


	MySTL::vector<unt08> tmp( DecodeSize );


	{
		//	まずは音をＰＣＭに変換する
		const int encode_size = m_pDecoder->Read( &tmp[0], (int)tmp.size() );
		if( encode_size < (int)tmp.size() )
		{
			//	デコードサイズが足りない == 終端まで行ったら
			//	設定で無音を入れたり、ループポジションの音を入れたり。
			if( m_IsLoop )
			{
				m_pDecoder->SetPosition( m_LoopPositon, ISoundDecoder::BEGIN );
				m_pDecoder->Read( &tmp[encode_size], (int)tmp.size() - encode_size );
			}else
			{
				ZERO( &tmp[encode_size], tmp.size() - encode_size );
			}
		}
	}


	//	バッファに書き込む
	const int w_pos = m_WriteCursorLog;
	{
		HRESULT ret;

		void *pBuf1, *pBuf2;
		DWORD len1, len2;

		ret = m_pBuffer->Lock( w_pos, DecodeSize, &pBuf1, &len1, &pBuf2, &len2, 0 );
		if( FAILED(ret) ) { MAID_ASSERT( true, "未確認エラー" ); }
		if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSoundBuffer::Lock") ); }

		{
			::memcpy( &m_Buff[w_pos], &tmp[0], len1 );
			::memcpy( pBuf1, &tmp[0], len1 );
			if( pBuf2!=NULL )
			{
				::memcpy( &m_Buff[0], &tmp[len1], len2 );
				::memcpy( pBuf2, &tmp[len1], len2 );
			}
		}
		ret = m_pBuffer->Unlock( pBuf1, len1, pBuf2, len2 );
		if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSoundBuffer::Lock") ); }
	}

	m_WriteOffset += DecodeSize;
	m_WriteCursorLog += DecodeSize;
	m_WriteCursorLog %= BufferSize;
}


const WAVEFORMATEX* CSoundBufferStreamDS8::GetFormat()const
{
	return (const WAVEFORMATEX*)&(m_FormatData[0]);
}

}
