/* 
 * UAE - The Un*x Amiga Emulator
 *
 * Win32 interface
 *
 * Copyright 1997 Mathias Ortmann
 */

#ifdef __GNUC__
#define __int64 long long
#include "machdep/winstuff.h"
#else
#include <windows.h>
#include <ddraw.h>
#include <stdlib.h>
#include <stdarg.h>
#include <commctrl.h>
#include <commdlg.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <io.h>
#endif

#include "config.h"
#include "sysconfig.h"
#include "sysdeps.h"
#include "options.h"
#include "gensound.h"
#include "sounddep/sound.h"
#include "events.h"
#include "uae.h"
#include "include/memory.h"
#include "custom.h"
#include "osdep/win32gui.h"
#include "osdep/win32.h"

/* Sound emulation, Win32 interface */
static HGLOBAL hWaveHdr;
static LPWAVEHDR lpWaveHdr;
static HANDLE hData;
char *soundbuffer;
int samplecount;
static HANDLE hWaveOut;

static WAVEFORMATEX wavfmt;
char *sndptr, *sndptrmax, soundneutral;
signed int bytesinbuf;
static HGLOBAL hSndbuf;
static MMTIME mmtime;
static DWORD basevsynctime;

static long bufsamples;
static DWORD soundbufsize;

void close_sound (void)
{
    if (hWaveOut) {
	waveOutReset (hWaveOut);
	/* do we have to wait here? */
	waveOutUnprepareHeader (hWaveOut, lpWaveHdr, sizeof (WAVEHDR));
	waveOutUnprepareHeader (hWaveOut, lpWaveHdr + 1, sizeof (WAVEHDR));

	GlobalUnlock (hWaveHdr);
	GlobalFree (lpWaveHdr);

	GlobalUnlock (hData);
	GlobalFree (hData);

	waveOutClose (hWaveOut);

	hWaveOut = 0;
    }
}

static void prepare0 (void)
{
    lpWaveHdr[0].lpData = soundbuffer;
    lpWaveHdr[0].dwBufferLength = soundbufsize;
    lpWaveHdr[0].dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
    lpWaveHdr[0].dwLoops = 1;
    waveOutPrepareHeader (hWaveOut, lpWaveHdr, sizeof (WAVEHDR));
}

static void prepare1 (void)
{
    lpWaveHdr[1].lpData = soundbuffer + soundbufsize;
    lpWaveHdr[1].dwBufferLength = soundbufsize;
    lpWaveHdr[1].dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
    lpWaveHdr[1].dwLoops = 1;
    waveOutPrepareHeader (hWaveOut, lpWaveHdr + 1, sizeof (WAVEHDR));
}

static int current_sound_buffer;
static int did_write_1;
static int sound_on;

void flush_sound_buffer (void)
{
    if (!sound_on)
	return;

    switch (current_sound_buffer) {
     case 0:
	prepare0 ();
	waveOutWrite (hWaveOut, lpWaveHdr, sizeof (WAVEHDR));
	if (did_write_1) {
	    while (((*(volatile DWORD *)&lpWaveHdr[1].dwFlags) & WHDR_DONE) == 0)
		;
	    waveOutUnprepareHeader (hWaveOut, lpWaveHdr + 1, sizeof (WAVEHDR));
	}
	current_sound_buffer = 1;
	sndptrmax = soundbuffer + 2*soundbufsize;
	break;
     case 1:
	prepare1 ();
	waveOutWrite (hWaveOut, lpWaveHdr + 1, sizeof (WAVEHDR));
	did_write_1 = 1;
	while (((*(volatile DWORD *)&lpWaveHdr[0].dwFlags) & WHDR_DONE) == 0)
	    ;
	waveOutUnprepareHeader (hWaveOut, lpWaveHdr, sizeof (WAVEHDR));
	current_sound_buffer = 0;
	sndptrmax = soundbuffer + soundbufsize;
	sndptr = soundbuffer;
	break;
    }
}

void startsound (void)
{
    sound_on = 1;
    did_write_1 = 0;
    current_sound_buffer = 0;
    sndptrmax = soundbuffer + soundbufsize;
    sndptr = soundbuffer;
}

void stopsound (void)
{
    if (!sound_on)
	return;

    sound_on = 0;
    if (current_sound_buffer == 1) {
	while (((*(volatile DWORD *)&lpWaveHdr[0].dwFlags) & WHDR_DONE) == 0)
	    ;
    } else if (did_write_1) {
	while (((*(volatile DWORD *)&lpWaveHdr[1].dwFlags) & WHDR_DONE) == 0)
	    ;
    }
    waveOutUnprepareHeader (hWaveOut, lpWaveHdr, sizeof (WAVEHDR));
    waveOutUnprepareHeader (hWaveOut, lpWaveHdr + 1, sizeof (WAVEHDR));
    waveOutReset (hWaveOut);
    did_write_1 = 0;
}

static int init_sound_win32 (void)
{
    MMRESULT mmres;
    DWORD id;
    long i;
    __int64 freq;

    if (currprefs.produce_sound < 0) {
	currprefs.produce_sound = 0;
	MyOutputDebugString ("FrameSync disabled.\n");
    } else {
	if (vsynctime > 0)
	    vsynctime = vsynctime * 9 / 10;
    }

    if (currprefs.produce_sound < 2) {
	MyOutputDebugString ("Sound output disabled.\n");
	return 1;
    }
    mmtime.wType = TIME_SAMPLES;

    wavfmt.wFormatTag = WAVE_FORMAT_PCM;
    wavfmt.nChannels = 1 + !!currprefs.stereo;
    wavfmt.nSamplesPerSec = currprefs.sound_freq;
    wavfmt.nBlockAlign = currprefs.sound_bits / 8 * wavfmt.nChannels;
    wavfmt.nAvgBytesPerSec = wavfmt.nBlockAlign * currprefs.sound_freq;
    wavfmt.wBitsPerSample = currprefs.sound_bits;

    soundneutral = currprefs.sound_bits == 8 ? 128 : 0;
    bufsamples = 1024 * currprefs.sound_freq / 22050;
    soundbufsize = bufsamples * wavfmt.nBlockAlign * 4;

    if (!(hData = GlobalAlloc (GMEM_MOVEABLE | GMEM_SHARE, soundbufsize * 2))) {
	MyOutputDebugString ("Failed to allocate sound buffer!\n");
	return 0;
    }
    if (!(soundbuffer = GlobalLock (hData))) {
	MyOutputDebugString ("Failed to lock sound buffer!\n");
	return 0;
    }
    if (!(hWaveHdr = GlobalAlloc (GMEM_MOVEABLE | GMEM_SHARE, (DWORD) 2*sizeof (WAVEHDR)))) {
	MyOutputDebugString ("Failed to allocate wave header!\n");
	return 0;
    }
    if (!(lpWaveHdr = (LPWAVEHDR) GlobalLock (hWaveHdr))) {
	MyOutputDebugString ("Failed to lock wave header!\n");
	return 0;
    }
    if (mmres = waveOutOpen (&hWaveOut, WAVE_MAPPER, &wavfmt, 0, 0, CALLBACK_NULL)) {
	MyOutputDebugString ("Sound device failed to open with error code %d.\n", mmres);
	return 0;
    }
    basevsynctime = vsynctime;
    sndptrmax = soundbuffer + soundbufsize;

    startsound ();

    return currprefs.sound_freq;
}

void adjust_sound_timing (void)
{
    static DWORD last;
    long diff;

    return;

    waveOutGetPosition (hWaveOut, &mmtime, sizeof (mmtime));
    if (!mmtime.u.sample)
	mmtime.u.sample++;

    if (last == mmtime.u.sample)
	return;
    last = mmtime.u.sample;

    diff = samplecount - mmtime.u.sample;

    if (diff < -bufsamples || diff > bufsamples) {
	samplecount = mmtime.u.sample;
	sndptr = soundbuffer + (samplecount % bufsamples) * wavfmt.nBlockAlign;
    }
    if (diff < 0)
	vsynctime = basevsynctime * 5 / 6;
    else if (diff > 0)
	vsynctime = basevsynctime * 7 / 6;
}

int setup_sound (void)
{
    sound_available = 1;
    return 1;
}

int init_sound (void)
{
    int rate;

    if ((rate = init_sound_win32 ()) < 2)
	return rate;

    sample_evtime = (long) maxhpos * maxvpos * 50 / rate;

    if (currprefs.sound_bits == 16) {
	init_sound_table16 ();
	eventtab[ev_sample].handler = currprefs.stereo ? sample16s_handler : sample16_handler;
    } else {
	init_sound_table8 ();
	eventtab[ev_sample].handler = currprefs.stereo ? sample8s_handler : sample8_handler;
    }

    MyOutputDebugString ("Sound driver found and configured for %d bits at %d Hz %s\n",
			 currprefs.sound_bits, rate, currprefs.stereo ? "stereo" : "");

    return 1;
}
