/*
 * 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"
#ifdef USE_OPENAL
#include "openal.h"
#endif
#include "compat.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;

    UNREFERENCED_PARAMETER(soundcard);

    if (DSOUND_Installed)
    {
        DSOUND_Shutdown();
    }

    initprintf("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;
    }

    initprintf("  - 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;
    }

    initprintf("  - 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;
    }

    initprintf("  - 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;
    }

    initprintf("  - 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;
    }

    initprintf("  - 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, (LPVOID *)&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) initprintf("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)
    {
        initprintf("  - Releasing secondary buffer\n");
        IDirectSoundBuffer_Stop(lpDSBSecondary);
        IDirectSoundBuffer_Release(lpDSBSecondary);
        lpDSBSecondary = NULL;
    }

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

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

    if (hDSoundDLL)
    {
        initprintf("  - 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;
    int rv;
#ifdef DEBUGAUDIO
    int h;
#endif
    int p;
    LPVOID lockptr; DWORD lockbytes;
    LPVOID lockptr2; DWORD lockbytes2;

    UNREFERENCED_PARAMETER(parm);

    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)
    {
#ifdef USE_OPENAL
        AL_Update();
#endif
        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); int __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;
    int i;

    if (isrthread)
    {
        SetEvent(isrfinish);

        initprintf("DirectSound: Waiting for sound thread to exit\n");
        if (WaitForSingleObject(isrthread, 300) == WAIT_OBJECT_0)
            initprintf("DirectSound: Sound thread has exited\n");
        else
            initprintf("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;
}


