#include "driver.h"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>
#include <dsound.h>
#include "osdepend.h"
#include "win32.h"
#include "video.h"


#define INGORE_UNDERFLOW_FRAMES 100
#define MAX_BUFFER_SIZE         (128 * 1024)
#define MAX_SAMPLE_ADJUST       16

#ifdef MAME_AVI
extern int GetAviCapture(void);
#endif /* MAME_AVI */


static struct RiffWaveHeader
{
    FOURCC     ckid;
    DWORD      cksize;
    FOURCC     fccType;
    FOURCC     fmt;
    DWORD      fmtsize;
    WAVEFORMAT wfmt;
    WORD       nBitRate;
    FOURCC     data;
    DWORD      datasize;
} wh;

static void *wavfile = NULL;


static LPDIRECTSOUND        ds;
static LPDIRECTSOUNDBUFFER  dsb;

static int                  sound_type;
static int                  attenuation;
static int                  stereo_factor;
static int                  voice_pos;
static int                  buffer_length;

static INT16                *stream_cache_data;
static UINT32               samples_per_frame;
static UINT32               samples_this_frame;
static UINT32               stream_cache_len;

/* sound type 1 (uomame32j) */
static int                  sound_delay;
static int                  first_time;

/* sound type 2 (mamew) */
static double               samples_per_frame2;
static double               samples_left_over;

static int                  current_adjustment;
static int                  lower_thresh;
static int                  upper_thresh;

static int                  consecutive_lows;
static int                  consecutive_mids;
static int                  consecutive_highs;


typedef HRESULT (WINAPI *dsc_proc)(GUID FAR *lpGUID,
                                   LPDIRECTSOUND FAR *lplpDS,
                                   IUnknown FAR *pUnkOuter);


/********************************************************************/

void win32_sound_init(options_type *osd_options)
{
    ds                = NULL;
    dsb               = NULL;
    attenuation       = 0;
    voice_pos         = 0;
    first_time        = 1;
    stream_cache_data = NULL;
    sound_delay       = 0;

    if (osd_options->sound_type)
    {
        HANDLE   hDLL;
        UINT     error_mode;
        int      done = 0;

        /* Turn off error dialog for this call */
        error_mode = SetErrorMode(0);
        hDLL = LoadLibrary("dsound.dll");
        SetErrorMode(error_mode);

        if (hDLL != NULL)
        {
            dsc_proc _DirectSoundCreate = (dsc_proc)GetProcAddress(hDLL, "DirectSoundCreate");

            if (_DirectSoundCreate != NULL)
            {
                if (_DirectSoundCreate(NULL, &ds, NULL) == DS_OK)
                {
                    DSCAPS dsound_caps;

                    memset(&dsound_caps, 0, sizeof(dsound_caps));
                    dsound_caps.dwSize = sizeof(dsound_caps);
                    if (IDirectSound_GetCaps(ds, &dsound_caps) == DS_OK)
                    {
                        sound_delay = osd_options->sound_delay;
                        done = 1;
                    }
                }
            }

            FreeLibrary(hDLL);
        }

        if (done == 0)
        {
            Machine->sample_rate = 0;
            osd_options->sound_type = 0;
            ds = NULL;
        }
    }
    sound_type = osd_options->sound_type;
}


void win32_sound_exit(void)
{
    if (dsb != NULL)
    {
        IDirectSoundBuffer_Stop(dsb);
        IDirectSoundBuffer_Release(dsb);
        dsb = NULL;
    }
    if (ds != NULL)
    {
        IDirectSound_Release(ds);
        ds = NULL;
    }
}

void win32_sound_calc_samples(void)
{
    if (sound_type == 1)
    {
        if (dsb && stream_cache_data)
        {
            DWORD curpos;
            static DWORD prevpos;

            IDirectSoundBuffer_GetCurrentPosition(dsb, &curpos, NULL);
            if (first_time)
            {
                prevpos = curpos - samples_per_frame * sizeof(INT16) * stereo_factor;
                first_time = 0;
            }
            else
            {
                if (curpos > prevpos)
                    samples_per_frame = (curpos - prevpos) / (sizeof(INT16) * stereo_factor);
                else
                    samples_per_frame = ((curpos + buffer_length) - prevpos) / (sizeof(INT16) * stereo_factor);
            }
            prevpos = curpos;

            voice_pos += stream_cache_len;
            if (voice_pos >= buffer_length)
                voice_pos -= buffer_length;
        }
    }
}

/********************************************************************/

int osd_start_audio_stream(int stereo)
{
    stereo = (stereo) ? 1 : 0;

    if (stereo && (Machine->drv->sound_attributes & SOUND_SUPPORTS_STEREO))
        stereo_factor = 2;
    else
        stereo_factor = 1;

    samples_per_frame2 = (double)Machine->sample_rate / (double)Machine->drv->frames_per_second;
    samples_per_frame  = (UINT32)samples_per_frame2;
    samples_this_frame = samples_per_frame;

    dsb                = NULL;
    voice_pos          = 0;
    first_time         = 1;
    stream_cache_data  = NULL;

    current_adjustment = 0;
    consecutive_lows   = 0;
    consecutive_mids   = 0;
    consecutive_highs  = 0;

    if (Machine->sample_rate != 0)
    {
        LPDIRECTSOUNDBUFFER  primary_buffer;
        DSBUFFERDESC         dsb_desc;
        WAVEFORMATEX         wfx;
        void *buffer;
        DWORD length;

        wfx.wBitsPerSample  = 16;
        wfx.wFormatTag      = WAVE_FORMAT_PCM;
        wfx.nChannels       = stereo_factor;
        wfx.nSamplesPerSec  = Machine->sample_rate;
        wfx.nBlockAlign     = wfx.wBitsPerSample * wfx.nChannels / 8;
        wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;

        if (sound_type == 1)
        {
            buffer_length = ((samples_per_frame * sizeof(INT16) * stereo_factor) + 15) & ~15;
            buffer_length *= 12;
        }
        else
        {
            buffer_length = ((UINT64)MAX_BUFFER_SIZE * (UINT64)wfx.nSamplesPerSec) / 44100;
            buffer_length = (buffer_length * wfx.nBlockAlign) / 4;
            buffer_length = (buffer_length * 30) / Machine->drv->frames_per_second;
            buffer_length = (buffer_length / 1024) * 1024;

            lower_thresh = 1 * buffer_length / 5;
            upper_thresh = 2 * buffer_length / 5;

            samples_left_over = samples_per_frame2;
            samples_this_frame = (UINT32)samples_left_over;
            samples_left_over -= (double)samples_this_frame;
        }

        memset(&dsb_desc, 0, sizeof(dsb_desc));
        dsb_desc.dwSize      = sizeof(dsb_desc);
        dsb_desc.dwFlags     = DSBCAPS_PRIMARYBUFFER | DSBCAPS_GETCURRENTPOSITION2;
        dsb_desc.lpwfxFormat = NULL;

        if (IDirectSound_SetCooperativeLevel(ds, MAME32App.m_hWnd, DSSCL_PRIORITY) != DS_OK)
            return 1;

        if (IDirectSound_CreateSoundBuffer(ds, &dsb_desc, &primary_buffer, NULL) != DS_OK)
            return 1;

        if (IDirectSoundBuffer_SetFormat(primary_buffer, &wfx) != DS_OK)
        {
            IDirectSoundBuffer_Release(primary_buffer);
            return 1;
        }

        if (IDirectSoundBuffer_GetFormat(primary_buffer, &wfx, sizeof(wfx), NULL) != DS_OK)
        {
            IDirectSoundBuffer_Release(primary_buffer);
            return 1;
        }
        IDirectSoundBuffer_Release(primary_buffer);


        memset(&dsb_desc, 0, sizeof(dsb_desc));
        dsb_desc.dwSize        = sizeof(dsb_desc);
        dsb_desc.dwFlags       = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
        dsb_desc.dwBufferBytes = buffer_length;
        dsb_desc.lpwfxFormat   = &wfx;

        if (IDirectSound_CreateSoundBuffer(ds, &dsb_desc, &dsb, NULL) != DS_OK)
        {
            dsb = NULL;
            return 1;
        }

        if (IDirectSoundBuffer_Lock(dsb, 0, buffer_length, &buffer, &length, NULL, NULL, 0) != DS_OK)
        {
            IDirectSoundBuffer_Release(dsb);
            return 1;
        }

        memset(buffer, 0, length);

        IDirectSoundBuffer_Unlock(dsb, buffer, length, NULL, 0);

        if (IDirectSoundBuffer_Play(dsb, 0, 0, DSBPLAY_LOOPING) != DS_OK)
        {
            osd_stop_audio_stream();
            return 1;
        }
    }

    return samples_this_frame;
}


void osd_stop_audio_stream(void)
{
    if (wavfile)
    {
        osd_stop_log_wave();
        wavfile = 0;
    }
    if (dsb)
    {
        IDirectSoundBuffer_Stop(dsb);
        IDirectSoundBuffer_Release(dsb);
        dsb = NULL;
    }
}


int osd_update_audio_stream(INT16 *buffer)
{
    void *buffer1, *buffer2;
    DWORD length1, length2;
    HRESULT result;

    stream_cache_len = samples_this_frame * sizeof(INT16) * stereo_factor;

    if (sound_type == 1)
    {
        if (dsb)
        {
            if (stream_cache_data == NULL)
            {
                DWORD curpos;
                int i;

                IDirectSoundBuffer_GetCurrentPosition(dsb, &curpos, NULL);

                voice_pos = curpos + sound_delay * stream_cache_len;
                if (voice_pos >= buffer_length)
                    voice_pos -= buffer_length;

                for (i = 0; i < stream_cache_len; i++)
                {
                    if (buffer[i] != 0)
                    {
                        stream_cache_data = buffer;
                        break;
                    }
                }
            }
            else
                stream_cache_data = buffer;

            result = IDirectSoundBuffer_Lock(dsb,
                                             voice_pos,
                                             stream_cache_len,
                                             &buffer1, &length1,
                                             &buffer2, &length2,
                                             0);
            if (result == DS_OK)
            {
                memcpy(buffer1, buffer, length1);
                memcpy(buffer2, ((UINT8 *)buffer + length1), length2);

                IDirectSoundBuffer_Unlock(dsb, buffer1, length1, buffer2, length2);
            }
        }

        samples_this_frame = samples_per_frame;
    }
    else
    {
        if (dsb)
        {
            DWORD curpos;
            int   original_bytes;
            int   cur_bytes;
            INT16 *stream_data;

            stream_data = stream_cache_data = buffer;

            IDirectSoundBuffer_GetCurrentPosition(dsb, &curpos, NULL);
            if (voice_pos > curpos)
                original_bytes = voice_pos - curpos;
            else
                original_bytes = buffer_length + voice_pos - curpos;

#ifdef MAME_AVI
            if (throttled() || !GetAviCapture())
#else
            if (throttled())
#endif /* MAME_AVI */
            {
                if (original_bytes < lower_thresh)
                {
                    consecutive_lows++;
                    consecutive_mids = 0;
                    consecutive_highs = 0;
                    current_adjustment = (consecutive_lows < MAX_SAMPLE_ADJUST) ? consecutive_lows : MAX_SAMPLE_ADJUST;
                }
                else if (original_bytes > upper_thresh)
                {
                    consecutive_lows = 0;
                    consecutive_mids = 0;
                    consecutive_highs++;

                    current_adjustment = (consecutive_highs < MAX_SAMPLE_ADJUST) ? -consecutive_highs : -MAX_SAMPLE_ADJUST;
                }
                else
                {
                    consecutive_lows = 0;
                    consecutive_mids++;
                    consecutive_highs = 0;

                    if (consecutive_mids > 10 && current_adjustment != 0)
                        current_adjustment = 0;
                }
            }
            else
            {
                consecutive_lows = 0;
                consecutive_mids = 0;
                consecutive_highs = 0;
                current_adjustment = 0;
            }

            result = IDirectSoundBuffer_Lock(dsb,
                                             voice_pos, stream_cache_len,
                                             &buffer1, &length1,
                                             &buffer2, &length2,
                                             0);
            if (result == DS_OK)
            {
                voice_pos = (voice_pos + stream_cache_len) % buffer_length;

                cur_bytes = (stream_cache_len > length1) ? length1 : stream_cache_len;
                memcpy(buffer1, stream_data, cur_bytes);

                stream_cache_len -= cur_bytes;
                stream_data = (INT16 *)((INT8 *)stream_data + cur_bytes);

                if (stream_cache_len)
                {
                    cur_bytes = (stream_cache_len > length2) ? length2 : stream_cache_len;
                    memcpy(buffer2, stream_data, cur_bytes);
                }

                IDirectSoundBuffer_Unlock(dsb, buffer1, length1, buffer2, length2);
            }
        }

        samples_left_over += samples_per_frame;
        samples_this_frame = (UINT32)samples_left_over;
        samples_left_over -= (double)samples_this_frame;

        samples_this_frame += current_adjustment;
    }

    return samples_this_frame;
}


void osd_set_mastervolume(int _attenuation)
{
    if (_attenuation > 0)
        _attenuation = 0;
    if (_attenuation < -32)
        _attenuation = -32;

    attenuation = _attenuation;

    if (dsb)
        IDirectSoundBuffer_SetVolume(dsb, attenuation * 100);
}


int osd_get_mastervolume(void)
{
    return attenuation;
}


void osd_sound_enable(int enable_it)
{
    if (dsb)
    {
        if (enable_it)
        {
            IDirectSoundBuffer_SetVolume(dsb, attenuation * 100);
            first_time = 1;
            stream_cache_data = NULL;
        }
        else
            IDirectSoundBuffer_SetVolume(dsb, DSBVOLUME_MIN);
    }
}

/*********************************************************************/

void osd_start_log_wave(void)
{
    char fname[_MAX_PATH];
    int wavno = 0;

    strcpy(fname, Machine->gamedrv->name);
    if (osd_faccess(fname, OSD_FILETYPE_WAVE))
    {
        do
        {
            sprintf(fname, "%s_%02d", Machine->gamedrv->name, wavno++);
        } while (osd_faccess(fname, OSD_FILETYPE_WAVE));
    }

    wavfile = osd_fopen(NULL, fname, OSD_FILETYPE_WAVE, 1);
    if (wavfile == NULL)
        return;

    wh.ckid                 = FOURCC_RIFF;
    wh.cksize               = 0;
    wh.fccType              = MAKEFOURCC('W', 'A', 'V', 'E');
    wh.fmt                  = MAKEFOURCC('f', 'm', 't', ' ');
    wh.fmtsize              = 16;
    wh.wfmt.wFormatTag      = WAVE_FORMAT_PCM;
    wh.wfmt.nChannels       = stereo_factor;
    wh.wfmt.nSamplesPerSec  = Machine->sample_rate;
    wh.wfmt.nAvgBytesPerSec = wh.wfmt.nSamplesPerSec * wh.wfmt.nChannels * sizeof(INT16);
    wh.wfmt.nBlockAlign     = wh.wfmt.nChannels * sizeof(INT16);
    wh.nBitRate             = 16;
    wh.data                 = MAKEFOURCC('d', 'a', 't', 'a');
    wh.datasize             = 0;

    osd_fwrite(wavfile, &wh, sizeof(struct RiffWaveHeader));
}

void osd_stop_log_wave(void)
{
    int len;

    if (wavfile == NULL)
        return;

    len = osd_ftell(wavfile);

    wh.cksize   = len - 8;
    wh.datasize = len - sizeof(struct RiffWaveHeader);

    osd_fseek(wavfile, 0, SEEK_SET);
    osd_fwrite(wavfile, &wh, sizeof(struct RiffWaveHeader));

    osd_fclose(wavfile);

    wavfile = NULL;
}

void osd_log_wave(void)
{
    if (wavfile && stream_cache_data)
        osd_fwrite(wavfile, stream_cache_data, stream_cache_len);
}
