#include"CSoundCardDriverDS8.h"
#include"CSoundBufferDS8.h"
#include"CSoundBufferStaticDS8.h"
#include"CSoundBufferStreamDS8.h"
#include"../../../../Auxiliary/CString.h"


namespace Maid
{
struct DEVICETYPE
{
	GUID			guid;
	MySTL::string	Desc;
	MySTL::string	Driver;
};

static MySTL::vector<DEVICETYPE>	s_DeviceList;


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! DirectSoundドライバの能力を調べる関数
/*!	
 *	詳しくはDXヘルプを参照
 *
 */
BOOL CALLBACK DSoundEnumCallback( GUID* pGUID, LPCSTR strDesc, LPCSTR strDrvName, VOID* pContext )
{
    if( pGUID!=NULL )
    {
		DEVICETYPE	type;

		memcpy( &type.guid, pGUID, sizeof(GUID) );
		type.Desc   = strDesc;
		type.Driver = strDrvName;

		s_DeviceList.push_back( type );
	}

    return TRUE;
}


CSoundCardDriverDS8::CSoundCardDriverDS8( HWND hWnd )
	: m_hWnd(hWnd)
{

}


void	CSoundCardDriverDS8::Initialize( const PCMBUFFERFORMAT& Format )
{
	m_dsoundDLL.Load( MAIDTEXT("dsound.dll") );


	{
		s_DeviceList.clear();
		//DirectSoundEnumerate はdefine されているのでこうする

		typedef  HRESULT (WINAPI *SoundEnum)( LPDSENUMCALLBACK, LPVOID );
		#ifdef UNICODE
			SoundEnum pDirectSoundEnumerate = (SoundEnum)m_dsoundDLL.GetProcAddress(L"DirectSoundEnumerateW");
		#else
			SoundEnum pDirectSoundEnumerate = (SoundEnum)m_dsoundDLL.GetProcAddress(MAIDTEXT("DirectSoundEnumerateA"));
		#endif
		pDirectSoundEnumerate( DSoundEnumCallback,  NULL );

	}

	{
		typedef  HRESULT (WINAPI *FUNCTIONPTR)( LPCGUID, LPDIRECTSOUND*, LPUNKNOWN );
		FUNCTIONPTR pdsoundCreate = (FUNCTIONPTR)m_dsoundDLL.GetProcAddress(MAIDTEXT("DirectSoundCreate"));

		LPDIRECTSOUND pD3D=NULL;
		pdsoundCreate( NULL, &pD3D, NULL );

		if( pD3D==NULL ) 
		{
			MAID_THROWEXCEPTION( MAIDTEXT("DirectSoundCreate()") );	
		}

		m_pDevice.reset( pD3D );
	}


	HRESULT ret;

	ret = m_pDevice->SetCooperativeLevel( m_hWnd, DSSCL_PRIORITY );
	if( FAILED(ret) ) { goto NORMAL_MODE; }

	{
		IDirectSoundBuffer* pBuf;
		DSBUFFERDESC dsbd;

		ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );
		dsbd.dwSize        = sizeof(DSBUFFERDESC);
		dsbd.dwFlags       = DSBCAPS_PRIMARYBUFFER;
		dsbd.dwBufferBytes = 0;
		dsbd.lpwfxFormat   = NULL;
  		dsbd.guid3DAlgorithm = GUID_NULL ;
 
		ret = m_pDevice->CreateSoundBuffer( &dsbd, &pBuf, NULL );
 		if( FAILED(ret) ) { goto NORMAL_MODE; }

		m_pPrimary.reset( pBuf );
	}
	{
		WAVEFORMATEX wfx;
		ZeroMemory( &wfx, sizeof(WAVEFORMATEX) ); 
		wfx.wFormatTag      = WAVE_FORMAT_PCM; 
		wfx.nChannels       = (WORD) Format.Channels; 
		wfx.nSamplesPerSec  = Format.SamplesPerSec; 
		wfx.wBitsPerSample  = (WORD) Format.BitsPerSample;
		wfx.nBlockAlign     = wfx.wBitsPerSample / 8 * wfx.nChannels;
		wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;

		ret = m_pPrimary->SetFormat(&wfx);
 		if( FAILED(ret) ) { goto NORMAL_MODE; }
	}

	PlayDammySound();
	return ;

NORMAL_MODE:
	ret = m_pDevice->SetCooperativeLevel( m_hWnd, DSSCL_NORMAL );
	if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("DirectSound8 初期化失敗") ); }

	PlayDammySound();
}

void	CSoundCardDriverDS8::Finalize()
{

}

mstring	CSoundCardDriverDS8::DeviceCapsStringOut()const
{
	MySTL::string SoundCaps;

	SoundCaps += "Sound Caps------------------------------------------------------------------\r\n";

	SoundCaps += "Driver\r\n";
	for( int i=0; i<(int)s_DeviceList.size(); ++i )
	{
		SoundCaps += "\t";
		SoundCaps += s_DeviceList[i].Driver;
		SoundCaps += "\t";
		SoundCaps += s_DeviceList[i].Desc;
		SoundCaps += "\r\n";
	}

	DSCAPS	caps = { sizeof(DSCAPS) };

	m_pDevice->GetCaps( &caps );

	SoundCaps += "SampleRate\r\n";
	char mess[256];
	::wsprintf( mess, "\tmin %d max %d\r\n", caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate );
	SoundCaps += mess;

	//	そのほかの能力はろくすっぽ返ってこないから無視（Ｔ-Ｔ）

	return CString::ConvertSJIStoMAID(SoundCaps);
}

SPSOUNDBUFFERPCMSTATIC	CSoundCardDriverDS8::CreateBufferPCMStatic( const MySTL::vector<unt08>& buf, const PCMBUFFERFORMAT& Format )const
{
	boost::shared_ptr<CSoundBufferStaticDS8>	pRet( new CSoundBufferStaticDS8 );

	{
		WAVEFORMATEX	fmt = {0};

		fmt.wFormatTag      = WAVE_FORMAT_PCM;
		fmt.nChannels       = Format.Channels;
		fmt.nSamplesPerSec  = Format.SamplesPerSec;
		fmt.wBitsPerSample  = Format.BitsPerSample;
		fmt.nBlockAlign     = Format.Channels * (Format.BitsPerSample/8);
		fmt.nAvgBytesPerSec = Format.SamplesPerSec*fmt.nBlockAlign;
		fmt.cbSize          = 0;

		DSBUFFERDESC dsbd = {0};

		dsbd.dwSize        = sizeof(dsbd);
		dsbd.dwFlags       = DSBCAPS_GETCURRENTPOSITION2|DSBCAPS_LOCSOFTWARE|DSBCAPS_CTRLVOLUME|DSBCAPS_CTRLPAN;
		dsbd.dwBufferBytes = (DWORD)buf.size();
		dsbd.lpwfxFormat   = &fmt;
   		dsbd.guid3DAlgorithm = GUID_NULL ;
 
		SPDSBUFFER pBuffer;
		{
			IDirectSoundBuffer* p;
			const HRESULT ret  = m_pDevice->CreateSoundBuffer( &dsbd, &p, NULL );
			if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSound8::CreateSoundBuffer()") ); }
			pBuffer.reset(p);
		}

//		const void* pppp = &(buf[0]);
		{
			DWORD l1,l2;
			void* pBuf1, *pBuf2;
			HRESULT ret;

			ret = pBuffer->Lock( 0, (DWORD)buf.size(), &pBuf1, &l1, &pBuf2, &l2, 0 );
			if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSoundBuffer8::Lock()") ); }

			::memcpy( pBuf1, &(buf[0]), buf.size() );

			ret = pBuffer->Unlock( pBuf1, l1, pBuf2, l2 );
			if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSoundBuffer8::Unlock()") ); }
		}

		pRet->Setup(pBuffer);
	}

	return pRet;
}

SPSOUNDBUFFERPCMSTATIC	CSoundCardDriverDS8::DuplicateSoundBuffer( const ISoundBufferPCMStatic& pSrc )const
{
	const CSoundBufferStaticDS8* p = static_cast<const CSoundBufferStaticDS8*>( &pSrc );

	SPDSBUFFER pRefarence = p->GetBuffer();
	SPDSBUFFER pNew;
	{
		IDirectSoundBuffer* p;
		const HRESULT ret  = m_pDevice->DuplicateSoundBuffer( pRefarence.get(), &p );
		if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSound8::DuplicateSoundBuffer()") ); }
		pNew.reset(p);
	}

	boost::shared_ptr<CSoundBufferStaticDS8>	pRet( new CSoundBufferStaticDS8 );
	pRet->Setup( pNew );

	return pRet;
}

SPSOUNDBUFFERPCMSTREAM	CSoundCardDriverDS8::CreateBufferPCMStream( const SPSOUNDDECODER& pDecoder )const
{
	boost::shared_ptr<CSoundBufferStreamDS8>	pRet( new CSoundBufferStreamDS8 );

	{
		WAVEFORMATEX	fmt = {0};

		fmt.wFormatTag      = WAVE_FORMAT_PCM;
		fmt.nChannels       = pDecoder->GetChannels();
		fmt.nSamplesPerSec  = pDecoder->GetSamplesPerSec();
		fmt.wBitsPerSample  = pDecoder->GetBitsPerSamples();
		fmt.nBlockAlign     = fmt.nChannels * (fmt.wBitsPerSample/8);
		fmt.nAvgBytesPerSec = fmt.nSamplesPerSec*fmt.nBlockAlign;
		fmt.cbSize          = 0;

		DSBUFFERDESC dsbd = {0};

		//	バッファサイズは２秒でがんばってもらうことにする
		const DWORD len = fmt.nBlockAlign * fmt.nSamplesPerSec * 2;
		const DWORD flag = DSBCAPS_CTRLFREQUENCY|DSBCAPS_CTRLVOLUME|DSBCAPS_CTRLPAN
							| DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE;

		dsbd.dwSize        = sizeof(dsbd);
		dsbd.dwFlags       = flag;
		dsbd.dwBufferBytes = len;
		dsbd.lpwfxFormat   = &fmt;
   		dsbd.guid3DAlgorithm = GUID_NULL ;
 
		SPDSBUFFER pBuffer;
		{
			IDirectSoundBuffer* p;
			const HRESULT ret  = m_pDevice->CreateSoundBuffer( &dsbd, &p, NULL );
			if( FAILED(ret) ) { MAID_THROWEXCEPTION( MAIDTEXT("IDirectSound8::CreateSoundBuffer()") ); }
			pBuffer.reset(p);
		}

		pRet->Setup( pBuffer, pDecoder );
	}

	return pRet;
}


/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
//! WDMドライバのバグ対策関数
/*!	
 *	プライマリバッファが止まった後に再度再生させるとき
 *	前回再生したバッファが少し再生されてしまいノイズが聞こえる
 *	なのでプライマリバッファを停止させないように無音を再生しつづける
 *
 */
void CSoundCardDriverDS8::PlayDammySound()
{
	const DWORD chan = 2;
	const DWORD freq = 44100;
	const DWORD bps  = 16;

	WAVEFORMATEX wfx={0};
	wfx.wFormatTag      = WAVE_FORMAT_PCM;
	wfx.nChannels       = chan;
	wfx.nSamplesPerSec  = freq;
	wfx.wBitsPerSample  = bps;
	wfx.nBlockAlign     = chan* (bps/8);
	wfx.nAvgBytesPerSec = freq*wfx.nBlockAlign;
	wfx.cbSize          = 0;

	{
		DSBUFFERDESC dsbd;
		ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );
		dsbd.dwSize        = sizeof(dsbd);
		dsbd.dwFlags       = DSBCAPS_LOCSOFTWARE;
		dsbd.dwBufferBytes = wfx.nAvgBytesPerSec;
		dsbd.lpwfxFormat   = &wfx;
   		dsbd.guid3DAlgorithm = GUID_NULL ;
 
		IDirectSoundBuffer* pBuf;
		m_pDevice->CreateSoundBuffer( &dsbd, &pBuf, NULL );
		m_pDammy.reset(pBuf);
	}
	
	//	そんでもって流し込む
	LPVOID	pPos1,	pPos2;
	DWORD	dwLen1,	dwLen2;

	m_pDammy->Lock( 0, wfx.nAvgBytesPerSec, &pPos1, &dwLen1, &pPos2, &dwLen2, 0 );
	{
		ZeroMemory( pPos1, wfx.nAvgBytesPerSec );
	}
	m_pDammy->Unlock( pPos1, dwLen1, pPos2, dwLen2 );

	m_pDammy->Play( 0, 0, DSBPLAY_LOOPING );
}

}
