#include "standard.h"
#include "global.h"
#include "ds.h"
#include "winmain.h"
#include "../game/camera.h"

/*** defines ***/

/*** globals ***/
defglobal List<SoundAdapter> G_SoundAdapterList;
defglobal SoundAdapter G_SoundAdapter;
defglobal Position G_PrimarySoundAdapter;

/*** locals ***/

#define NUMBER_SOUND_BUFFERS 256
#define NUMBER_SOUND_PLAY_BUFFERS 32
local IDirectSound* L_DirectSound;
local IDirectSoundBuffer* L_DSBuffer[NUMBER_SOUND_BUFFERS];
local IDirectSoundBuffer* LaPlayBuffer[NUMBER_SOUND_PLAY_BUFFERS];
local int LnSamplePlaying[NUMBER_SOUND_PLAY_BUFFERS];
local int LanSampleFrequency[NUMBER_SOUND_BUFFERS];
local int LnSamplesPlaying;
local bool LtSoundEnabled;

/*** misc ***/

/*** code ***/

local bool DS_Create(GUID* pGUID)
	{
	Log(LT_Enter,"DS_Create");
	if (DX_TRY(DirectSoundCreate(pGUID,&L_DirectSound,NULL)))
		{
		Log(LT_Error,"DirectSoundCreate failed");
		return false;
		}
	return true;
	}

local void DS_Release()
	{
	Log(LT_Enter,"DS_Release");
	RELEASE(L_DirectSound);
	}

local BOOL WINAPI DS_EnumCallback(GUID* pGUID,const char* pDescription,const char* pModule,void* pContext)
	{
	Log(LT_Enter,"DS_EnumCallback: %s (%s)",pDescription,pModule);
	List<SoundAdapter>* pList=static_cast<List<SoundAdapter>*>(pContext);
	// probably want to DirectSoundCreate and check capabilities...
	SoundAdapter& adapter=pList->NewTail();
	adapter.SetGUID(pGUID);
	strcpy(adapter.strDescription,pDescription);
	strcpy(adapter.strName,pModule);
	return true;
	}

local bool DS_EnumerateDevices(List<SoundAdapter>& rDeviceList)
	{
	Log(LT_Enter,"DS_EnumerateDevices");
	if (DX_TRY(DirectSoundEnumerate(DS_EnumCallback,static_cast<void*>(&rDeviceList))))
		{
		Log(LT_Error,"DirectSoundEnumerate failed");
		return false;
		}
	return true;
	}

// Make a list of all cards installed in the system.  Also works out the capabilities of each one.
local bool DS_MakeAdapterList()
	{
	Log(LT_Enter,"DS_MakeAdapterList");
	G_SoundAdapterList.RemoveAll();
	if (!DS_EnumerateDevices(G_SoundAdapterList))
		return false;
	G_PrimarySoundAdapter=0;
	for_all(p,G_SoundAdapterList)
		{
		SoundAdapter& adapter=G_SoundAdapterList[p];
		if (!adapter.pGUID)
			{
			G_PrimarySoundAdapter=p;
			return true;
			}
		}
	Log(LT_Warning,"No primary adapter found");
	return true;
	}

Position DS_FindPreferredAdapter(GUID* pGUID)
	{
	Log(LT_Enter,"DS_FindPreferredAdapter");
	Position found=0;
	for_all(p,G_SoundAdapterList)
		{
		SoundAdapter& adapter=G_SoundAdapterList[p];
		if (pGUID && adapter.AdapterGUID==*pGUID)
			{
			found=p;
			break;
			}
		}
	if (!found)
		{
		found=G_PrimarySoundAdapter;
		if (!found)
			{
			Log(LT_Warning,"No primary sound adapter");
			return 0;
			}
		}
	SoundAdapter& adapter=G_SoundAdapterList[found];
	Log(LT_Info,"Found adapter: %s (%s)",static_cast<char*>(adapter.strDescription),
											static_cast<char*>(adapter.strName));
	return found;
	}

void DS_FreeAllSamples()
	{
	Log(LT_Enter,"DS_FreeAllSamples");
	if (LtSoundEnabled)
		RELEASEARRAY(L_DSBuffer);
	}

bool DS_MakeSample(int nSample,WAVEFORMATEX* pWF,void* pWaveData,DWORD dwWaveLength)
	{
	//Log(LT_Enter,"DS_MakeSample");
	if (!LtSoundEnabled)
		return false;
	if (nSample>=ArraySize(L_DSBuffer))
		return false;
	DSBUFFERDESC desc;
	desc.dwSize=sizeof desc;
	desc.dwFlags=DSBCAPS_CTRLDEFAULT|DSBCAPS_LOCSOFTWARE;	//DSBCAPS_STATIC;
	desc.dwBufferBytes=dwWaveLength;
	desc.lpwfxFormat=pWF;
	desc.dwReserved=0;
	if (DX_TRY(L_DirectSound->CreateSoundBuffer(&desc,L_DSBuffer+nSample,NULL)))
		return false;
	void* pBlock;
	DWORD dwBlockLength;
	if (DX_TRY(L_DSBuffer[nSample]->Lock(0,dwWaveLength,&pBlock,&dwBlockLength,NULL,NULL,0)))
		return false;
	memcpy(pBlock,pWaveData,dwBlockLength);
	if (DX_TRY(L_DSBuffer[nSample]->Unlock(pBlock,dwBlockLength,NULL,0)))
		return false;
	LanSampleFrequency[nSample]=pWF->nSamplesPerSec;
	Log(LT_Sound,"Sample %d: %d, %d, %d",nSample,pWF->nSamplesPerSec,pWF->wBitsPerSample,pWF->nChannels);
	return true;
	}

bool DS_IsChannelPlaying(int nChannel)
	{
	IDirectSoundBuffer* pBuffer=LaPlayBuffer[nChannel];
	if (!pBuffer)
		return false;
	DWORD dwStatus;
	if (DX_TRY(pBuffer->GetStatus(&dwStatus)))
		{
		Log(LT_Error,"SoundSampleIsPlaying: GetStatus error");
		return false;
		}
	if (dwStatus&DSBSTATUS_PLAYING)
		return true;
	pBuffer->Release();
	LaPlayBuffer[nChannel]=0;
	return false;
	}

int DS_HowManyChannelsPlaying()
	{
	int nPlaying,nChannel;
	DWORD dwStatus;
	for (nChannel=nPlaying=0;nChannel<ArraySize(LaPlayBuffer);++nChannel)
		{
		IDirectSoundBuffer* pBuffer=LaPlayBuffer[nChannel];
		if (pBuffer &&
			!DX_TRY(pBuffer->GetStatus(&dwStatus)) &&
			(dwStatus&DSBSTATUS_PLAYING))
				++nPlaying;
		}
	return nPlaying;
	}

local int DS_GetFreeChannel()
	{
	int nChannel;
	// first look for an empty slot (not played yet, or definitely stopped)
	for (nChannel=0;nChannel<ArraySize(LaPlayBuffer);++nChannel)
		if (LaPlayBuffer[nChannel]==0)
			return nChannel;
	// if no empty slots then search for one that has stopped
	for (nChannel=0;nChannel<ArraySize(LaPlayBuffer);++nChannel)
		if (!DS_IsChannelPlaying(nChannel))
			return nChannel;
	return -1;
	}

int DS_StartSample(int nSample,int nVolume,int nPitch,int nPan,DWORD dwFlags)
	{
	int nChannel=DS_GetFreeChannel();
	if (nChannel<0)
		return DS_ERR_SOUND_NO_FREE_SLOTS;
	IDirectSoundBuffer* pOrigBuffer=L_DSBuffer[nSample],* pBuffer;
	if (DX_TRY(L_DirectSound->DuplicateSoundBuffer(pOrigBuffer,&pBuffer)))
		return DS_ERR_SOUND_FAILED;
	int nFrequency=(nPitch*LanSampleFrequency[nSample])>>16;
	if (DX_TRY(pBuffer->SetVolume(nVolume)) ||
		DX_TRY(pBuffer->SetFrequency(nFrequency)) ||
		DX_TRY(pBuffer->SetPan(nPan)) ||
		DX_TRY(pBuffer->SetCurrentPosition(0)) ||
		DX_TRY(pBuffer->Play(0,0,dwFlags)))
		return DS_ERR_SOUND_FAILED;
	LaPlayBuffer[nChannel]=pBuffer;
	LnSamplePlaying[nChannel]=nSample;
	return nChannel;
	}

void DS_AdjustVolumeAndPan(int nHandle,int nVolume,int nPan)
	{
	if (nHandle<0)
		return;
	IDirectSoundBuffer* pBuffer=LaPlayBuffer[nHandle];
	if (pBuffer)
		{
		DX_TRY(pBuffer->SetVolume(nVolume));
		DX_TRY(pBuffer->SetPan(nPan));
		}
	}

void DS_AdjustPitch(int nHandle,int nPitch)
	{
	if (nHandle<0)
		return;
	IDirectSoundBuffer* pBuffer=LaPlayBuffer[nHandle];
	if (pBuffer)
		{
		int nFrequency=(nPitch*LanSampleFrequency[LnSamplePlaying[nHandle]])>>16;
		DX_TRY(pBuffer->SetFrequency(nFrequency));
		}
	}

void DS_StopSample(int nHandle)
	{
	if (nHandle<0)
		return;
	IDirectSoundBuffer* pBuffer=LaPlayBuffer[nHandle];
	if (pBuffer)
		{
		DX_TRY(pBuffer->Stop());
		pBuffer->Release();
		LaPlayBuffer[nHandle]=0;
		}
	}

void DS_StopAllSamples()
	{
	for (int nChannel=0;nChannel<ArraySize(LaPlayBuffer);++nChannel)
		{
		IDirectSoundBuffer* pBuffer=LaPlayBuffer[nChannel];
		if (pBuffer)
			{
			DX_TRY(pBuffer->Stop());
			pBuffer->Release();
			LaPlayBuffer[nChannel]=0;
			}
		}
	}

local bool DS_SetOutputFormat()
	{
	Log(LT_Enter,"DS_SetOutputFormat");
    DSBUFFERDESC dsbd;
    InitDXStruct(dsbd);
    dsbd.dwFlags=DSBCAPS_PRIMARYBUFFER;
	IDirectSoundBuffer* pPrimary;
	if (DX_TRY(L_DirectSound->CreateSoundBuffer(&dsbd,&pPrimary,NULL)))
		{
		Log(LT_Error,"Can't get primary sound buffer");
		return false;
		}
	WAVEFORMATEX wf;
	Zero(wf);
	wf.wFormatTag=WAVE_FORMAT_PCM;
	wf.nChannels=2;
	wf.nSamplesPerSec=11025;
	wf.nAvgBytesPerSec=11025*2*2;
	wf.nBlockAlign=4;
	wf.wBitsPerSample=16;
	if (DX_TRY(pPrimary->SetFormat(&wf)))
		{
		Log(LT_Error,"Can't set sound output format to 11025;16;2");
		return false;
		}
	RELEASE(pPrimary);
	return true;
	}

bool DS_Init()
	{
	Log(LT_Enter,"DS_Init");
	return DS_MakeAdapterList();
	}

void DS_Cleanup()
	{
	Log(LT_Enter,"DS_Cleanup");
	// List<> destructor will empty adapter list
	}

void DS_Start(HWND hWnd)
	{

	
	Log(LT_Enter,"DS_Start");
	LtSoundEnabled=false;
	ZeroArray(L_DSBuffer);
	ZeroArray(LaPlayBuffer);
	LnSamplesPlaying=0;
	camera.mike_at_lara = false; //G_AppSettings.tLaraMic;
	
//	if (!G_AppSettings.tSoundEnabled)
//		return;
	
//	if (!G_AppSettings.SoundAdapter)
//		return;
	
//	G_SoundAdapter=G_SoundAdapterList[G_AppSettings.SoundAdapter];

	//G_SoundAdapter=G_SoundAdapterList[];	// Temp Select First One

	DS_FindPreferredAdapter(G_SoundAdapter.pGUID);

	if (!DS_Create(G_SoundAdapter.pGUID))
		return;			// Could this fail if someone else is using the sound card?  Do something better?
	if (!hWnd)
		hWnd=App.WindowHandle;	// RF
	if (DX_TRY(L_DirectSound->SetCooperativeLevel(hWnd,DSSCL_EXCLUSIVE)))
		{
		Log(LT_Error,"Can't set exclusive level for DirectSound");
		throw INIT_ERR_CantSetDSCooperativeLevel;
		}
	if (!DS_SetOutputFormat())
		Log(LT_Warning,"Couldn't set output format... sounds will be suboptimal");
	LtSoundEnabled=true;
	

//  	LtSoundEnabled=false;
	}		

void DS_Finish()
	{
	Log(LT_Enter,"DS_Finish");
	DS_FreeAllSamples();
	DS_Release();
	}

bool DS_IsSoundEnabled()
	{
	return LtSoundEnabled;
	}
