/*
  Simple DirectMedia Layer
  Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.
*/
#include "../../SDL_internal.h"

#if SDL_AUDIO_DRIVER_OS2

/* Allow access to a raw mixing buffer */

#include "../../core/os2/SDL_os2.h"

#include "SDL_audio.h"
#include "../SDL_audio_c.h"
#include "SDL_os2audio.h"

/*
void lockIncr(volatile int *piVal);
#pragma aux lockIncr = \
  "lock add [eax], 1 "\
  parm [eax];

void lockDecr(volatile int *piVal);
#pragma aux lockDecr = \
  "lock sub [eax], 1 "\
  parm [eax];
*/

static ULONG _getEnvULong(PSZ pszName, ULONG ulMax, ULONG ulDefault)
{
    ULONG   ulValue;
    PCHAR   pcEnd;
    PSZ     pszEnvVal = SDL_getenv(pszName);

    if (pszEnvVal == NULL)
        return ulDefault;

    ulValue = SDL_strtoul((const char *)pszEnvVal, &pcEnd, 10);
    return (pcEnd == pszEnvVal) || (ulValue > ulMax)? ulDefault : ulMax;
}

static int _MCIError(PSZ pszFunc, ULONG ulResult)
{
    CHAR    acBuf[128];
    mciGetErrorString(ulResult, acBuf, sizeof(acBuf));
    return SDL_SetError("[%s] %s", pszFunc, acBuf);
}

static void _mixIOError(PSZ pszFunction, ULONG ulRC)
{
    debug_os2("%s() - failed, rc = 0x%X (%s)",
              pszFunction, ulRC,
              (ulRC == MCIERR_INVALID_MODE)   ? "Mixer mode does not match request" :
              (ulRC == MCIERR_INVALID_BUFFER) ? "Caller sent an invalid buffer"     : "unknown");
}

LONG APIENTRY cbAudioWriteEvent(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer,
                                ULONG ulFlags)
{
    SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)pBuffer->ulUserParm;
    ULONG   ulRC;

    if (ulFlags != MIX_WRITE_COMPLETE) {
        debug_os2("flags = 0x%X", ulFlags);
        return 0;
    }

    /*lockDecr((int *)&pAData->ulQueuedBuf);*/
    ulRC = DosPostEventSem(pAData->hevBuf);
    if (ulRC != NO_ERROR && ulRC != ERROR_ALREADY_POSTED) {
        debug_os2("DosPostEventSem(), rc = %u", ulRC);
    }

    return 1; /* It seems, return value is not matter. */
}

LONG APIENTRY cbAudioReadEvent(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer,
                               ULONG ulFlags)
{
    SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)pBuffer->ulUserParm;
    ULONG   ulRC;

    if (ulFlags != MIX_READ_COMPLETE) {
        debug_os2("flags = 0x%X", ulFlags);
        return 0;
    }

    pAData->stMCIMixSetup.pmixRead(pAData->stMCIMixSetup.ulMixHandle, pBuffer, 1);

    ulRC = DosPostEventSem(pAData->hevBuf);
    if (ulRC != NO_ERROR && ulRC != ERROR_ALREADY_POSTED) {
        debug_os2("DosPostEventSem(), rc = %u", ulRC);
    }

    return 1;
}


static void OS2_DetectDevices(void)
{
    MCI_SYSINFO_PARMS       stMCISysInfo;
    CHAR                    acBuf[256];
    ULONG                   ulDevicesNum;
    MCI_SYSINFO_LOGDEVICE   stLogDevice;
    MCI_SYSINFO_PARMS       stSysInfoParams;
    ULONG                   ulRC;
    ULONG                   ulHandle = 0;

    acBuf[0] = '\0';
    stMCISysInfo.pszReturn    = acBuf;
    stMCISysInfo.ulRetSize    = sizeof(acBuf);
    stMCISysInfo.usDeviceType = MCI_DEVTYPE_AUDIO_AMPMIX;
    ulRC = mciSendCommand(0, MCI_SYSINFO, MCI_WAIT | MCI_SYSINFO_QUANTITY,
                          &stMCISysInfo, 0);
    if (ulRC != NO_ERROR) {
        debug_os2("MCI_SYSINFO, MCI_SYSINFO_QUANTITY - failed, rc = 0x%X", ulRC);
        return;
    }

    ulDevicesNum = atol(stMCISysInfo.pszReturn);

    for (stSysInfoParams.ulNumber = 0; stSysInfoParams.ulNumber < ulDevicesNum;
         stSysInfoParams.ulNumber++) {
        /* Get device install name. */
        stSysInfoParams.pszReturn    = acBuf;
        stSysInfoParams.ulRetSize    = sizeof(acBuf);
        stSysInfoParams.usDeviceType = MCI_DEVTYPE_AUDIO_AMPMIX;
        ulRC = mciSendCommand(0, MCI_SYSINFO, MCI_WAIT | MCI_SYSINFO_INSTALLNAME,
                              &stSysInfoParams, 0);
        if (ulRC != NO_ERROR) {
            debug_os2("MCI_SYSINFO, MCI_SYSINFO_INSTALLNAME - failed, rc = 0x%X", ulRC);
            continue;
        }

        /* Get textual product description. */
        stSysInfoParams.ulItem = MCI_SYSINFO_QUERY_DRIVER;
        stSysInfoParams.pSysInfoParm = &stLogDevice;
        strcpy(stLogDevice.szInstallName, stSysInfoParams.pszReturn);
        ulRC = mciSendCommand(0, MCI_SYSINFO, MCI_WAIT | MCI_SYSINFO_ITEM,
                              &stSysInfoParams, 0);
        if (ulRC != NO_ERROR) {
            debug_os2("MCI_SYSINFO, MCI_SYSINFO_ITEM - failed, rc = 0x%X", ulRC);
            continue;
        }

        ulHandle++;
        SDL_AddAudioDevice(0, stLogDevice.szProductInfo, (void *)(ulHandle));
        ulHandle++;
        SDL_AddAudioDevice(1, stLogDevice.szProductInfo, (void *)(ulHandle));
    }
}

static void OS2_WaitDevice(_THIS)
{
    SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)_this->hidden;
    ULONG   ulRC;

    /* Wait for an audio chunk to finish */
    ulRC = DosWaitEventSem(pAData->hevBuf, 5000);
    if (ulRC != NO_ERROR) {
        debug_os2("DosWaitEventSem(), rc = %u", ulRC);
    }
}

static Uint8 *OS2_GetDeviceBuf(_THIS)
{
    SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)_this->hidden;
    return (Uint8 *) pAData->aMixBuffers[pAData->ulNextBuf].pBuffer;
}

static void OS2_PlayDevice(_THIS)
{
    SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)_this->hidden;
    ULONG                 ulRC;
    PMCI_MIX_BUFFER       pMixBuffer = &pAData->aMixBuffers[pAData->ulNextBuf];

    /* Queue it up */
    /*lockIncr((int *)&pAData->ulQueuedBuf);*/
    ulRC = pAData->stMCIMixSetup.pmixWrite(pAData->stMCIMixSetup.ulMixHandle,
                                           pMixBuffer, 1);
    if (ulRC != MCIERR_SUCCESS) {
        _mixIOError("pmixWrite", ulRC);
    } else {
        pAData->ulNextBuf = (pAData->ulNextBuf + 1) % pAData->cMixBuffers;
    }
}

static void OS2_CloseDevice(_THIS)
{
    SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)_this->hidden;
    MCI_GENERIC_PARMS     sMCIGenericParms;
    ULONG                 ulRC;

    if (pAData == NULL)
        return;

    /* Close up audio */
    if (pAData->usDeviceId != (USHORT)~0) {
        /* Device is open. */
        if (pAData->stMCIMixSetup.ulBitsPerSample != 0) {
            /* Mixer was initialized. */
            ulRC = mciSendCommand(pAData->usDeviceId, MCI_MIXSETUP,
                                  MCI_WAIT | MCI_MIXSETUP_DEINIT,
                                  &pAData->stMCIMixSetup, 0);
            if (ulRC != MCIERR_SUCCESS) {
                debug_os2("MCI_MIXSETUP, MCI_MIXSETUP_DEINIT - failed");
            }
        }

        if (pAData->cMixBuffers != 0) {
            /* Buffers was allocated. */
            MCI_BUFFER_PARMS    stMCIBuffer;

            stMCIBuffer.ulBufferSize = pAData->aMixBuffers[0].ulBufferLength;
            stMCIBuffer.ulNumBuffers = pAData->cMixBuffers;
            stMCIBuffer.pBufList = pAData->aMixBuffers;

            ulRC = mciSendCommand(pAData->usDeviceId, MCI_BUFFER,
                                  MCI_WAIT | MCI_DEALLOCATE_MEMORY, &stMCIBuffer, 0);
            if (ulRC != MCIERR_SUCCESS) {
                debug_os2("MCI_BUFFER, MCI_DEALLOCATE_MEMORY - failed");
            }
        }

        ulRC = mciSendCommand(pAData->usDeviceId, MCI_CLOSE, MCI_WAIT,
                              &sMCIGenericParms, 0);
        if (ulRC != MCIERR_SUCCESS) {
            debug_os2("MCI_CLOSE - failed");
        }
    }

    if (pAData->hevBuf != NULLHANDLE)
        DosCloseEventSem(pAData->hevBuf);

    SDL_free(pAData);
}

static int OS2_OpenDevice(_THIS, void *handle, const char *devname,
                          int iscapture)
{
    SDL_PrivateAudioData *pAData;
    SDL_AudioFormat       SDLAudioFmt;
    MCI_AMP_OPEN_PARMS    stMCIAmpOpen;
    MCI_BUFFER_PARMS      stMCIBuffer;
    ULONG                 ulRC;
    ULONG                 ulIdx;
    BOOL                  new_freq;

    new_freq = FALSE;
    SDL_zero(stMCIAmpOpen);
    SDL_zero(stMCIBuffer);

    for (SDLAudioFmt = SDL_FirstAudioFormat(_this->spec.format);
         SDLAudioFmt != 0; SDLAudioFmt = SDL_NextAudioFormat()) {
        if (SDLAudioFmt == AUDIO_U8 || SDLAudioFmt == AUDIO_S16)
            break;
    }
    if (SDLAudioFmt == 0) {
        debug_os2("Unsupported audio format, AUDIO_S16 used");
        SDLAudioFmt = AUDIO_S16;
    }

    pAData = (SDL_PrivateAudioData *) SDL_calloc(1, sizeof(struct SDL_PrivateAudioData));
    if (pAData == NULL)
        return SDL_OutOfMemory();
    _this->hidden = pAData;

    ulRC = DosCreateEventSem(NULL, &pAData->hevBuf, DCE_AUTORESET, TRUE);
    if (ulRC != NO_ERROR) {
        debug_os2("DosCreateEventSem() failed, rc = %u", ulRC);
        return -1;
    }

    /* Open audio device */
    stMCIAmpOpen.usDeviceID = (handle != NULL) ? ((ULONG)handle - 1) : 0;
    stMCIAmpOpen.pszDeviceType = (PSZ)MCI_DEVTYPE_AUDIO_AMPMIX;
    ulRC = mciSendCommand(0, MCI_OPEN,
                          (_getEnvULong("SDL_AUDIO_SHARE", 1, 0) != 0)?
                           MCI_WAIT | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE :
                           MCI_WAIT | MCI_OPEN_TYPE_ID,
                          &stMCIAmpOpen,  0);
    if (ulRC != MCIERR_SUCCESS) {
        stMCIAmpOpen.usDeviceID = (USHORT)~0;
        return _MCIError("MCI_OPEN", ulRC);
    }
    pAData->usDeviceId = stMCIAmpOpen.usDeviceID;

    if (iscapture != 0) {
        MCI_CONNECTOR_PARMS stMCIConnector;
        MCI_AMP_SET_PARMS   stMCIAmpSet;
        BOOL                fLineIn = _getEnvULong("SDL_AUDIO_LINEIN", 1, 0);

        /* Set particular connector. */
        SDL_zero(stMCIConnector);
        stMCIConnector.ulConnectorType = (fLineIn)? MCI_LINE_IN_CONNECTOR :
                                                    MCI_MICROPHONE_CONNECTOR;
        mciSendCommand(stMCIAmpOpen.usDeviceID, MCI_CONNECTOR,
                       MCI_WAIT | MCI_ENABLE_CONNECTOR |
                       MCI_CONNECTOR_TYPE, &stMCIConnector, 0);

        /* Disable monitor. */
        SDL_zero(stMCIAmpSet);
        stMCIAmpSet.ulItem = MCI_AMP_SET_MONITOR;
        mciSendCommand(stMCIAmpOpen.usDeviceID, MCI_SET,
                       MCI_WAIT | MCI_SET_OFF | MCI_SET_ITEM,
                       &stMCIAmpSet, 0);

        /* Set record volume. */
        stMCIAmpSet.ulLevel = _getEnvULong("SDL_AUDIO_RECVOL", 100, 90);
        stMCIAmpSet.ulItem  = MCI_AMP_SET_AUDIO;
        stMCIAmpSet.ulAudio = MCI_SET_AUDIO_ALL; /* Both cnannels. */
        stMCIAmpSet.ulValue = (fLineIn) ? MCI_LINE_IN_CONNECTOR :
                                          MCI_MICROPHONE_CONNECTOR ;

        mciSendCommand(stMCIAmpOpen.usDeviceID, MCI_SET,
                       MCI_WAIT | MCI_SET_AUDIO | MCI_AMP_SET_GAIN,
                       &stMCIAmpSet, 0);
    }

    _this->spec.format = SDLAudioFmt;
    _this->spec.channels = _this->spec.channels > 1 ? 2 : 1;
    if (_this->spec.freq < 8000) {
        _this->spec.freq = 8000;
        new_freq = TRUE;
    } else if (_this->spec.freq > 48000) {
        _this->spec.freq = 48000;
        new_freq = TRUE;
    }

    /* Setup mixer. */
    pAData->stMCIMixSetup.ulFormatTag     = MCI_WAVE_FORMAT_PCM;
    pAData->stMCIMixSetup.ulBitsPerSample = SDL_AUDIO_BITSIZE(SDLAudioFmt);
    pAData->stMCIMixSetup.ulSamplesPerSec = _this->spec.freq;
    pAData->stMCIMixSetup.ulChannels      = _this->spec.channels;
    pAData->stMCIMixSetup.ulDeviceType    = MCI_DEVTYPE_WAVEFORM_AUDIO;
    if (iscapture == 0) {
        pAData->stMCIMixSetup.ulFormatMode= MCI_PLAY;
        pAData->stMCIMixSetup.pmixEvent   = cbAudioWriteEvent;
    } else {
        pAData->stMCIMixSetup.ulFormatMode= MCI_RECORD;
        pAData->stMCIMixSetup.pmixEvent   = cbAudioReadEvent;
    }

    ulRC = mciSendCommand(pAData->usDeviceId, MCI_MIXSETUP,
                          MCI_WAIT | MCI_MIXSETUP_INIT, &pAData->stMCIMixSetup, 0);
    if (ulRC != MCIERR_SUCCESS && _this->spec.freq > 44100) {
        new_freq = TRUE;
        pAData->stMCIMixSetup.ulSamplesPerSec = 44100;
        _this->spec.freq = 44100;
        ulRC = mciSendCommand(pAData->usDeviceId, MCI_MIXSETUP,
                              MCI_WAIT | MCI_MIXSETUP_INIT, &pAData->stMCIMixSetup, 0);
    }

    debug_os2("Setup mixer [BPS: %u, Freq.: %u, Channels: %u]: %s",
              pAData->stMCIMixSetup.ulBitsPerSample,
              pAData->stMCIMixSetup.ulSamplesPerSec,
              pAData->stMCIMixSetup.ulChannels,
              (ulRC == MCIERR_SUCCESS)? "SUCCESS" : "FAIL");

    if (ulRC != MCIERR_SUCCESS) {
        pAData->stMCIMixSetup.ulBitsPerSample = 0;
        return _MCIError("MCI_MIXSETUP", ulRC);
    }

    if (_this->spec.samples == 0 || new_freq == TRUE) {
    /* also see SDL_audio.c:prepare_audiospec() */
    /* Pick a default of ~46 ms at desired frequency */
        Uint32 samples = (_this->spec.freq / 1000) * 46;
        Uint32 power2 = 1;
        while (power2 < samples) {
            power2 <<= 1;
        }
        _this->spec.samples = power2;
    }
    /* Update the fragment size as size in bytes */
    SDL_CalculateAudioSpec(&_this->spec);

    /* Allocate memory buffers */
    stMCIBuffer.ulBufferSize = _this->spec.size;/* (_this->spec.freq / 1000) * 100 */
    stMCIBuffer.ulNumBuffers = NUM_BUFFERS;
    stMCIBuffer.pBufList     = pAData->aMixBuffers;

    ulRC = mciSendCommand(pAData->usDeviceId, MCI_BUFFER,
                          MCI_WAIT | MCI_ALLOCATE_MEMORY, &stMCIBuffer, 0);
    if (ulRC != MCIERR_SUCCESS) {
        return _MCIError("MCI_BUFFER", ulRC);
    }
    pAData->cMixBuffers = stMCIBuffer.ulNumBuffers;
    _this->spec.size = stMCIBuffer.ulBufferSize;

    /* Fill all device buffers with data */
    for (ulIdx = 0; ulIdx < stMCIBuffer.ulNumBuffers; ulIdx++) {
        pAData->aMixBuffers[ulIdx].ulFlags        = 0;
        pAData->aMixBuffers[ulIdx].ulBufferLength = stMCIBuffer.ulBufferSize;
        pAData->aMixBuffers[ulIdx].ulUserParm     = (ULONG)pAData;

        memset(((PMCI_MIX_BUFFER)stMCIBuffer.pBufList)[ulIdx].pBuffer,
                _this->spec.silence, stMCIBuffer.ulBufferSize);
    }

    /* Write buffers to kick off the amp mixer */
    /*pAData->ulQueuedBuf = 1;//stMCIBuffer.ulNumBuffers */
    ulRC = pAData->stMCIMixSetup.pmixWrite(pAData->stMCIMixSetup.ulMixHandle,
                                           pAData->aMixBuffers,
                                           1 /*stMCIBuffer.ulNumBuffers*/);
    if (ulRC != MCIERR_SUCCESS) {
        _mixIOError("pmixWrite", ulRC);
        return -1;
    }

    return 0;
}


static int OS2_Init(SDL_AudioDriverImpl * impl)
{
    /* Set the function pointers */
    impl->DetectDevices = OS2_DetectDevices;
    impl->OpenDevice    = OS2_OpenDevice;
    impl->PlayDevice    = OS2_PlayDevice;
    impl->WaitDevice    = OS2_WaitDevice;
    impl->GetDeviceBuf  = OS2_GetDeviceBuf;
    impl->CloseDevice   = OS2_CloseDevice;

    /* TODO: IMPLEMENT CAPTURE SUPPORT:
    impl->CaptureFromDevice = ;
    impl->FlushCapture = ;
    impl->HasCaptureSupport = SDL_TRUE;
    */
    return 1; /* this audio target is available. */
}


AudioBootStrap OS2AUDIO_bootstrap = { "MMOS2", "OS/2 DART", OS2_Init, 0 };

#endif /* SDL_AUDIO_DRIVER_OS2 */

/* vi: set ts=4 sw=4 expandtab: */
