#include <..\source\TAL\objbase.h>
#include <..\source\PAL\oshead.h>
#define INITGUID
#include <dsound.h>

typedef void	*sound_engine_t;

#define	INVALID_INDEX			0xFFFFFFFF
#define	INVALID_BUFFER_VALUE	INVALID_INDEX

/*
 *	Implementaion
 */

typedef struct	ds_sound_info_s
{
	IDirectSoundBuffer	*idsb;
	char				wave_name[_MAX_PATH];	/* variable sized */
}	ds_sound_info_t, *ds_sound_info_p;

typedef struct	ds_sound_engine_s
{
	libv_object_t	base;
	/*
	HANDLE				dsound;
	*/
	IDirectSound		*ids;
	vp_stack_t			sounds;
	HRESULT				err;
}	ds_sound_engine_t, *ds_sound_engine_p;

#ifdef	CHECKED_BUILD
V_LOCAL	char	*se_object="DSound engine";
#define	se_magic 			0x44534E44	/* DSND */
#endif	/* CHECKED_BUILD */

void	V_GLOBAL
seDestroyEngine(vhandle_t handle)
{
	int i,n;
	ds_sound_info_t **sounds;
	ds_sound_engine_t *se=(ds_sound_engine_t *)handle;
	if(!libvIsHandle(se,"seDestroyEngine",se_object,se_magic))
		return;
	/* Release all buffers */
	sounds=(ds_sound_info_t**)vpsGetStorage(&se->sounds);
	n=vpsCount(&se->sounds);
	for(i=0;i<n;i++)
	{
		if(sounds[i])
		{
			IDirectSoundBuffer_Release(sounds[i]->idsb);
			memFree(sounds[i]);
		}
	}
	vpsClear(&se->sounds);
	/* Release DirectSound */
	if(se->ids)
		se->ids->lpVtbl->Release(se->ids);
	/* Unload library */
	/* FreeLibrary(THIS(engine)->dsound); */
	memFreeObject(se);
}

/*
 *	'app_window' is a handle of main window.
 */
HINSTANCE 	hInstDS = NULL;
HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter);

vhandle_t V_GLOBAL
seCreateEngine(void *app_window)
{
	ds_sound_engine_t *se;
	if (!hInstDS)
	{
		hInstDS = LoadLibrary("dsound.dll");
		if (hInstDS == NULL)
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_WARNING, "Couldn't load dsound.dll\n");
#endif	/* CHECKED_BUILD */
			return NULL;
		}
		pDirectSoundCreate = (void *)GetProcAddress(hInstDS,"DirectSoundCreate");
		if (!pDirectSoundCreate)
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_WARNING,"Couldn't get DS proc addr\n");
#endif	/* CHECKED_BUILD */
			FreeLibrary(hInstDS);
			hInstDS=NULL;
			return NULL;
		}
	}

	ALLOC_OBJECT(se,ds_sound_engine_t,0,se_magic);
	vpsInit(&se->sounds);
	se->err=pDirectSoundCreate(NULL,&se->ids,NULL);
	
	if(se->err!=DS_OK)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"DirectSoundCreate() failed.\n");
#endif	/* CHECKED_BUILD */
		memFreeObject(se);
		FreeLibrary(hInstDS);
		hInstDS=NULL;
		return NULL;
	}

	se->err=IDirectSound_SetCooperativeLevel(se->ids,app_window,DSSCL_EXCLUSIVE);
	if(se->err!=DS_OK)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_WARNING,"seCreateEngine(): "
			"IDirectSound()->SetCooperativeLevel(DSSCL_EXCLUSIVE) failed.\n");
#endif	/* CHECKED_BUILD */
		se->err=IDirectSound_SetCooperativeLevel(se->ids,app_window,DSSCL_NORMAL);
		if(se->err!=DS_OK)
		{
#ifdef	CHECKED_BUILD
			conlPuts(LOG_ERROR,"seCreateEngine(): "
				"IDirectSound()->SetCooperativeLevel(DSSCL_NORMAL) failed.\n");
#endif	/* CHECKED_BUILD */
			se->ids->lpVtbl->Release(se->ids);
			memFreeObject(se);
			FreeLibrary(hInstDS); hInstDS=NULL;
			return NULL;
		}
	}

#if 0

// get access to the primary buffer, if possible, so we can set the
// sound hardware format
	memset (&dsbuf, 0, sizeof(dsbuf));
	dsbuf.dwSize = sizeof(DSBUFFERDESC);
	dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
	dsbuf.dwBufferBytes = 0;
	dsbuf.lpwfxFormat = NULL;

	memset(&dsbcaps, 0, sizeof(dsbcaps));
	dsbcaps.dwSize = sizeof(dsbcaps);
	primary_format_set = false;

	if (!COM_CheckParm ("-snoforceformat"))
	{
		if (DS_OK == pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL))
		{
			pformat = format;

			if (DS_OK != pDSPBuf->lpVtbl->SetFormat (pDSPBuf, &pformat))
			{
				if (snd_firsttime)
					Con_SafePrintf ("Set primary sound buffer format: no\n");
			}
			else
			{
				if (snd_firsttime)
					Con_SafePrintf ("Set primary sound buffer format: yes\n");

				primary_format_set = true;
			}
		}
	}

	if (!primary_format_set || !COM_CheckParm ("-primarysound"))
	{
	// create the secondary buffer we'll actually work with
		memset (&dsbuf, 0, sizeof(dsbuf));
		dsbuf.dwSize = sizeof(DSBUFFERDESC);
		dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE;
		dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
		dsbuf.lpwfxFormat = &format;

		memset(&dsbcaps, 0, sizeof(dsbcaps));
		dsbcaps.dwSize = sizeof(dsbcaps);

		if (DS_OK != pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL))
		{
			Con_SafePrintf ("DS:CreateSoundBuffer Failed");
			FreeSound ();
			return SIS_FAILURE;
		}

		shm->channels = format.nChannels;
		shm->samplebits = format.wBitsPerSample;
		shm->speed = format.nSamplesPerSec;

		if (DS_OK != pDSBuf->lpVtbl->GetCaps (pDSBuf, &dsbcaps))
		{
			Con_SafePrintf ("DS:GetCaps failed\n");
			FreeSound ();
			return SIS_FAILURE;
		}

		if (snd_firsttime)
			Con_SafePrintf ("Using secondary sound buffer\n");
	}
	else
	{
		if (DS_OK != pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_WRITEPRIMARY))
		{
			Con_SafePrintf ("Set coop level failed\n");
			FreeSound ();
			return SIS_FAILURE;
		}

		if (DS_OK != pDSPBuf->lpVtbl->GetCaps (pDSPBuf, &dsbcaps))
		{
			Con_Printf ("DS:GetCaps failed\n");
			return SIS_FAILURE;
		}

		pDSBuf = pDSPBuf;
		Con_SafePrintf ("Using primary sound buffer\n");
	}

	// Make sure mixer is active
	pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);

	if (snd_firsttime)
		Con_SafePrintf("   %d channel(s)\n"
		               "   %d bits/sample\n"
					   "   %d bytes/sec\n",
					   shm->channels, shm->samplebits, shm->speed);
	
	gSndBufSize = dsbcaps.dwBufferBytes;

// initialize the buffer
	reps = 0;

	while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, &lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
	{
		if (hresult != DSERR_BUFFERLOST)
		{
			Con_SafePrintf ("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n");
			FreeSound ();
			return SIS_FAILURE;
		}

		if (++reps > 10000)
		{
			Con_SafePrintf ("SNDDMA_InitDirect: DS: couldn't restore buffer\n");
			FreeSound ();
			return SIS_FAILURE;
		}

	}

	memset(lpData, 0, dwSize);
//		lpData[4] = lpData[5] = 0x7f;	// force a pop for debugging

	pDSBuf->lpVtbl->Unlock(pDSBuf, lpData, dwSize, NULL, 0);

	/* we don't want anyone to access the buffer directly w/o locking it first. */
	lpData = NULL; 

	pDSBuf->lpVtbl->Stop(pDSBuf);
	pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &mmstarttime.u.sample, &dwWrite);
	pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);

	shm->soundalive = true;
	shm->splitbuffer = false;
	shm->samples = gSndBufSize/(shm->samplebits/8);
	shm->samplepos = 0;
	shm->submission_chunk = 1;
	shm->buffer = (unsigned char *) lpData;
	sample16 = (shm->samplebits/8) - 1;

#endif

	return se;
}

/*
**	Returns sound ID or INVALID_BUFFER_VALUE on error
*/
u32_t	V_GLOBAL
seLoadSound(vhandle_t handle,char *wave_name)
{
	u8_stack_t decomp;
	vhandle_t wave;
	ds_sound_engine_t *se=(ds_sound_engine_t *)handle;
	WAVEFORMATEX wf;
	DSBUFFERDESC bd;
	int name_len;
	void *ptr1, *ptr2, *data;
	IDirectSoundBuffer *idsb;
	DWORD size1, size2;
	ds_sound_info_t *dsi;
	u32_t buffer_num;

	if(!libvIsHandle(handle,"seLoadSound",se_object,se_magic))
		return INVALID_BUFFER_VALUE;
	
	wave=wavOpenRead(wave_name);
	if(!wave)
		return INVALID_BUFFER_VALUE;
    u8sInit(&decomp);
	memZero(&wf,sizeof(WAVEFORMATEX));
	wf.wFormatTag=WAVE_FORMAT_PCM;
	wf.nChannels=(SHORT)wavNumChannels(wave);
	wf.nSamplesPerSec=wavSamplingFrequency(wave);
	wf.wBitsPerSample=(SHORT)wavBitsPerSample(wave);
	wf.nBlockAlign=(SHORT)((wf.wBitsPerSample>>3) * wavNumChannels(wave));
	wf.nAvgBytesPerSec=wf.nSamplesPerSec * wf.nBlockAlign;
	memZero(&bd,sizeof(DSBUFFERDESC));
	bd.dwSize=sizeof(DSBUFFERDESC);
	bd.dwFlags=DSBCAPS_CTRLPAN|DSBCAPS_CTRLVOLUME;
	bd.dwBufferBytes=wavNumSamples(wave) * wf.nBlockAlign;
	bd.lpwfxFormat=&wf;
	se->err=IDirectSound_CreateSoundBuffer(se->ids,&bd,&idsb,NULL);
	if(se->err!=DS_OK)
	{
		/* Reduce number of controls and try again */
		bd.dwFlags=DSBCAPS_CTRLVOLUME;
		se->err=IDirectSound_CreateSoundBuffer(se->ids,&bd,&idsb,NULL);
		if(se->err!=DS_OK)
		{
			fileClose(wave);
			return INVALID_BUFFER_VALUE;
		}
	}
	/* lock buffer and copy data */
	se->err=idsb->lpVtbl->Lock(idsb,0,bd.dwBufferBytes,&ptr1,&size1,
		&ptr2,&size2,/* DSBLOCK_ENTIREBUFFER */ 0);
	if(se->err!=DS_OK)
	{
		idsb->lpVtbl->Release(idsb);
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"seLoadSound('%s'): IDirectSoundBuffer->Lock() failed.\n",
			wave_name);
#endif	/* CHECKED_BUILD */
		se->err=DSERR_GENERIC;
		fileClose(wave);
		return INVALID_BUFFER_VALUE;
	}
	data=NULL;
	fileControl(wave,pctl_map_ispace,&data);
	if(data==NULL)
	{
		/* Compressed input ? */
		u32_t len;
		u8_t buf[4096];
		for(;;)
		{
			len=fileRead(wave,buf,4096);
			if(len == 0)
				break;
			u8sPushBuffer(&decomp,buf,len);
		}
		data = u8sGetStorage(&decomp);
	}
	memCopy(ptr1,data,size1);
	if(ptr2)
	{
		data = ((u8_t*)data) + size1;
		memCopy(ptr2,data,size2);
	}
	u8sClear(&decomp);
	IDirectSoundBuffer_Unlock(idsb,ptr1,size1,ptr2,size2);
	fileClose(wave);
	name_len=strlen(wave_name);
	dsi=(ds_sound_info_t*)memAlloc(INT_ALIGN(sizeof(void*)+name_len+4));
	dsi->idsb=idsb;
	memCopy(dsi->wave_name,wave_name,name_len+1);
	buffer_num=vpsStore(&se->sounds,dsi);
	return buffer_num;
}

/*
 *	Returns sound info with IDirectSoundBuffer or NULL.
 */
ds_sound_info_p	V_LOCAL
#ifdef	CHECKED_BUILD
get_sound_info(char *caller,sound_engine_t *engine,u32_t buffer_num)
#else	/* !CHECKED_BUILD */
#define	get_sound_info(caller,engine,buffer)	\
		get_sound_info_quiet(engine,buffer)
get_sound_info_quiet(sound_engine_t *engine,u32_t buffer_num)
#endif	/* !CHECKED_BUILD
 */
{
	u32_t len;
	ds_sound_info_t *info=NULL;
	ds_sound_engine_t *se=(ds_sound_engine_t *)engine;
	if(!libvIsHandle(engine,caller,se_object,se_magic))
		return NULL;
	len=vpsCount(&se->sounds);
	if(buffer_num<len)
		info=(ds_sound_info_t*)vpsGet(&se->sounds,buffer_num);
	if(!info)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"%s(%d): %s buffer.\n",
			caller,buffer_num,(buffer_num<len) ? "empty" : "invalid");
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	return info;
}

vboolean	V_GLOBAL
sePlaySound(sound_engine_t *engine,u32_t buffer_num)
{
	ds_sound_info_t *info;
	info=get_sound_info("sePlaySound",engine,buffer_num);
	if(!info)
		return false;
	IDirectSoundBuffer_SetCurrentPosition(info->idsb,0);
	((ds_sound_engine_t *)engine)->err=IDirectSoundBuffer_Play(info->idsb,0,0,0);
	return ((ds_sound_engine_t *)engine)->err == DS_OK;
}

vboolean	V_GLOBAL
seFreeSound(sound_engine_t *engine,u32_t buffer_num)
{
	ds_sound_info_t *info;
	info=get_sound_info("seFreeSound",engine,buffer_num);
	if(!info)
		return false;
	info->idsb->lpVtbl->Release(info->idsb);
	((ds_sound_engine_t *)engine)->err=DS_OK;
	vpsSet(&((ds_sound_engine_t *)engine)->sounds,buffer_num,NULL);
	return true;
}

/*
 *	Float attenuation in decibels; 0 mean full folume.
 */
vboolean	V_GLOBAL
seSetVolume(sound_engine_t *engine,u32_t buffer_num,u32_t attenuation)
{
	ds_sound_info_t *info;
	LONG la;
	info=get_sound_info("seSetVolume",engine,buffer_num);
	if(!info)
		return false;
	/*
	 *	Attenuation in hundredths of decibels, as documented for
	 *	IDirectSoundBuffer->SetVolume
	 */
	la=(LONG)(100 * attenuation);
	((ds_sound_engine_t *)engine)->err=
		IDirectSoundBuffer_SetVolume(info->idsb,la);
	return ((ds_sound_engine_t *)engine)->err==DS_OK;
}

/*
 *	Float pan in decibels; 0 mean full folume for both channels,
 *	negative mean right attenuation, positive - left attenuation.
 */
vboolean	V_GLOBAL
seSetPan(sound_engine_t *engine,u32_t buffer_num,vec_t pan)
{
	LONG lp;
	ds_sound_info_t *info;
	info=get_sound_info("seSetPan",engine,buffer_num);
	if(!info)
		return false;
	lp=(LONG)(100 * pan);
	((ds_sound_engine_t *)engine)->err=IDirectSoundBuffer_SetPan(info->idsb,lp);
	return ((ds_sound_engine_t *)engine)->err==DS_OK;
}

/*
 *	TODO: seLoopBuffer().
 */


int main()
{
	sound_engine_t se;
	u32_t sound;
	logStartConsoleLog();
	logUpperLevel(LOG_TRACE);
	vglCreateWindow("blah",0,0,128,128);
	se=seCreateEngine(GetForegroundWindow());
	if(!se)
	{
		printf("failed !");
		vglDestroyWindow(vglGetCurrentWindow());
		return -1;
	}
	sound=seLoadSound(se,"../../../media/uncompressed.wav");
	if(sound!=INVALID_BUFFER_VALUE)
	{
		sePlaySound(se,sound);
		fgetc(stdin);
	}
	seDestroyEngine(se);
	vglDestroyWindow(vglGetCurrentWindow());
	return 0;
}
