/*
 * MPEG Layer 3 handling
 *
 * Copyright (C) 2002 Eric Pouech
 * Copyright (C) 2009 CodeWeavers, Aric Stewart
 * Copyright (C) 2010 Kristofer Henriksson
 *
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"
#include "wine/port.h"

#include <assert.h>
#include <stdarg.h>
#include <string.h>

#ifdef HAVE_MPG123_H
# include <mpg123.h>
#else
# ifdef HAVE_COREAUDIO_COREAUDIO_H
#  include <CoreFoundation/CoreFoundation.h>
#  include <CoreAudio/CoreAudio.h>
# endif
# ifdef HAVE_AUDIOTOOLBOX_AUDIOCONVERTER_H
#  include <AudioToolbox/AudioConverter.h>
# endif
#endif

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "mmsystem.h"
#include "mmreg.h"
#include "msacm.h"
#include "msacmdrv.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(mpeg3);

/* table to list all supported formats... those are the basic ones. this
 * also helps given a unique index to each of the supported formats
 */
typedef	struct
{
    int		nChannels;
    int		nBits;
    int		rate;
} Format;

static const Format PCM_Formats[] =
{
    {1,  8,  8000}, {2,  8,  8000}, {1, 16,  8000}, {2, 16,  8000},
    {1,  8, 11025}, {2,  8, 11025}, {1, 16, 11025}, {2, 16, 11025},
    {1,  8, 12000}, {2,  8, 12000}, {1, 16, 12000}, {2, 16, 12000},
    {1,  8, 16000}, {2,  8, 16000}, {1, 16, 16000}, {2, 16, 16000},
    {1,  8, 22050}, {2,  8, 22050}, {1, 16, 22050}, {2, 16, 22050},
    {1,  8, 24000}, {2,  8, 24000}, {1, 16, 24000}, {2, 16, 24000},
    {1,  8, 32000}, {2,  8, 32000}, {1, 16, 32000}, {2, 16, 32000},
    {1,  8, 44100}, {2,  8, 44100}, {1, 16, 44100}, {2, 16, 44100},
    {1,  8, 48000}, {2,  8, 48000}, {1, 16, 48000}, {2, 16, 48000}
};

static const Format MPEG3_Formats[] =
{
    {1,  0,  8000}, {2,  0,  8000},
    {1,  0, 11025}, {2,  0, 11025},
    {1,  0, 12000}, {2,  0, 12000},
    {1,  0, 16000}, {2,  0, 16000},
    {1,  0, 22050}, {2,  0, 22050},
    {1,  0, 24000}, {2,  0, 24000},
    {1,  0, 32000}, {2,  0, 32000},
    {1,  0, 44100}, {2,  0, 44100},
    {1,  0, 48000}, {2,  0, 48000}
};

/***********************************************************************
 *           MPEG3_GetFormatIndex
 */
static	DWORD	MPEG3_GetFormatIndex(LPWAVEFORMATEX wfx)
{
    int 	i, hi;
    const Format *fmts;

    switch (wfx->wFormatTag)
    {
    case WAVE_FORMAT_PCM:
	hi = ARRAY_SIZE(PCM_Formats);
	fmts = PCM_Formats;
	break;
    case WAVE_FORMAT_MPEG:
    case WAVE_FORMAT_MPEGLAYER3:
	hi = ARRAY_SIZE(MPEG3_Formats);
	fmts = MPEG3_Formats;
	break;
    default:
	return 0xFFFFFFFF;
    }

    for (i = 0; i < hi; i++)
    {
	if (wfx->nChannels == fmts[i].nChannels &&
	    wfx->nSamplesPerSec == fmts[i].rate &&
	    (wfx->wBitsPerSample == fmts[i].nBits || !fmts[i].nBits))
	    return i;
    }

    return 0xFFFFFFFF;
}

#ifdef HAVE_MPG123_H

typedef struct tagAcmMpeg3Data
{
    void (*convert)(PACMDRVSTREAMINSTANCE adsi,
		    const unsigned char*, LPDWORD, unsigned char*, LPDWORD);
    mpg123_handle *mh;
} AcmMpeg3Data;

/***********************************************************************
 *           MPEG3_drvOpen
 */
static LRESULT MPEG3_drvOpen(LPCSTR str)
{
    mpg123_init();
    return 1;
}

/***********************************************************************
 *           MPEG3_drvClose
 */
static LRESULT MPEG3_drvClose(DWORD_PTR dwDevID)
{
    mpg123_exit();
    return 1;
}


static void mp3_horse(PACMDRVSTREAMINSTANCE adsi,
                      const unsigned char* src, LPDWORD nsrc,
                      unsigned char* dst, LPDWORD ndst)
{
    AcmMpeg3Data*       amd = (AcmMpeg3Data*)adsi->dwDriver;
    int                 ret;
    size_t              size;
    DWORD               dpos = 0;


    if (*nsrc > 0)
    {
        ret = mpg123_feed(amd->mh, src, *nsrc);
        if (ret != MPG123_OK)
        {
            ERR("Error feeding data\n");
            *ndst = *nsrc = 0;
            return;
        }
    }

    do {
        size = 0;
        ret = mpg123_read(amd->mh, dst + dpos, *ndst - dpos, &size);
        if (ret == MPG123_ERR)
        {
            FIXME("Error occurred during decoding!\n");
            *ndst = *nsrc = 0;
            return;
        }

        if (ret == MPG123_NEW_FORMAT)
        {
            long rate;
            int channels, enc;
            mpg123_getformat(amd->mh, &rate, &channels, &enc);
            TRACE("New format: %li Hz, %i channels, encoding value %i\n", rate, channels, enc);
        }
        dpos += size;
        if (dpos >= *ndst) break;
    } while (ret != MPG123_ERR && ret != MPG123_NEED_MORE);
    *ndst = dpos;
}

/***********************************************************************
 *           MPEG3_Reset
 *
 */
static void MPEG3_Reset(PACMDRVSTREAMINSTANCE adsi, AcmMpeg3Data* aad)
{
    mpg123_feedseek(aad->mh, 0, SEEK_SET, NULL);
    mpg123_close(aad->mh);
    mpg123_open_feed(aad->mh);
}

/***********************************************************************
 *           MPEG3_StreamOpen
 *
 */
static	LRESULT	MPEG3_StreamOpen(PACMDRVSTREAMINSTANCE adsi)
{
    LRESULT error = MMSYSERR_NOTSUPPORTED;
    AcmMpeg3Data*	aad;
    int err;

    assert(!(adsi->fdwOpen & ACM_STREAMOPENF_ASYNC));

    if (MPEG3_GetFormatIndex(adsi->pwfxSrc) == 0xFFFFFFFF ||
	MPEG3_GetFormatIndex(adsi->pwfxDst) == 0xFFFFFFFF)
	return ACMERR_NOTPOSSIBLE;

    aad = HeapAlloc(GetProcessHeap(), 0, sizeof(AcmMpeg3Data));
    if (aad == 0) return MMSYSERR_NOMEM;

    adsi->dwDriver = (DWORD_PTR)aad;

    if (adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_PCM &&
	adsi->pwfxDst->wFormatTag == WAVE_FORMAT_PCM)
    {
	goto theEnd;
    }
    else if ((adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_MPEGLAYER3 ||
              adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_MPEG) &&
             adsi->pwfxDst->wFormatTag == WAVE_FORMAT_PCM)
    {
        if (adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_MPEGLAYER3)
        {
            MPEGLAYER3WAVEFORMAT *formatmp3 = (MPEGLAYER3WAVEFORMAT *)adsi->pwfxSrc;

            if (adsi->pwfxSrc->cbSize < MPEGLAYER3_WFX_EXTRA_BYTES ||
                formatmp3->wID != MPEGLAYER3_ID_MPEG)
            {
                error = ACMERR_NOTPOSSIBLE;
                goto theEnd;
            }
        }

	/* resampling or mono <=> stereo not available
         * MPEG3 algo only define 16 bit per sample output
         */
	if (adsi->pwfxSrc->nSamplesPerSec != adsi->pwfxDst->nSamplesPerSec ||
	    adsi->pwfxSrc->nChannels != adsi->pwfxDst->nChannels ||
            adsi->pwfxDst->wBitsPerSample != 16)
	    goto theEnd;
        aad->convert = mp3_horse;
        aad->mh = mpg123_new(NULL,&err);
        mpg123_open_feed(aad->mh);

#if MPG123_API_VERSION >= 31 /* needed for MPG123_IGNORE_FRAMEINFO enum value */
        /* mpg123 may find a XING header in the mp3 and use that information
         * to ask for seeks in order to read specific frames in the file.
         * We cannot allow that since the caller application is feeding us.
         * This fixes problems for mp3 files encoded with LAME (bug 42361)
         */
        mpg123_param(aad->mh, MPG123_ADD_FLAGS, MPG123_IGNORE_INFOFRAME, 0);
#endif
    }
    else if (adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_PCM &&
             (adsi->pwfxDst->wFormatTag == WAVE_FORMAT_MPEGLAYER3 ||
              adsi->pwfxDst->wFormatTag == WAVE_FORMAT_MPEG))
    {
        WARN("Encoding to MPEG is not supported\n");
        goto theEnd;
    }
    else goto theEnd;
    MPEG3_Reset(adsi, aad);

    return MMSYSERR_NOERROR;

 theEnd:
    HeapFree(GetProcessHeap(), 0, aad);
    adsi->dwDriver = 0L;
    return error;
}

/***********************************************************************
 *           MPEG3_StreamClose
 *
 */
static	LRESULT	MPEG3_StreamClose(PACMDRVSTREAMINSTANCE adsi)
{
    mpg123_close(((AcmMpeg3Data*)adsi->dwDriver)->mh);
    mpg123_delete(((AcmMpeg3Data*)adsi->dwDriver)->mh);
    HeapFree(GetProcessHeap(), 0, (void*)adsi->dwDriver);
    return MMSYSERR_NOERROR;
}

#elif defined(HAVE_AUDIOTOOLBOX_AUDIOCONVERTER_H)

static const unsigned short Mp3BitRates[2][16] =
{
    {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0},
    {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}
};

static const unsigned short Mp3SampleRates[2][4] =
{
    {44100, 48000, 32000, 0},
    {22050, 24000, 16000, 0}
};

typedef struct tagAcmMpeg3Data
{
    LRESULT (*convert)(PACMDRVSTREAMINSTANCE adsi, unsigned char*,
                       LPDWORD, unsigned char*, LPDWORD);
    AudioConverterRef acr;
    AudioStreamBasicDescription in,out;

    AudioBufferList outBuffer;
    AudioBuffer inBuffer;

    SInt32 tagBytesLeft;

    UInt32 NumberPackets;
    AudioStreamPacketDescription *PacketDescriptions;
} AcmMpeg3Data;

/***********************************************************************
 *           MPEG3_drvOpen
 */
static LRESULT MPEG3_drvOpen(LPCSTR str)
{
    return 1;
}

/***********************************************************************
 *           MPEG3_drvClose
 */
static LRESULT MPEG3_drvClose(DWORD_PTR dwDevID)
{
    return 1;
}

/*
 When it asks for data, give it all we have. If we have no data, we assume
 we will in the future, so give it no packets and return an error, which
 signals that we will have more later.
 */
static OSStatus Mp3AudioConverterComplexInputDataProc(
   AudioConverterRef             inAudioConverter,
   UInt32                        *ioNumberDataPackets,
   AudioBufferList               *ioData,
   AudioStreamPacketDescription  **outDataPacketDescription,
   void                          *inUserData
)
{
    AcmMpeg3Data *amd = (AcmMpeg3Data*)inUserData;

    if (amd->inBuffer.mDataByteSize > 0)
    {
        *ioNumberDataPackets = amd->NumberPackets;
        ioData->mNumberBuffers = 1;
        ioData->mBuffers[0] = amd->inBuffer;
        amd->inBuffer.mDataByteSize = 0;
        if (outDataPacketDescription)
            *outDataPacketDescription = amd->PacketDescriptions;
        return noErr;
    }
    else
    {
        *ioNumberDataPackets = 0;
        return -74;
    }
}

/*
 Get the length of the current frame. We need to be at the start of a
 frame now. The buffer must have at least the four bytes for the header.
 */
static SInt32 Mp3GetPacketLength(const unsigned char* src)
{
    unsigned char mpegv;
    unsigned short brate, srate;
    unsigned int size;

    /*
     Check that our position looks like an MP3 header and see which type
     of MP3 file we have.
     */
    if (src[0] == 0xff && src[1] >> 1 == 0x7d) mpegv = 0; /* MPEG-1 File */
    else if (src[0] == 0xff && src[1] >> 1 == 0x79) mpegv = 1; /* MPEG-2 File */
    else return -1;

    /* Fill in bit rate and sample rate. */
    brate = Mp3BitRates[mpegv][(src[2] & 0xf0) >> 4];
    srate = Mp3SampleRates[mpegv][(src[2] & 0xc) >> 2];

    /* Certain values for bit rate and sample rate are invalid. */
    if (brate == 0 || srate == 0) return -1;

    /* Compute frame size, round down */
    size = 72 * (2 - mpegv) * brate * 1000 / srate;

    /* If there is padding, add one byte */
    if (src[2] & 0x2) return size + 1;
    else return size;
}

/*
 Apple's AudioFileStream does weird things so we deal with parsing the
 file ourselves. It was also designed for a different use case, so this
 is not unexpected. We expect to have MP3 data as input (i.e. we can only
 deal with MPEG-1 or MPEG-2 Layer III), which simplifies parsing a bit. We
 understand the ID3v2 header and skip over it. Whenever we have data we
 want to skip at the beginning of the input, we do this by setting *ndst=0
 and *nsrc to the length of the unwanted data and return no error.
 */
static LRESULT mp3_leopard_horse(PACMDRVSTREAMINSTANCE adsi,
                                 unsigned char* src, LPDWORD nsrc,
                                 unsigned char* dst, LPDWORD ndst)
{
    OSStatus err;
    UInt32 size, aspdi, synci, syncSkip;
    short framelen[4];
    const unsigned char* psrc;
    AcmMpeg3Data* amd = (AcmMpeg3Data*)adsi->dwDriver;

    TRACE("ndst %u %p  <-  %u %p\n", *ndst, dst, *nsrc, src);

    TRACE("First 16 bytes to input: %s\n", wine_dbgstr_an((const char *)src, 16));

    /* Parse ID3 tag */
    if (!memcmp(src, "ID3", 3) && amd->tagBytesLeft == -1)
    {
        amd->tagBytesLeft = (src[6] << 21) + (src[7] << 14) + (src[8] << 7) + src[9];
        if (src[5] & 0x10) amd->tagBytesLeft += 20; /* There is a footer */
        else amd->tagBytesLeft += 10;
    }

    /* Consume the tag */
    if (amd->tagBytesLeft >= (SInt32)*nsrc)
    {
        *ndst = 0;
        amd->tagBytesLeft -= *nsrc;

        TRACE("All %d bytes of source data is ID3 tag\n", *nsrc);
        return MMSYSERR_NOERROR;
    }
    else if (amd->tagBytesLeft > 0)
    {
        src += amd->tagBytesLeft;
        *nsrc -= amd->tagBytesLeft;
        TRACE("Skipping %ld for ID3 tag\n", amd->tagBytesLeft);
    }

    /*
     Sync to initial MP3 frame. The largest possible MP3 frame is 1440.
     Thus, in the first 1440 bytes we must find the beginning of 3 valid
     frames in a row unless we reach the end of the file first.
     */
    syncSkip = 0;
    for (psrc = src; psrc <= src + *nsrc - 4 && psrc < src + 1440; psrc++)
    {
        framelen[0] = 0;
        for (synci = 1;
             synci < 4 && psrc + framelen[synci-1] < src + *nsrc - 4;
             synci++)
        {
            framelen[synci] = Mp3GetPacketLength(psrc + framelen[synci-1]);
            if (framelen[synci] == -1)
            {
                synci = 0;
                break;
            }
            framelen[synci] += framelen[synci-1];
        }
        if (synci > 0) /* We synced successfully */
        {
            if (psrc - src > 0)
            {
                syncSkip = psrc - src;
                src += syncSkip;
                *nsrc -= syncSkip;
                TRACE("Skipping %ld for frame sync\n", syncSkip);
            }
            break;
        }
    }

    if (Mp3GetPacketLength(src) == -1)
    {
        *ndst = *nsrc = 0;
        ERR("Frame sync failed. Cannot play file.\n");
        return MMSYSERR_ERROR;
    }

    /*
     Fill in frame descriptions for all frames. We use an extra pointer
     to keep track of our position in the input.
     */

    amd->NumberPackets = 25; /* This is the initial array capacity */
    amd->PacketDescriptions = HeapAlloc(GetProcessHeap(), 0, amd->NumberPackets * sizeof(AudioStreamPacketDescription));
    if (amd->PacketDescriptions == 0) return MMSYSERR_NOMEM;

    for (aspdi = 0, psrc = src;
         psrc <= src + *nsrc - 4;
         psrc += amd->PacketDescriptions[aspdi].mDataByteSize, aspdi++)
    {
        /* Return an error if we can't read the frame header */
        if (Mp3GetPacketLength(psrc) == -1)
        {
            *ndst = *nsrc = 0;
            ERR("Invalid header at %p.\n", psrc);
            HeapFree(GetProcessHeap(), 0, amd->PacketDescriptions);
            return MMSYSERR_ERROR;
        }

        /* If we run out of space, double size and reallocate */
        if (aspdi >= amd->NumberPackets)
        {
            amd->NumberPackets *= 2;
            amd->PacketDescriptions = HeapReAlloc(GetProcessHeap(), 0, amd->PacketDescriptions, amd->NumberPackets * sizeof(AudioStreamPacketDescription));
            if (amd->PacketDescriptions == 0) return MMSYSERR_NOMEM;
        }

        /* Fill in packet data */
        amd->PacketDescriptions[aspdi].mStartOffset = psrc - src;
        amd->PacketDescriptions[aspdi].mVariableFramesInPacket = 0;
        amd->PacketDescriptions[aspdi].mDataByteSize = Mp3GetPacketLength(psrc);

        /* If this brings us past the end, the last one doesn't count */
        if (psrc + amd->PacketDescriptions[aspdi].mDataByteSize > src + *nsrc) break;
    }

    /* Fill in correct number of frames */
    amd->NumberPackets = aspdi;

    /* Adjust nsrc to only include full frames */
    *nsrc = psrc - src;

    amd->inBuffer.mDataByteSize = *nsrc;
    amd->inBuffer.mData = src;
    amd->inBuffer.mNumberChannels = amd->in.mChannelsPerFrame;

    amd->outBuffer.mNumberBuffers = 1;
    amd->outBuffer.mBuffers[0].mDataByteSize = *ndst;
    amd->outBuffer.mBuffers[0].mData = dst;
    amd->outBuffer.mBuffers[0].mNumberChannels = amd->out.mChannelsPerFrame;

    /* Convert the data */
    size = amd->outBuffer.mBuffers[0].mDataByteSize / amd->out.mBytesPerPacket;
    err = AudioConverterFillComplexBuffer(amd->acr, Mp3AudioConverterComplexInputDataProc, amd, &size, &amd->outBuffer, 0);

    HeapFree(GetProcessHeap(), 0, amd->PacketDescriptions);

    /* Add skipped bytes back into *nsrc */
    if (amd->tagBytesLeft > 0)
    {
        *nsrc += amd->tagBytesLeft;
        amd->tagBytesLeft = 0;
    }
    *nsrc += syncSkip;

    if (err != noErr && err != -74)
    {
        *ndst = *nsrc = 0;
        ERR("Feed Error: %ld\n", err);
        return MMSYSERR_ERROR;
    }

    *ndst = amd->outBuffer.mBuffers[0].mDataByteSize;

    TRACE("convert %d -> %d\n", *nsrc, *ndst);

    return MMSYSERR_NOERROR;
}

/***********************************************************************
 *           MPEG3_Reset
 *
 */
static void MPEG3_Reset(PACMDRVSTREAMINSTANCE adsi, AcmMpeg3Data* aad)
{
    AudioConverterReset(aad->acr);
}

/***********************************************************************
 *           MPEG3_StreamOpen
 *
 */
static LRESULT MPEG3_StreamOpen(PACMDRVSTREAMINSTANCE adsi)
{
    AcmMpeg3Data* aad;

    assert(!(adsi->fdwOpen & ACM_STREAMOPENF_ASYNC));

    if (MPEG3_GetFormatIndex(adsi->pwfxSrc) == 0xFFFFFFFF ||
        MPEG3_GetFormatIndex(adsi->pwfxDst) == 0xFFFFFFFF)
        return ACMERR_NOTPOSSIBLE;

    aad = HeapAlloc(GetProcessHeap(), 0, sizeof(AcmMpeg3Data));
    if (aad == 0) return MMSYSERR_NOMEM;

    adsi->dwDriver = (DWORD_PTR)aad;

    if ((adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_MPEGLAYER3 ||
         adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_MPEG) &&
        adsi->pwfxDst->wFormatTag == WAVE_FORMAT_PCM)
    {
        OSStatus err;

        aad->in.mSampleRate = adsi->pwfxSrc->nSamplesPerSec;
        aad->out.mSampleRate = adsi->pwfxDst->nSamplesPerSec;
        aad->in.mBitsPerChannel = adsi->pwfxSrc->wBitsPerSample;
        aad->out.mBitsPerChannel = adsi->pwfxDst->wBitsPerSample;
        aad->in.mFormatID = kAudioFormatMPEGLayer3;
        aad->out.mFormatID = kAudioFormatLinearPCM;
        aad->in.mChannelsPerFrame = adsi->pwfxSrc->nChannels;
        aad->out.mChannelsPerFrame = adsi->pwfxDst->nChannels;
        aad->in.mFormatFlags = 0;
        aad->out.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
        aad->in.mBytesPerFrame = 0;
        aad->out.mBytesPerFrame = (aad->out.mBitsPerChannel * aad->out.mChannelsPerFrame) / 8;
        aad->in.mBytesPerPacket =  0;
        aad->out.mBytesPerPacket = aad->out.mBytesPerFrame;
        aad->in.mFramesPerPacket = 0;
        aad->out.mFramesPerPacket = 1;
        aad->in.mReserved = aad->out.mReserved = 0;

        aad->tagBytesLeft = -1;

        aad->convert = mp3_leopard_horse;

        err = AudioConverterNew(&aad->in, &aad->out, &aad->acr);
        if (err != noErr)
        {
            ERR("Create failed: %ld\n", err);
        }
        else
        {
            MPEG3_Reset(adsi, aad);

            return MMSYSERR_NOERROR;
        }
    }

    HeapFree(GetProcessHeap(), 0, aad);
    adsi->dwDriver = 0;

    return MMSYSERR_NOTSUPPORTED;
}

/***********************************************************************
 *           MPEG3_StreamClose
 *
 */
static LRESULT MPEG3_StreamClose(PACMDRVSTREAMINSTANCE adsi)
{
    AcmMpeg3Data* amd = (AcmMpeg3Data*)adsi->dwDriver;

    AudioConverterDispose(amd->acr);

    HeapFree(GetProcessHeap(), 0, amd);
    adsi->dwDriver = 0;

    return MMSYSERR_NOERROR;
}

#endif

/***********************************************************************
 *           MPEG3_DriverDetails
 *
 */
static	LRESULT MPEG3_DriverDetails(PACMDRIVERDETAILSW add)
{
    add->fccType = ACMDRIVERDETAILS_FCCTYPE_AUDIOCODEC;
    add->fccComp = ACMDRIVERDETAILS_FCCCOMP_UNDEFINED;
    add->wMid = MM_FRAUNHOFER_IIS;
    add->wPid = MM_FHGIIS_MPEGLAYER3_DECODE;
    add->vdwACM = 0x01000000;
    add->vdwDriver = 0x01000000;
    add->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CODEC;
    add->cFormatTags = 3; /* PCM, MPEG3 */
    add->cFilterTags = 0;
    add->hicon = NULL;
    MultiByteToWideChar( CP_ACP, 0, "MPEG Layer-3 Codec", -1,
                         add->szShortName, ARRAY_SIZE( add->szShortName ));
    MultiByteToWideChar( CP_ACP, 0, "Wine MPEG3 decoder", -1,
                         add->szLongName, ARRAY_SIZE( add->szLongName ));
    MultiByteToWideChar( CP_ACP, 0, "Brought to you by the Wine team...", -1,
                         add->szCopyright, ARRAY_SIZE( add->szCopyright ));
    MultiByteToWideChar( CP_ACP, 0, "Refer to LICENSE file", -1,
                         add->szLicensing, ARRAY_SIZE( add->szLicensing ));
    add->szFeatures[0] = 0;

    return MMSYSERR_NOERROR;
}

/***********************************************************************
 *           MPEG3_FormatTagDetails
 *
 */
static	LRESULT	MPEG3_FormatTagDetails(PACMFORMATTAGDETAILSW aftd, DWORD dwQuery)
{
    static const WCHAR szPcm[]={'P','C','M',0};
    static const WCHAR szMpeg3[]={'M','P','e','g','3',0};
    static const WCHAR szMpeg[]={'M','P','e','g',0};

    switch (dwQuery)
    {
    case ACM_FORMATTAGDETAILSF_INDEX:
	if (aftd->dwFormatTagIndex > 2) return ACMERR_NOTPOSSIBLE;
	break;
    case ACM_FORMATTAGDETAILSF_LARGESTSIZE:
	if (aftd->dwFormatTag == WAVE_FORMAT_UNKNOWN)
        {
            aftd->dwFormatTagIndex = 2; /* WAVE_FORMAT_MPEG is biggest */
	    break;
	}
	/* fall through */
    case ACM_FORMATTAGDETAILSF_FORMATTAG:
	switch (aftd->dwFormatTag)
        {
	case WAVE_FORMAT_PCM:		aftd->dwFormatTagIndex = 0; break;
	case WAVE_FORMAT_MPEGLAYER3:    aftd->dwFormatTagIndex = 1; break;
	case WAVE_FORMAT_MPEG:          aftd->dwFormatTagIndex = 2; break;
	default:			return ACMERR_NOTPOSSIBLE;
	}
	break;
    default:
	WARN("Unsupported query %08x\n", dwQuery);
	return MMSYSERR_NOTSUPPORTED;
    }

    aftd->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CODEC;
    switch (aftd->dwFormatTagIndex)
    {
    case 0:
	aftd->dwFormatTag = WAVE_FORMAT_PCM;
	aftd->cbFormatSize = sizeof(PCMWAVEFORMAT);
	aftd->cStandardFormats = ARRAY_SIZE(PCM_Formats);
        lstrcpyW(aftd->szFormatTag, szPcm);
        break;
    case 1:
	aftd->dwFormatTag = WAVE_FORMAT_MPEGLAYER3;
	aftd->cbFormatSize = sizeof(MPEGLAYER3WAVEFORMAT);
        aftd->cStandardFormats = 0;
        lstrcpyW(aftd->szFormatTag, szMpeg3);
	break;
    case 2:
	aftd->dwFormatTag = WAVE_FORMAT_MPEG;
	aftd->cbFormatSize = sizeof(MPEG1WAVEFORMAT);
        aftd->cStandardFormats = 0;
        lstrcpyW(aftd->szFormatTag, szMpeg);
	break;
    }
    return MMSYSERR_NOERROR;
}

/***********************************************************************
 *           MPEG3_FormatDetails
 *
 */
static	LRESULT	MPEG3_FormatDetails(PACMFORMATDETAILSW afd, DWORD dwQuery)
{
    switch (dwQuery)
    {
    case ACM_FORMATDETAILSF_FORMAT:
	if (MPEG3_GetFormatIndex(afd->pwfx) == 0xFFFFFFFF) return ACMERR_NOTPOSSIBLE;
	break;
    case ACM_FORMATDETAILSF_INDEX:
	afd->pwfx->wFormatTag = afd->dwFormatTag;
	switch (afd->dwFormatTag)
        {
	case WAVE_FORMAT_PCM:
	    if (afd->dwFormatIndex >= ARRAY_SIZE(PCM_Formats)) return ACMERR_NOTPOSSIBLE;
	    afd->pwfx->nChannels = PCM_Formats[afd->dwFormatIndex].nChannels;
	    afd->pwfx->nSamplesPerSec = PCM_Formats[afd->dwFormatIndex].rate;
	    afd->pwfx->wBitsPerSample = PCM_Formats[afd->dwFormatIndex].nBits;
	    /* native MSACM uses a PCMWAVEFORMAT structure, so cbSize is not accessible
	     * afd->pwfx->cbSize = 0;
	     */
	    afd->pwfx->nBlockAlign =
		(afd->pwfx->nChannels * afd->pwfx->wBitsPerSample) / 8;
	    afd->pwfx->nAvgBytesPerSec =
		afd->pwfx->nSamplesPerSec * afd->pwfx->nBlockAlign;
	    break;
	case WAVE_FORMAT_MPEGLAYER3:
	case WAVE_FORMAT_MPEG:
            WARN("Encoding to MPEG is not supported\n");
            return ACMERR_NOTPOSSIBLE;
	default:
            WARN("Unsupported tag %08x\n", afd->dwFormatTag);
	    return MMSYSERR_INVALPARAM;
	}
	break;
    default:
	WARN("Unsupported query %08x\n", dwQuery);
	return MMSYSERR_NOTSUPPORTED;
    }
    afd->fdwSupport = ACMDRIVERDETAILS_SUPPORTF_CODEC;
    afd->szFormat[0] = 0; /* let MSACM format this for us... */

    return MMSYSERR_NOERROR;
}

/***********************************************************************
 *           MPEG3_FormatSuggest
 *
 */
static	LRESULT	MPEG3_FormatSuggest(PACMDRVFORMATSUGGEST adfs)
{
    /* some tests ... */
    if (adfs->cbwfxSrc < sizeof(PCMWAVEFORMAT) ||
	adfs->cbwfxDst < sizeof(PCMWAVEFORMAT) ||
	MPEG3_GetFormatIndex(adfs->pwfxSrc) == 0xFFFFFFFF) return ACMERR_NOTPOSSIBLE;
    /* FIXME: should do those tests against the real size (according to format tag */

    /* If no suggestion for destination, then copy source value */
    if (!(adfs->fdwSuggest & ACM_FORMATSUGGESTF_NCHANNELS))
	adfs->pwfxDst->nChannels = adfs->pwfxSrc->nChannels;
    if (!(adfs->fdwSuggest & ACM_FORMATSUGGESTF_NSAMPLESPERSEC))
        adfs->pwfxDst->nSamplesPerSec = adfs->pwfxSrc->nSamplesPerSec;
    if (!(adfs->fdwSuggest & ACM_FORMATSUGGESTF_WBITSPERSAMPLE))
        adfs->pwfxDst->wBitsPerSample = 16;
    if (!(adfs->fdwSuggest & ACM_FORMATSUGGESTF_WFORMATTAG))
    {
	if (adfs->pwfxSrc->wFormatTag == WAVE_FORMAT_PCM)
        {
            WARN("Encoding to MPEG is not supported\n");
            return ACMERR_NOTPOSSIBLE;
        }
        else
            adfs->pwfxDst->wFormatTag = WAVE_FORMAT_PCM;
    }

    /* check if result is ok */
    if (MPEG3_GetFormatIndex(adfs->pwfxDst) == 0xFFFFFFFF) return ACMERR_NOTPOSSIBLE;

    /* recompute other values */
    switch (adfs->pwfxDst->wFormatTag)
    {
    case WAVE_FORMAT_PCM:
        adfs->pwfxDst->nBlockAlign = (adfs->pwfxDst->nChannels * adfs->pwfxDst->wBitsPerSample) / 8;
        adfs->pwfxDst->nAvgBytesPerSec = adfs->pwfxDst->nSamplesPerSec * adfs->pwfxDst->nBlockAlign;
        break;
    case WAVE_FORMAT_MPEG:
    case WAVE_FORMAT_MPEGLAYER3:
        WARN("Encoding to MPEG is not supported\n");
        return ACMERR_NOTPOSSIBLE;
        break;
    default:
        FIXME("\n");
        break;
    }

    return MMSYSERR_NOERROR;
}

/***********************************************************************
 *           MPEG3_StreamSize
 *
 */
static	LRESULT MPEG3_StreamSize(PACMDRVSTREAMINSTANCE adsi, PACMDRVSTREAMSIZE adss)
{
    DWORD nblocks;

    switch (adss->fdwSize)
    {
    case ACM_STREAMSIZEF_DESTINATION:
	/* cbDstLength => cbSrcLength */
	if (adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_PCM &&
            (adsi->pwfxDst->wFormatTag == WAVE_FORMAT_MPEGLAYER3 ||
             adsi->pwfxDst->wFormatTag == WAVE_FORMAT_MPEG))
        {
            nblocks = (adss->cbDstLength - 3000) / (DWORD)(adsi->pwfxDst->nAvgBytesPerSec * 1152 / adsi->pwfxDst->nSamplesPerSec + 0.5);
            if (nblocks == 0)
                return ACMERR_NOTPOSSIBLE;
            adss->cbSrcLength = nblocks * 1152 * adsi->pwfxSrc->nBlockAlign;
	}
        else if ((adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_MPEGLAYER3 ||
                 adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_MPEG) &&
                 adsi->pwfxDst->wFormatTag == WAVE_FORMAT_PCM)
        {
            nblocks = adss->cbDstLength / (adsi->pwfxDst->nBlockAlign * 1152);
            if (nblocks == 0)
                return ACMERR_NOTPOSSIBLE;
            adss->cbSrcLength = nblocks * (DWORD)(adsi->pwfxSrc->nAvgBytesPerSec * 1152 / adsi->pwfxSrc->nSamplesPerSec);
	}
        else
        {
	    return MMSYSERR_NOTSUPPORTED;
	}
	break;
    case ACM_STREAMSIZEF_SOURCE:
	/* cbSrcLength => cbDstLength */
	if (adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_PCM &&
            (adsi->pwfxDst->wFormatTag == WAVE_FORMAT_MPEGLAYER3 ||
             adsi->pwfxDst->wFormatTag == WAVE_FORMAT_MPEG))
        {
            nblocks = adss->cbSrcLength / (adsi->pwfxSrc->nBlockAlign * 1152);
            if (adss->cbSrcLength % (DWORD)(adsi->pwfxSrc->nBlockAlign * 1152))
                /* Round block count up. */
                nblocks++;
            if (nblocks == 0)
                return ACMERR_NOTPOSSIBLE;
            adss->cbDstLength = 3000 + nblocks * (DWORD)(adsi->pwfxDst->nAvgBytesPerSec * 1152 / adsi->pwfxDst->nSamplesPerSec + 0.5);
	}
        else if ((adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_MPEGLAYER3 ||
                 adsi->pwfxSrc->wFormatTag == WAVE_FORMAT_MPEG) &&
                 adsi->pwfxDst->wFormatTag == WAVE_FORMAT_PCM)
        {
            nblocks = adss->cbSrcLength / (DWORD)(adsi->pwfxSrc->nAvgBytesPerSec * 1152 / adsi->pwfxSrc->nSamplesPerSec);
            if (adss->cbSrcLength % (DWORD)(adsi->pwfxSrc->nAvgBytesPerSec * 1152 / adsi->pwfxSrc->nSamplesPerSec))
                /* Round block count up. */
                nblocks++;
            if (nblocks == 0)
                return ACMERR_NOTPOSSIBLE;
            adss->cbDstLength = nblocks * 1152 * adsi->pwfxDst->nBlockAlign;
	}
        else
        {
	    return MMSYSERR_NOTSUPPORTED;
	}
	break;
    default:
	WARN("Unsupported query %08x\n", adss->fdwSize);
	return MMSYSERR_NOTSUPPORTED;
    }
    return MMSYSERR_NOERROR;
}

/***********************************************************************
 *           MPEG3_StreamConvert
 *
 */
static LRESULT MPEG3_StreamConvert(PACMDRVSTREAMINSTANCE adsi, PACMDRVSTREAMHEADER adsh)
{
    AcmMpeg3Data*	aad = (AcmMpeg3Data*)adsi->dwDriver;
    DWORD		nsrc = adsh->cbSrcLength;
    DWORD		ndst = adsh->cbDstLength;

    if (adsh->fdwConvert &
	~(ACM_STREAMCONVERTF_BLOCKALIGN|
	  ACM_STREAMCONVERTF_END|
	  ACM_STREAMCONVERTF_START))
    {
	FIXME("Unsupported fdwConvert (%08x), ignoring it\n", adsh->fdwConvert);
    }
    /* ACM_STREAMCONVERTF_BLOCKALIGN
     *	currently all conversions are block aligned, so do nothing for this flag
     * ACM_STREAMCONVERTF_END
     *	no pending data, so do nothing for this flag
     */
    if ((adsh->fdwConvert & ACM_STREAMCONVERTF_START))
    {
        MPEG3_Reset(adsi, aad);
    }

    aad->convert(adsi, adsh->pbSrc, &nsrc, adsh->pbDst, &ndst);
    adsh->cbSrcLengthUsed = nsrc;
    adsh->cbDstLengthUsed = ndst;

    return MMSYSERR_NOERROR;
}

/**************************************************************************
 * 			MPEG3_DriverProc			[exported]
 */
LRESULT CALLBACK MPEG3_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg,
					 LPARAM dwParam1, LPARAM dwParam2)
{
    TRACE("(%08lx %p %04x %08lx %08lx);\n",
	  dwDevID, hDriv, wMsg, dwParam1, dwParam2);

    switch (wMsg)
    {
    case DRV_LOAD:		return 1;
    case DRV_FREE:		return 1;
    case DRV_OPEN:		return MPEG3_drvOpen((LPSTR)dwParam1);
    case DRV_CLOSE:		return MPEG3_drvClose(dwDevID);
    case DRV_ENABLE:		return 1;
    case DRV_DISABLE:		return 1;
    case DRV_QUERYCONFIGURE:	return 1;
    case DRV_CONFIGURE:		MessageBoxA(0, "MPEG3 filter !", "Wine Driver", MB_OK); return 1;
    case DRV_INSTALL:		return DRVCNF_RESTART;
    case DRV_REMOVE:		return DRVCNF_RESTART;

    case ACMDM_DRIVER_NOTIFY:
	/* no caching from other ACM drivers is done so far */
	return MMSYSERR_NOERROR;

    case ACMDM_DRIVER_DETAILS:
	return MPEG3_DriverDetails((PACMDRIVERDETAILSW)dwParam1);

    case ACMDM_FORMATTAG_DETAILS:
	return MPEG3_FormatTagDetails((PACMFORMATTAGDETAILSW)dwParam1, dwParam2);

    case ACMDM_FORMAT_DETAILS:
	return MPEG3_FormatDetails((PACMFORMATDETAILSW)dwParam1, dwParam2);

    case ACMDM_FORMAT_SUGGEST:
	return MPEG3_FormatSuggest((PACMDRVFORMATSUGGEST)dwParam1);

    case ACMDM_STREAM_OPEN:
	return MPEG3_StreamOpen((PACMDRVSTREAMINSTANCE)dwParam1);

    case ACMDM_STREAM_CLOSE:
	return MPEG3_StreamClose((PACMDRVSTREAMINSTANCE)dwParam1);

    case ACMDM_STREAM_SIZE:
	return MPEG3_StreamSize((PACMDRVSTREAMINSTANCE)dwParam1, (PACMDRVSTREAMSIZE)dwParam2);

    case ACMDM_STREAM_CONVERT:
	return MPEG3_StreamConvert((PACMDRVSTREAMINSTANCE)dwParam1, (PACMDRVSTREAMHEADER)dwParam2);

    case ACMDM_HARDWARE_WAVE_CAPS_INPUT:
    case ACMDM_HARDWARE_WAVE_CAPS_OUTPUT:
	/* this converter is not a hardware driver */
    case ACMDM_FILTERTAG_DETAILS:
    case ACMDM_FILTER_DETAILS:
	/* this converter is not a filter */
    case ACMDM_STREAM_RESET:
	/* only needed for asynchronous driver... we aren't, so just say it */
	return MMSYSERR_NOTSUPPORTED;
    case ACMDM_STREAM_PREPARE:
    case ACMDM_STREAM_UNPREPARE:
	/* nothing special to do here... so don't do anything */
	return MMSYSERR_NOERROR;

    default:
	return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2);
    }
}
