/*
Copyright (C) 1997-2001 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#ifndef _WIN32
#  error You should not be compiling this file on this platform
#endif

#ifndef USE_DSOUND
#  error You should not be compiling this file without USE_DSOUND defined
#endif

#include "win_local.h"
#include <mmsystem.h>
#include <dsound.h>
#include "snd_local.h"

typedef HRESULT (WINAPI *LPDIRECTSOUNDCREATE)( LPCGUID, LPDIRECTSOUND *, LPUNKNOWN );

// 64K is > 1 second at 16-bit, 22050 Hz
#define	WAV_BUFFERS				64
#define	WAV_MASK				0x3F
#define	WAV_BUFFER_SIZE			0x0400
#define SECONDARY_BUFFER_SIZE	0x10000

// starts at 0 for disabled
static int	sample16;

static DWORD	locksize;

static MMTIME		mmstarttime;

static LPDIRECTSOUND pDS;
static LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf;

static HINSTANCE hInstDS;

static DWORD		gSndBufSize;

static const char *DSoundError( int error ) {
	switch ( error ) {
	case DSERR_BUFFERLOST:
		return "DSERR_BUFFERLOST";
	case DSERR_INVALIDCALL:
		return "DSERR_INVALIDCALL";
	case DSERR_INVALIDPARAM:
		return "DSERR_INVALIDPARAM";
	case DSERR_PRIOLEVELNEEDED:
		return "DSERR_PRIOLEVELNEEDED";
	}

	return "<unknown error>";
}

/*
** DS_DestroyBuffers
*/
static void DS_DestroyBuffers( void ) {
	Com_DPrintf( "Destroying DS buffers\n" );
	if( pDS ) {
		Com_DPrintf( "...setting NORMAL coop level\n" );
		IDirectSound_SetCooperativeLevel( pDS, hWndMain, DSSCL_NORMAL );
	}

	if( pDSBuf ) {
		Com_DPrintf( "...stopping and releasing sound buffer\n" );
		IDirectSoundBuffer_Stop( pDSBuf );
		IDirectSoundBuffer_Release( pDSBuf );
	}

	// only release primary buffer if it's not also the mixing buffer we just released
	if( pDSPBuf && ( pDSBuf != pDSPBuf ) ) {
		Com_DPrintf( "...releasing primary buffer\n" );
		IDirectSoundBuffer_Release( pDSPBuf );
	}
	pDSBuf = NULL;
	pDSPBuf = NULL;

	dma.buffer = NULL;
}

/*
==============
DS_Shutdown

Reset the sound device for exiting
===============
*/
static void DS_Shutdown(void) {
	Com_Printf( "Shutting down DirectSound\n" );

	if( pDS ) {
		DS_DestroyBuffers();

		Com_DPrintf( "...releasing DS object\n" );
		IDirectSound_Release( pDS );
	}

	if ( hInstDS ) {
		Com_DPrintf( "...freeing DSOUND.DLL\n" );
		FreeLibrary( hInstDS );
		hInstDS = NULL;
	}

	pDS = NULL;
	pDSBuf = NULL;
	pDSPBuf = NULL;
}

/*
** DS_CreateBuffers
*/
static qboolean DS_CreateBuffers( void ) {
	DSBUFFERDESC	dsbuf;
	DSBCAPS			dsbcaps;
	WAVEFORMATEX	format;
	DWORD			dwWrite;

	memset (&format, 0, sizeof(format));
	format.wFormatTag = WAVE_FORMAT_PCM;
    format.nChannels = dma.channels;
    format.wBitsPerSample = dma.samplebits;
    format.nSamplesPerSec = dma.speed;
    format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
    format.cbSize = sizeof( format );
    format.nAvgBytesPerSec = format.nSamplesPerSec*format.nBlockAlign; 

	Com_DPrintf( "Creating DS buffer\n" );

	Com_DPrintf("...setting PRIORITY coop level: " );
	if ( DS_OK != IDirectSound_SetCooperativeLevel( pDS, hWndMain, DSSCL_PRIORITY ) ) {
		Com_DPrintf ("failed\n");
		return qfalse;
	}
	Com_DPrintf("ok\n" );

// create the secondary buffer we'll actually work with
	memset (&dsbuf, 0, sizeof(dsbuf));
	dsbuf.dwSize = sizeof(DSBUFFERDESC);
	dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCHARDWARE;
	dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
	dsbuf.lpwfxFormat = &format;

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

	Com_DPrintf( "...creating secondary buffer: " );
	if (DS_OK != IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL)) {
		dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE;
		if (DS_OK != IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL)) {
			Com_DPrintf( "failed\n" );
			return qfalse;
		}

		Com_DPrintf( "ok\n...forced to software\n" );
	} else  {
		Com_DPrintf( "ok\n...locked hardware\n" );
	}

	dma.channels = format.nChannels;
	dma.samplebits = format.wBitsPerSample;
	dma.speed = format.nSamplesPerSec;

	if (DS_OK != IDirectSoundBuffer_GetCaps (pDSBuf, &dsbcaps)) {
		Com_DPrintf ("*** GetCaps failed ***\n");
		return qfalse;
	}

	// Make sure mixer is active
	if (DS_OK != IDirectSoundBuffer_Play (pDSBuf, 0, 0, DSBPLAY_LOOPING)) {
		Com_DPrintf ("*** Play failed ***\n");
		return qfalse;
	}

	Com_DPrintf(    "   %d channel(s)\n"
		            "   %d bits/sample\n"
					"   %d bytes/sec\n",
					dma.channels, dma.samplebits, dma.speed);
	
	gSndBufSize = dsbcaps.dwBufferBytes;

	IDirectSoundBuffer_Stop(pDSBuf);
	IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &mmstarttime.u.sample, &dwWrite);
	IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);

	dma.samples = gSndBufSize/(dma.samplebits/8);
	dma.samplepos = 0;
	dma.submission_chunk = 1;
	dma.buffer = NULL;
	sample16 = (dma.samplebits/8) - 1;

	return qtrue;
}



/*
==================
DS_Init

Direct-Sound support
==================
*/
static sndinitstat DS_Init (void) {
	DSCAPS			dscaps;
	HRESULT			hresult;
	LPDIRECTSOUNDCREATE pDirectSoundCreate;

	memset (&dma, 0, sizeof (dma));
	dma.channels = 2;
	dma.samplebits = 16;

	switch( s_khz->integer ) {
    case 48:
		dma.speed = 48000;
        break;
    case 44:
		dma.speed = 44100;
        break;
    case 22:
		dma.speed = 22050;
        break;
    default:
		dma.speed = 11025;
        break;
    }

	Com_Printf( "Initializing DirectSound\n");

	if ( !hInstDS ) {
		Com_DPrintf( "...loading dsound.dll: " );

		hInstDS = LoadLibrary("dsound.dll");
		if (hInstDS == NULL) {
			Com_DPrintf ("failed\n");
			return SIS_FAILURE;
		}
		Com_DPrintf ("ok\n");
	}

	pDirectSoundCreate = ( LPDIRECTSOUNDCREATE )
		GetProcAddress(hInstDS,"DirectSoundCreate");
	if (!pDirectSoundCreate) {
		Com_DPrintf ("...couldn't get DS proc addr\n");
		return SIS_FAILURE;
	}

	Com_DPrintf( "...creating DS object: " );
	while ( ( hresult = pDirectSoundCreate( NULL, &pDS, NULL ) ) != DS_OK ) {
		if (hresult != DSERR_ALLOCATED) {
			Com_DPrintf( "failed\n" );
			return SIS_FAILURE;
		}

		if (MessageBox (NULL,
						"The sound hardware is in use by another app.\n\n"
					    "Select Retry to try to start sound again or Cancel to run " APPLICATION " with no sound.",
						"Sound not available",
						MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY)
		{
			Com_DPrintf ("failed, hardware already in use\n" );
			return SIS_NOTAVAIL;
		}
	}
	Com_DPrintf( "ok\n" );

	dscaps.dwSize = sizeof(dscaps);

	if( DS_OK != IDirectSound_GetCaps( pDS, &dscaps ) ) {
		Com_DPrintf ("...couldn't get DS caps\n");
		DS_Shutdown();
		return SIS_FAILURE;
	}

	if( dscaps.dwFlags & DSCAPS_EMULDRIVER ) {
		Com_DPrintf ("...no DSound driver found\n" );
		DS_Shutdown();
		return SIS_FAILURE;
	}

	if( !DS_CreateBuffers() ) {
		DS_Shutdown ();
		return SIS_FAILURE;
	}

	Com_DPrintf("...completed successfully\n" );

	return SIS_SUCCESS;
}

/*
==============
DS_GetDMAPos

return the current sample position (in mono samples read)
inside the recirculating dma buffer, so the mixing code will know
how many sample are required to fill it up.
===============
*/
static int DS_GetDMAPos(void) {
	MMTIME	mmtime;
	int		s;
	DWORD	dwWrite;

	if (!pDSBuf) {
		return 0;
	}

	mmtime.wType = TIME_SAMPLES;
	IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &mmtime.u.sample, &dwWrite);
	s = mmtime.u.sample - mmstarttime.u.sample;

	s >>= sample16;
	s &= (dma.samples-1);

	return s;
}

/*
==============
DS_BeginPainting

Makes sure dma.buffer is valid
===============
*/
static void DS_BeginPainting (void) {
	int		reps;
	DWORD	dwSize2;
	DWORD	*pbuf, *pbuf2;
	HRESULT	hresult;
	DWORD	dwStatus;

	if (!pDSBuf)
		return;

	// if the buffer was lost or stopped, restore it and/or restart it
	if (IDirectSoundBuffer_GetStatus (pDSBuf, &dwStatus) != DS_OK) {
		Com_EPrintf ("DS_BeginPainting: Couldn't get sound buffer status\n");
		DS_Shutdown ();
		return;
	}
	
	if (dwStatus & DSBSTATUS_BUFFERLOST)
		IDirectSoundBuffer_Restore (pDSBuf);
	
	if (!(dwStatus & DSBSTATUS_PLAYING))
		IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING);

	// lock the dsound buffer

	reps = 0;
	dma.buffer = NULL;

	while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, ( void ** )&pbuf, &locksize, 
								   ( void ** )&pbuf2, &dwSize2, 0)) != DS_OK)
	{
		if (hresult != DSERR_BUFFERLOST) {
			Com_EPrintf( "DS_BeginPainting: Lock failed with error '%s'\n", DSoundError( hresult ) );
			DS_Shutdown ();
			return;
		}
	
		IDirectSoundBuffer_Restore( pDSBuf );

		if (++reps > 2)
			return;
	}
	dma.buffer = (byte *)pbuf;
}

/*
==============
DS_Submit

Send sound to device if buffer isn't really the dma buffer
Also unlocks the dsound buffer
===============
*/
static void DS_Submit(void) {
	if (!pDSBuf)
		return;

	// unlock the dsound buffer
	IDirectSoundBuffer_Unlock(pDSBuf, dma.buffer, locksize, NULL, 0);
}

/*
===========
DS_Activate

Called when the main window gains or loses focus.
The window have been destroyed and recreated
between a deactivate and an activate.
===========
*/
static void DS_Activate (qboolean active) {
	if( !pDS ) {
		return;
	}
	if( active ) {
		if( !DS_CreateBuffers() ) {
			Com_EPrintf( "DS_Activate: DS_CreateBuffers failed\n" );
			DS_Shutdown();
		}
	} else {
		DS_DestroyBuffers();
	}
}

void DS_FillAPI( snddmaAPI_t *api ) {
	api->Init = DS_Init;
	api->GetDMAPos = DS_GetDMAPos;
	api->Shutdown = DS_Shutdown;
	api->BeginPainting = DS_BeginPainting;
	api->Submit = DS_Submit;
	api->Activate = DS_Activate;
}

