/*              DirectSoundFullDuplex
 *
 * Copyright 1998 Marcus Meissner
 * Copyright 1998 Rob Riggs
 * Copyright 2000-2001 TransGaming Technologies, Inc.
 * Copyright 2005 Robert Reif
 *
 * 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 <stdarg.h>

#define COBJMACROS
#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "mmsystem.h"
#include "mmddk.h"
#include "wine/debug.h"
#include "dsound.h"
#include "dsound_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(dsound);

/*****************************************************************************
 * IDirectSoundFullDuplex implementation structure
 */
typedef struct IDirectSoundFullDuplexImpl
{
    IUnknown               IUnknown_iface;
    IDirectSoundFullDuplex IDirectSoundFullDuplex_iface;
    LONG                   ref, refdsfd, numIfaces;
    IUnknown              *ds8_unk;     /* Aggregated IDirectSound8 */
    IUnknown              *dsc8_unk;    /* Aggregated IDirectSoundCapture8 */
} IDirectSoundFullDuplexImpl;

static void fullduplex_destroy(IDirectSoundFullDuplexImpl *This)
{
    IDirectSound8 *ds8;
    IDirectSoundCapture8 *dsc8;

    if (This->ds8_unk) {
        IUnknown_QueryInterface(This->ds8_unk, &IID_IDirectSound8, (void**)&ds8);
        while(IDirectSound8_Release(ds8) > 0);
        IUnknown_Release(This->ds8_unk);
    }
    if (This->dsc8_unk) {
        IUnknown_QueryInterface(This->dsc8_unk, &IID_IDirectSoundCapture8, (void**)&dsc8);
        while(IDirectSoundCapture_Release(dsc8) > 0);
        IUnknown_Release(This->dsc8_unk);
    }
    TRACE("(%p) released\n", This);
    free(This);
}

/*******************************************************************************
 * IUnknown implementation for DirectSoundFullDuplex
 */
static inline IDirectSoundFullDuplexImpl *impl_from_IUnknown(IUnknown *iface)
{
    return CONTAINING_RECORD(iface, IDirectSoundFullDuplexImpl, IUnknown_iface);
}

static HRESULT WINAPI IUnknownImpl_QueryInterface(IUnknown *iface, REFIID riid, void **ppv)
{
    IDirectSoundFullDuplexImpl *This = impl_from_IUnknown(iface);

    TRACE("(%p,%s,%p)\n", This, debugstr_guid(riid), ppv);

    if (!ppv) {
        WARN("invalid parameter\n");
        return E_INVALIDARG;
    }

    if (IsEqualIID(riid, &IID_IUnknown)) {
        IUnknown_AddRef(&This->IUnknown_iface);
        *ppv = &This->IUnknown_iface;
        return S_OK;
    } else if (IsEqualIID(riid, &IID_IDirectSoundFullDuplex)) {
        IDirectSoundFullDuplex_AddRef(&This->IDirectSoundFullDuplex_iface);
        *ppv = &This->IDirectSoundFullDuplex_iface;
        return S_OK;
    } else if (This->ds8_unk && (IsEqualIID(riid, &IID_IDirectSound) ||
                                 IsEqualIID(riid, &IID_IDirectSound8)))
        return IUnknown_QueryInterface(This->ds8_unk, riid, ppv);
    else if (This->dsc8_unk && IsEqualIID(riid, &IID_IDirectSoundCapture))
        return IUnknown_QueryInterface(This->dsc8_unk, riid, ppv);

    *ppv = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI IUnknownImpl_AddRef(IUnknown *iface)
{
    IDirectSoundFullDuplexImpl *This = impl_from_IUnknown(iface);
    ULONG ref = InterlockedIncrement(&This->ref);

    TRACE("(%p) ref=%ld\n", This, ref);

    if(ref == 1)
        InterlockedIncrement(&This->numIfaces);
    return ref;
}

static ULONG WINAPI IUnknownImpl_Release(IUnknown *iface)
{
    IDirectSoundFullDuplexImpl *This = impl_from_IUnknown(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p) ref=%ld\n", This, ref);

    if (!ref && !InterlockedDecrement(&This->numIfaces))
        fullduplex_destroy(This);
    return ref;
}

static const IUnknownVtbl unk_vtbl =
{
    IUnknownImpl_QueryInterface,
    IUnknownImpl_AddRef,
    IUnknownImpl_Release
};

/***************************************************************************
 * IDirectSoundFullDuplex implementation
 */
static inline IDirectSoundFullDuplexImpl *impl_from_IDirectSoundFullDuplex(IDirectSoundFullDuplex *iface)
{
    return CONTAINING_RECORD(iface, IDirectSoundFullDuplexImpl, IDirectSoundFullDuplex_iface);
}

static HRESULT WINAPI IDirectSoundFullDuplexImpl_QueryInterface(IDirectSoundFullDuplex *iface,
        REFIID riid, void **ppv)
{
    IDirectSoundFullDuplexImpl *This = impl_from_IDirectSoundFullDuplex(iface);
    TRACE("(%p,%s,%p)\n", This, debugstr_guid(riid), ppv);
    return IUnknown_QueryInterface(&This->IUnknown_iface, riid, ppv);
}

static ULONG WINAPI IDirectSoundFullDuplexImpl_AddRef(IDirectSoundFullDuplex *iface)
{
    IDirectSoundFullDuplexImpl *This = impl_from_IDirectSoundFullDuplex(iface);
    ULONG ref = InterlockedIncrement(&This->refdsfd);

    TRACE("(%p) ref=%ld\n", This, ref);

    if(ref == 1)
        InterlockedIncrement(&This->numIfaces);
    return ref;
}

static ULONG WINAPI IDirectSoundFullDuplexImpl_Release(IDirectSoundFullDuplex *iface)
{
    IDirectSoundFullDuplexImpl *This = impl_from_IDirectSoundFullDuplex(iface);
    ULONG ref = InterlockedDecrement(&This->refdsfd);

    TRACE("(%p) ref=%ld\n", This, ref);

    if (!ref && !InterlockedDecrement(&This->numIfaces))
        fullduplex_destroy(This);
    return ref;
}

static HRESULT WINAPI IDirectSoundFullDuplexImpl_Initialize(IDirectSoundFullDuplex *iface,
        const GUID *capture_dev, const GUID *render_dev, const DSCBUFFERDESC *cbufdesc,
        const DSBUFFERDESC *bufdesc, HWND hwnd, DWORD level, IDirectSoundCaptureBuffer8 **dscb8,
        IDirectSoundBuffer8 **dsb8)
{
    IDirectSoundFullDuplexImpl *This = impl_from_IDirectSoundFullDuplex(iface);
    IDirectSound8 *ds8 = NULL;
    IDirectSoundCapture8 *dsc8 = NULL;
    HRESULT hr;

    TRACE("(%p,%s,%s,%p,%p,%p,%lx,%p,%p)\n", This, debugstr_guid(capture_dev),
            debugstr_guid(render_dev), cbufdesc, bufdesc, hwnd, level, dscb8, dsb8);

    if (!dscb8 || !dsb8)
        return E_INVALIDARG;

    *dscb8 = NULL;
    *dsb8 = NULL;

    if (This->ds8_unk || This->dsc8_unk) {
        WARN("already initialized\n");
        return DSERR_ALREADYINITIALIZED;
    }

    hr = IDirectSoundImpl_Create(&This->IUnknown_iface, &IID_IUnknown, (void**)&This->ds8_unk,
            TRUE);
    if (SUCCEEDED(hr)) {
        IUnknown_QueryInterface(This->ds8_unk, &IID_IDirectSound8, (void**)&ds8);
        hr = IDirectSound8_Initialize(ds8, render_dev);
    }
    if (hr != DS_OK) {
        WARN("Creating/initializing IDirectSound8 failed\n");
        goto error;
    }

    IDirectSound8_SetCooperativeLevel(ds8, hwnd, level);

    hr = IDirectSound8_CreateSoundBuffer(ds8, bufdesc, (IDirectSoundBuffer**)dsb8, NULL);
    if (hr != DS_OK) {
        WARN("IDirectSoundBuffer_Create() failed\n");
        goto error;
    }

    hr = IDirectSoundCaptureImpl_Create(&This->IUnknown_iface, &IID_IUnknown,
            (void**)&This->dsc8_unk, TRUE);
    if (SUCCEEDED(hr)) {
        IUnknown_QueryInterface(This->dsc8_unk, &IID_IDirectSoundCapture8, (void**)&dsc8);
        hr = IDirectSoundCapture_Initialize(dsc8, capture_dev);
    }
    if (hr != DS_OK) {
        WARN("Creating/initializing IDirectSoundCapture8 failed\n");
        goto error;
    }

    hr = IDirectSoundCapture_CreateCaptureBuffer(dsc8, cbufdesc,
            (IDirectSoundCaptureBuffer**)dscb8, NULL);
    if (hr != DS_OK) {
        WARN("IDirectSoundCapture_CreateCaptureBuffer() failed\n");
        goto error;
    }

    IDirectSound8_Release(ds8);
    IDirectSoundCapture_Release(dsc8);
    return DS_OK;

error:
    if (*dsb8) {
        IDirectSoundBuffer8_Release(*dsb8);
        *dsb8 = NULL;
    }
    if (ds8)
        IDirectSound8_Release(ds8);
    if (This->ds8_unk) {
        IUnknown_Release(This->ds8_unk);
        This->ds8_unk = NULL;
    }
    if (*dscb8) {
        IDirectSoundCaptureBuffer8_Release(*dscb8);
        *dscb8 = NULL;
    }
    if (dsc8)
        IDirectSoundCapture_Release(dsc8);
    if (This->dsc8_unk) {
        IUnknown_Release(This->dsc8_unk);
        This->dsc8_unk = NULL;
    }
    return hr;
}

static const IDirectSoundFullDuplexVtbl dsfd_vtbl =
{
    /* IUnknown methods */
    IDirectSoundFullDuplexImpl_QueryInterface,
    IDirectSoundFullDuplexImpl_AddRef,
    IDirectSoundFullDuplexImpl_Release,

    /* IDirectSoundFullDuplex methods */
    IDirectSoundFullDuplexImpl_Initialize
};

HRESULT DSOUND_FullDuplexCreate(REFIID riid, void **ppv)
{
    IDirectSoundFullDuplexImpl *obj;
    HRESULT hr;

    TRACE("(%s, %p)\n", debugstr_guid(riid), ppv);

    *ppv = NULL;
    obj = calloc(1, sizeof(*obj));
    if (!obj) {
        WARN("out of memory\n");
        return DSERR_OUTOFMEMORY;
    }

    setup_dsound_options();

    obj->IDirectSoundFullDuplex_iface.lpVtbl = &dsfd_vtbl;
    obj->IUnknown_iface.lpVtbl = &unk_vtbl;
    obj->ref = 1;
    obj->refdsfd = 0;
    obj->numIfaces = 1;

    hr = IUnknown_QueryInterface(&obj->IUnknown_iface, riid, ppv);
    IUnknown_Release(&obj->IUnknown_iface);

    return hr;
}

/***************************************************************************
 * DirectSoundFullDuplexCreate [DSOUND.10]
 *
 * Create and initialize a DirectSoundFullDuplex interface.
 *
 * PARAMS
 *    capture_dev         [I] Address of sound capture device GUID.
 *    render_dev          [I] Address of sound render device GUID.
 *    cbufdesc            [I] Address of capture buffer description.
 *    bufdesc             [I] Address of  render buffer description.
 *    hwnd                [I] Handle to application window.
 *    level               [I] Cooperative level.
 *    dsfd                [O] Address where full duplex interface returned.
 *    dscb8               [0] Address where capture buffer interface returned.
 *    dsb8                [0] Address where render buffer interface returned.
 *    outer_unk           [I] Must be NULL.
 *
 * RETURNS
 *    Success: DS_OK
 *    Failure: DSERR_NOAGGREGATION, DSERR_ALLOCATED, DSERR_INVALIDPARAM,
 *             DSERR_OUTOFMEMORY DSERR_INVALIDCALL DSERR_NODRIVER
 */
HRESULT WINAPI DirectSoundFullDuplexCreate(const GUID *capture_dev, const GUID *render_dev,
        const DSCBUFFERDESC *cbufdesc, const DSBUFFERDESC *bufdesc, HWND hwnd, DWORD level,
        IDirectSoundFullDuplex **dsfd, IDirectSoundCaptureBuffer8 **dscb8,
        IDirectSoundBuffer8 **dsb8, IUnknown *outer_unk)
{
    HRESULT hr;

    TRACE("(%s,%s,%p,%p,%p,%lx,%p,%p,%p,%p)\n", debugstr_guid(capture_dev),
            debugstr_guid(render_dev), cbufdesc, bufdesc, hwnd, level, dsfd, dscb8, dsb8,
            outer_unk);

    if (!dsfd)
        return DSERR_INVALIDPARAM;
    if (outer_unk) {
        *dsfd = NULL;
        return DSERR_NOAGGREGATION;
    }

    hr = DSOUND_FullDuplexCreate(&IID_IDirectSoundFullDuplex, (void**)dsfd);
    if (hr == DS_OK) {
        hr = IDirectSoundFullDuplex_Initialize(*dsfd, capture_dev, render_dev, cbufdesc, bufdesc,
                hwnd, level, dscb8, dsb8);
        if (hr != DS_OK) {
            IDirectSoundFullDuplex_Release(*dsfd);
            *dsfd = NULL;
            WARN("IDirectSoundFullDuplexImpl_Initialize failed\n");
        }
    }

    return hr;
}
