/*
 * DirectSound output code for MultiVoc
 * by Jonathon Fowler (jonof@edgenetwk.com)
 */
//-------------------------------------------------------------------------
/*
Duke Nukem Copyright (C) 1996, 2003 3D Realms Entertainment

This file is part of Duke Nukem 3D version 1.5 - Atomic Edition

Duke Nukem 3D 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.
*/
//-------------------------------------------------------------------------

#include "dsoundout.h"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <string.h>
#include "dsound.h"
#include "osd.h"

#include "winlayer.h"

#if defined(__WATCOMC__) || defined(_MSC_VER)
#include <malloc.h>
#endif


#ifndef RENDERTYPEWIN
#error The DirectSound output module for AudioLib only works with the Windows interface.
#endif

static CRITICAL_SECTION mutex;
static int _DSOUND_CriticalSectionAlloced = FALSE;
static HANDLE isrthread = NULL;
static HANDLE isrfinish = NULL;

static HMODULE             hDSoundDLL = NULL;
static LPDIRECTSOUND       lpDS = NULL;
static LPDIRECTSOUNDBUFFER lpDSBPrimary = NULL;
static LPDIRECTSOUNDBUFFER lpDSBSecondary = NULL;
static LPDIRECTSOUNDNOTIFY lpDSNotify = NULL;
static HANDLE              *hPosNotify = NULL;

static int (*_DSOUND_CallBack)(int) = NULL;
static int _DSOUND_BufferLength = 0;
static int _DSOUND_NumBuffers   = 0;
static char *_DSOUND_MixBuffer  = NULL;

static int DSOUND_Installed = FALSE;

int DSOUND_ErrorCode = DSOUND_Ok;

#define DSOUND_SetErrorCode( status ) \
   DSOUND_ErrorCode   = ( status );



/*
 * DisableInterrupts
 * Enter the critical section.
 */
int DisableInterrupts(void)
{
	if (!_DSOUND_CriticalSectionAlloced) return -1;

	EnterCriticalSection(&mutex);

	return 0;
}


/*
 * RestoreInterrupts
 * Leave the critical section.
 */
int RestoreInterrupts(int a)
{
	if (!_DSOUND_CriticalSectionAlloced) return -1;

	LeaveCriticalSection(&mutex);

	return 0;
	a=a;
}


/*
 * DSOUND_ErrorString
 * Returns a description of an error code.
 */
char *DSOUND_ErrorString(int errorcode)
{
	switch (errorcode) {
		case DSOUND_Warning:
		case DSOUND_Error:
			return DSOUND_ErrorString(DSOUND_ErrorCode);

		case DSOUND_Ok:
			return "DirectSound ok.";

		case DSOUND_NoDLL:
			return "Failed loading DSOUND.DLL.";

		case DSOUND_NoDirectSoundCreate:
			return "Failed getting DirectSoundCreate entry point.";

		case DSOUND_FailedDSC:
			return "DirectSoundCreate failed.";

		case DSOUND_FailedSetCoopLevel:
			return "Failed setting cooperative level.";

		case DSOUND_FailedCreatePrimary:
			return "Failed creating primary buffer.";

		case DSOUND_FailedSetFormat:
			return "Failed setting primary buffer format.";

		case DSOUND_FailedCreateSecondary:
			return "Failed creating secondary buffer.";

		case DSOUND_FailedCreateNotifyEvent:
			return "Failed creating notification event object.";

		case DSOUND_FailedQueryNotify:
			return "Failed querying notification interface.";

		case DSOUND_FailedSetNotify:
			return "Failed setting notification positions.";

		case DSOUND_FailedCreateFinishEvent:
			return "Failed creating finish event object.";

		case DSOUND_FailedCreateThread:
			return "Failed creating playback thread.";

		case DSOUND_FailedPlaySecondary:
			return "Failed playing secondary buffer.";

		case DSOUND_FailedGetExitCode:
			return "GetExitCodeThread failed.";

		default:
			return "Unknown DirectSound error code.";
	}
}


/*
 * DSOUND_Init
 * Initializes the DirectSound objects.
 */
int DSOUND_Init(int soundcard, int mixrate, int numchannels, int samplebits, int buffersize)
{
	HRESULT (WINAPI *aDirectSoundCreate)(LPGUID,LPDIRECTSOUND*,LPUNKNOWN);
	HRESULT hr;
	DSBUFFERDESC dsbuf;
	WAVEFORMATEX wfex;
	DSBPOSITIONNOTIFY posn;
	
	if (DSOUND_Installed) {
		DSOUND_Shutdown();
	}

	printOSD("Initializing DirectSound...\n");
	
	if (!_DSOUND_CriticalSectionAlloced) {
		// initialize the critical section object we'll use to
		// simulate (dis|en)abling interrupts
		InitializeCriticalSection(&mutex);
		_DSOUND_CriticalSectionAlloced = TRUE;
	}

	printOSD("  - Loading DSOUND.DLL\n");
	hDSoundDLL = LoadLibrary("DSOUND.DLL");
	if (!hDSoundDLL) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_NoDLL);
		return DSOUND_Error;
	}

	aDirectSoundCreate = (void *)GetProcAddress(hDSoundDLL, "DirectSoundCreate");
	if (!aDirectSoundCreate) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_NoDirectSoundCreate);
		return DSOUND_Error;
	}

	printOSD("  - Creating DirectSound object\n");
	hr = aDirectSoundCreate(NULL, &lpDS, NULL);
	if (hr != DS_OK) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedDSC);
		return DSOUND_Error;
	}

	hr = IDirectSound_SetCooperativeLevel(lpDS, (HWND)win_gethwnd(), DSSCL_EXCLUSIVE);
	if (hr != DS_OK) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedSetCoopLevel);
		return DSOUND_Error;
	}

	printOSD("  - Creating primary buffer\n");
	ZeroMemory(&dsbuf, sizeof(dsbuf));
	dsbuf.dwSize = sizeof(DSBUFFERDESC);
	dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
	hr = IDirectSound_CreateSoundBuffer(lpDS, &dsbuf, &lpDSBPrimary, NULL);
	if (hr != DS_OK) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedCreatePrimary);
		return DSOUND_Error;
	}

	printOSD("  - Setting primary buffer format\n"
	         "      Channels:    %d\n"
		 "      Sample rate: %dHz\n"
		 "      Sample size: %d bits\n",
		 numchannels, mixrate, samplebits);
	ZeroMemory(&wfex, sizeof(wfex));
	wfex.wFormatTag      = WAVE_FORMAT_PCM;
	wfex.nChannels       = numchannels;
	wfex.nSamplesPerSec  = mixrate;
	wfex.wBitsPerSample  = samplebits;
	wfex.nBlockAlign     = (wfex.wBitsPerSample / 8) * wfex.nChannels;
	wfex.nAvgBytesPerSec = wfex.nBlockAlign * wfex.nSamplesPerSec;
	hr = IDirectSoundBuffer_SetFormat(lpDSBPrimary, &wfex);
	if (hr != DS_OK) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedSetFormat);
		return DSOUND_Error;
	}

	printOSD("  - Creating secondary buffer\n");
	ZeroMemory(&dsbuf, sizeof(dsbuf));
	dsbuf.dwSize = sizeof(DSBUFFERDESC);
	dsbuf.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_LOCSOFTWARE;
	dsbuf.dwBufferBytes = buffersize;
	dsbuf.lpwfxFormat = &wfex;
	hr = IDirectSound_CreateSoundBuffer(lpDS, &dsbuf, &lpDSBSecondary, NULL);
	if (hr != DS_OK) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedCreateSecondary);
		return DSOUND_Error;
	}

	hr = IDirectSoundBuffer_QueryInterface(lpDSBSecondary, &IID_IDirectSoundNotify, &lpDSNotify);
	if (hr != DS_OK) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedQueryNotify);
		return DSOUND_Error;
	}

	hPosNotify = (HANDLE *)malloc(sizeof(HANDLE));
	if (!hPosNotify) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedSetNotify);
		return DSOUND_Error;
	}
	
	hPosNotify[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
	if (!hPosNotify) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedCreateNotifyEvent);
		return DSOUND_Error;
	}

	_DSOUND_BufferLength = 0;
	_DSOUND_NumBuffers   = 1;
	posn.dwOffset = 0;
	posn.hEventNotify = hPosNotify[0];

	hr = IDirectSoundNotify_SetNotificationPositions(lpDSNotify, 1, &posn);
	if (hr != DS_OK) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedSetNotify);
		return DSOUND_Error;
	}

	DSOUND_Installed = TRUE;
	
	DSOUND_SetErrorCode(DSOUND_Ok);
	return DSOUND_Ok;
}


/*
 * DSOUND_Shutdown
 * Shuts down DirectSound and it's associates.
 */
int DSOUND_Shutdown(void)
{
	int i;

	if (DSOUND_Installed) printOSD("Uninitializing DirectSound...\n");

	DSOUND_Installed = FALSE;

	DSOUND_StopPlayback();

	if (lpDSNotify) {
		IDirectSoundNotify_Release(lpDSNotify);
		lpDSNotify = NULL;
	}

	if (hPosNotify) {
		for (i=0; i<_DSOUND_NumBuffers; i++) {
			if (hPosNotify[i]) CloseHandle(hPosNotify[i]);
		}
		free(hPosNotify);
		hPosNotify = NULL;
	}

	if (lpDSBSecondary) {
		printOSD("  - Releasing secondary buffer\n");
		IDirectSoundBuffer_Stop(lpDSBSecondary);
		IDirectSoundBuffer_Release(lpDSBSecondary);
		lpDSBSecondary = NULL;
	}

	if (lpDSBPrimary) {
		printOSD("  - Releasing primary buffer\n");
		IDirectSoundBuffer_Release(lpDSBPrimary);
		lpDSBPrimary = NULL;
	}

	if (lpDS) {
		printOSD("  - Releasing DirectSound object\n");
		IDirectSound_Release(lpDS);
		lpDS = NULL;
	}

	if (hDSoundDLL) {
		printOSD("  - Unloading DSOUND.DLL\n");
		FreeLibrary(hDSoundDLL);
		hDSoundDLL = NULL;
	}
	
	DSOUND_SetErrorCode(DSOUND_Ok);
	return DSOUND_Ok;
}


/*
 * DSOUND_SetMixMode
 * Bit of filler for the future.
 */
int DSOUND_SetMixMode(int mode)
{
	return mode;
}


//#define DEBUGAUDIO

#ifdef DEBUGAUDIO
#include <sys/stat.h>
#endif
static DWORD WINAPI isr(LPVOID parm)
{
	HANDLE *handles;
	HRESULT hr;
	DWORD rv;
#ifdef DEBUGAUDIO
	int h;
#endif
	int p;
	LPVOID lockptr; DWORD lockbytes;
	LPVOID lockptr2; DWORD lockbytes2;

	handles = (HANDLE *)malloc(sizeof(HANDLE)*(1+_DSOUND_NumBuffers));
	if (!handles) return 1;
	
	handles[0] = isrfinish;
	for (p=0; p<_DSOUND_NumBuffers; p++)
		handles[p+1] = hPosNotify[p];

#ifdef DEBUGAUDIO
	h = creat("audio.raw",S_IREAD|S_IWRITE);
#endif
	
	while (1) {
		rv = WaitForMultipleObjects(1+_DSOUND_NumBuffers, handles, FALSE, INFINITE);

		if (!(rv >= WAIT_OBJECT_0 && rv <= WAIT_OBJECT_0+1+_DSOUND_NumBuffers)) return -1;

		if (rv == WAIT_OBJECT_0) {
			// we've been asked to finish up
			break;
		}

		// otherwise we just service the interrupt
		if (_DSOUND_CallBack) {
			DisableInterrupts();

			p = _DSOUND_CallBack(rv-WAIT_OBJECT_0-1);
#ifdef DEBUGAUDIO
			write(h, _DSOUND_MixBuffer + p*_DSOUND_BufferLength, _DSOUND_BufferLength);
#endif

			hr = IDirectSoundBuffer_Lock(lpDSBSecondary, p*_DSOUND_BufferLength, _DSOUND_BufferLength,
					&lockptr, &lockbytes, &lockptr2, &lockbytes2, 0);
			if (hr == DSERR_BUFFERLOST) {
				hr = IDirectSoundBuffer_Restore(lpDSBSecondary);
			}
			if (hr == DS_OK) {
				/*
#define copybuf(S,D,c) \
	({ void *__S=(S), *__D=(D); long __c=(c); \
	   __asm__ __volatile__ ("rep; movsl" \
		: "+S" (__S), "+D" (__D), "+c" (__c) : : "memory", "cc"); \
	 0; })
*/
				//copybuf(_DSOUND_MixBuffer + p * _DSOUND_BufferLength, lockptr, _DSOUND_BufferLength >> 2);
				memcpy(lockptr, _DSOUND_MixBuffer + p * _DSOUND_BufferLength, _DSOUND_BufferLength);
				IDirectSoundBuffer_Unlock(lpDSBSecondary, lockptr, lockbytes, lockptr2, lockbytes2);
			}

			RestoreInterrupts(0);
		}
	}

#ifdef DEBUGAUDIO
	close(h);
#endif

	return 0;
}


/*
 * DSOUND_BeginBufferedPlayback
 * Spins off a thread that behaves somewhat like the SoundBlaster DMA ISR did.
 */
int DSOUND_BeginBufferedPlayback(char *BufferStart, int (*CallBackFunc)(int), int buffersize, int numdivisions)
{
	DWORD threadid;
	HRESULT hr;
	DSBPOSITIONNOTIFY *posns;
	int i;
	
	_DSOUND_CallBack = CallBackFunc;
	_DSOUND_MixBuffer = BufferStart;

	if (!lpDSBSecondary) return DSOUND_Error;

	if (isrthread) {
		DSOUND_StopPlayback();
	}
	
	isrfinish = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (!isrfinish) {
		DSOUND_SetErrorCode(DSOUND_FailedCreateFinishEvent);
		return DSOUND_Error;
	}
	
	isrthread = CreateThread(NULL, 0, isr, NULL, CREATE_SUSPENDED, &threadid);
	if (!isrthread) {
		DSOUND_SetErrorCode(DSOUND_FailedCreateThread);
		return DSOUND_Error;
	}

	hPosNotify = (HANDLE *)malloc(sizeof(HANDLE)*numdivisions);
	if (!hPosNotify) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedSetNotify);
		return DSOUND_Error;
	}
	
	memset(hPosNotify, 0, sizeof(HANDLE)*numdivisions);
	for (i=0; i<numdivisions; i++) {
		hPosNotify[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
		if (!hPosNotify[i]) {
			DSOUND_Shutdown();
			DSOUND_SetErrorCode(DSOUND_FailedSetNotify);
			return DSOUND_Error;
		}
	}

	posns = (LPDSBPOSITIONNOTIFY)malloc(sizeof(DSBPOSITIONNOTIFY)*numdivisions);
	if (!posns) {
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedSetNotify);
		return DSOUND_Error;
	}

	_DSOUND_BufferLength = buffersize/numdivisions;
	_DSOUND_NumBuffers   = numdivisions;
	for (i=0; i<numdivisions; i++) {
		posns[i].dwOffset = i*_DSOUND_BufferLength;
		posns[i].hEventNotify = hPosNotify[i];
	}

	hr = IDirectSoundNotify_SetNotificationPositions(lpDSNotify, numdivisions, posns);
	if (hr != DS_OK) {
		free(posns);
		DSOUND_Shutdown();
		DSOUND_SetErrorCode(DSOUND_FailedSetNotify);
		return DSOUND_Error;
	}

	free(posns);

	SetThreadPriority(isrthread, THREAD_PRIORITY_ABOVE_NORMAL);
	ResumeThread(isrthread);

	hr = IDirectSoundBuffer_Play(lpDSBSecondary, 0, 0, DSBPLAY_LOOPING);
	if (hr != DS_OK) {
		DSOUND_SetErrorCode(DSOUND_FailedPlaySecondary);
		return DSOUND_Error;
	}

	return DSOUND_Ok;
}


/*
 * DSOUND_StopPlayback
 * Halts the playback thread.
 */
int DSOUND_StopPlayback(void)
{
//	DWORD exitcode;
	BOOL t;
	int i;
	
	if (isrthread) {
		SetEvent(isrfinish);

		printOSD("DirectSound: Waiting for sound thread to exit\n");
		if (WaitForSingleObject(isrthread, 300) == WAIT_OBJECT_0)
			printOSD("DirectSound: Sound thread has exited\n");
		else
			printOSD("DirectSound: Sound thread failed to exit!\n");
		/*
		while (1) {
			if (!GetExitCodeThread(isrthread, &exitcode)) {
				DSOUND_SetErrorCode(DSOUND_FailedGetExitCode);
				return DSOUND_Warning;
			}
			if (exitcode != STILL_ACTIVE) break;
		}*/

		CloseHandle(isrthread);
		isrthread = NULL;
	}

	if (isrfinish) {
		CloseHandle(isrfinish);
		isrfinish = NULL;
	}

	if (lpDSBSecondary) {
		IDirectSoundBuffer_Stop(lpDSBSecondary);
	}
	
	if (hPosNotify) {
		for (i=0; i<_DSOUND_NumBuffers; i++) {
			if (hPosNotify[i]) CloseHandle(hPosNotify[i]);
		}
		free(hPosNotify);
		hPosNotify = NULL;
	}

	return DSOUND_Ok;
}


