/*
 * IShellItem and IShellItemArray implementations
 *
 * Copyright 2008 Vincent Povirk for CodeWeavers
 *
 * 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 <stdio.h>
#include <stdarg.h>

#define COBJMACROS
#define NONAMELESSUNION

#include "windef.h"
#include "winbase.h"
#include "wine/debug.h"

#include "pidl.h"
#include "shell32_main.h"
#include "debughlp.h"

WINE_DEFAULT_DEBUG_CHANNEL(shell);

typedef struct _ShellItem {
    IShellItem2             IShellItem2_iface;
    LONG                    ref;
    LPITEMIDLIST            pidl;
    IPersistIDList          IPersistIDList_iface;
} ShellItem;

typedef struct _CustomDestinationList {
    ICustomDestinationList ICustomDestinationList_iface;
    LONG ref;
} CustomDestinationList;

static inline ShellItem *impl_from_IShellItem2(IShellItem2 *iface)
{
    return CONTAINING_RECORD(iface, ShellItem, IShellItem2_iface);
}

static inline ShellItem *impl_from_IPersistIDList( IPersistIDList *iface )
{
    return CONTAINING_RECORD(iface, ShellItem, IPersistIDList_iface);
}

static inline CustomDestinationList *impl_from_ICustomDestinationList( ICustomDestinationList *iface )
{
    return CONTAINING_RECORD(iface, CustomDestinationList, ICustomDestinationList_iface);
}

static HRESULT WINAPI ShellItem_QueryInterface(IShellItem2 *iface, REFIID riid,
    void **ppv)
{
    ShellItem *This = impl_from_IShellItem2(iface);

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

    if (!ppv) return E_INVALIDARG;

    if (IsEqualIID(&IID_IUnknown, riid) || IsEqualIID(&IID_IShellItem, riid) ||
        IsEqualIID(&IID_IShellItem2, riid))
    {
        *ppv = &This->IShellItem2_iface;
    }
    else if (IsEqualIID(&IID_IPersist, riid) || IsEqualIID(&IID_IPersistIDList, riid))
    {
        *ppv = &This->IPersistIDList_iface;
    }
    else {
        FIXME("not implemented for %s\n", shdebugstr_guid(riid));
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    IUnknown_AddRef((IUnknown*)*ppv);
    return S_OK;
}

static ULONG WINAPI ShellItem_AddRef(IShellItem2 *iface)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    ULONG ref = InterlockedIncrement(&This->ref);

    TRACE("(%p), new refcount=%i\n", iface, ref);

    return ref;
}

static ULONG WINAPI ShellItem_Release(IShellItem2 *iface)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p), new refcount=%i\n", iface, ref);

    if (ref == 0)
    {
        ILFree(This->pidl);
        heap_free(This);
    }

    return ref;
}

static HRESULT ShellItem_get_parent_pidl(ShellItem *This, LPITEMIDLIST *parent_pidl)
{
    *parent_pidl = ILClone(This->pidl);
    if (*parent_pidl)
    {
        if (ILRemoveLastID(*parent_pidl))
            return S_OK;
        else
        {
            ILFree(*parent_pidl);
            *parent_pidl = NULL;
            return E_INVALIDARG;
        }
    }
    else
    {
        *parent_pidl = NULL;
        return E_OUTOFMEMORY;
    }
}

static HRESULT ShellItem_get_parent_shellfolder(ShellItem *This, IShellFolder **ppsf)
{
    LPITEMIDLIST parent_pidl;
    IShellFolder *desktop;
    HRESULT ret;

    ret = ShellItem_get_parent_pidl(This, &parent_pidl);
    if (SUCCEEDED(ret))
    {
        ret = SHGetDesktopFolder(&desktop);
        if (SUCCEEDED(ret))
        {
            if (_ILIsDesktop(parent_pidl))
            {
                *ppsf = desktop;
            }
            else
            {
                ret = IShellFolder_BindToObject(desktop, parent_pidl, NULL, &IID_IShellFolder, (void**)ppsf);
                IShellFolder_Release(desktop);
            }
        }
        ILFree(parent_pidl);
    }

    return ret;
}

static HRESULT ShellItem_get_shellfolder(ShellItem *This, IBindCtx *pbc, IShellFolder **ppsf)
{
    IShellFolder *desktop;
    HRESULT ret;

    ret = SHGetDesktopFolder(&desktop);
    if (SUCCEEDED(ret))
    {
        if (_ILIsDesktop(This->pidl))
        {
            *ppsf = desktop;
            IShellFolder_AddRef(*ppsf);
        }
        else
        {
            ret = IShellFolder_BindToObject(desktop, This->pidl, pbc, &IID_IShellFolder, (void**)ppsf);
        }

        IShellFolder_Release(desktop);
    }

    return ret;
}

static HRESULT WINAPI ShellItem_BindToHandler(IShellItem2 *iface, IBindCtx *pbc,
    REFGUID rbhid, REFIID riid, void **ppvOut)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    HRESULT ret;
    TRACE("(%p,%p,%s,%p,%p)\n", iface, pbc, shdebugstr_guid(rbhid), riid, ppvOut);

    *ppvOut = NULL;
    if (IsEqualGUID(rbhid, &BHID_SFObject))
    {
        IShellFolder *psf;
        ret = ShellItem_get_shellfolder(This, pbc, &psf);
        if (SUCCEEDED(ret))
        {
            ret = IShellFolder_QueryInterface(psf, riid, ppvOut);
            IShellFolder_Release(psf);
        }
        return ret;
    }
    else if (IsEqualGUID(rbhid, &BHID_SFUIObject))
    {
        IShellFolder *psf_parent;
        if (_ILIsDesktop(This->pidl))
            ret = SHGetDesktopFolder(&psf_parent);
        else
            ret = ShellItem_get_parent_shellfolder(This, &psf_parent);

        if (SUCCEEDED(ret))
        {
            LPCITEMIDLIST pidl = ILFindLastID(This->pidl);
            ret = IShellFolder_GetUIObjectOf(psf_parent, NULL, 1, &pidl, riid, NULL, ppvOut);
            IShellFolder_Release(psf_parent);
        }
        return ret;
    }
    else if (IsEqualGUID(rbhid, &BHID_DataObject))
    {
        return ShellItem_BindToHandler(&This->IShellItem2_iface, pbc, &BHID_SFUIObject,
                                       &IID_IDataObject, ppvOut);
    }

    FIXME("Unsupported BHID %s.\n", debugstr_guid(rbhid));

    return MK_E_NOOBJECT;
}

static HRESULT WINAPI ShellItem_GetParent(IShellItem2 *iface, IShellItem **ppsi)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    LPITEMIDLIST parent_pidl;
    HRESULT ret;

    TRACE("(%p,%p)\n", iface, ppsi);

    ret = ShellItem_get_parent_pidl(This, &parent_pidl);
    if (SUCCEEDED(ret))
    {
        ret = SHCreateShellItem(NULL, NULL, parent_pidl, ppsi);
        ILFree(parent_pidl);
    }

    return ret;
}

static HRESULT WINAPI ShellItem_GetDisplayName(IShellItem2 *iface, SIGDN sigdnName,
    LPWSTR *ppszName)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    TRACE("(%p,%x,%p)\n", iface, sigdnName, ppszName);

    return SHGetNameFromIDList(This->pidl, sigdnName, ppszName);
}

static HRESULT WINAPI ShellItem_GetAttributes(IShellItem2 *iface, SFGAOF sfgaoMask,
    SFGAOF *psfgaoAttribs)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    IShellFolder *parent_folder;
    LPITEMIDLIST child_pidl;
    HRESULT ret;

    TRACE("(%p,%x,%p)\n", iface, sfgaoMask, psfgaoAttribs);

    if (_ILIsDesktop(This->pidl))
        ret = SHGetDesktopFolder(&parent_folder);
    else
        ret = ShellItem_get_parent_shellfolder(This, &parent_folder);
    if (SUCCEEDED(ret))
    {
        child_pidl = ILFindLastID(This->pidl);
        *psfgaoAttribs = sfgaoMask;
        ret = IShellFolder_GetAttributesOf(parent_folder, 1, (LPCITEMIDLIST*)&child_pidl, psfgaoAttribs);
        *psfgaoAttribs &= sfgaoMask;
        IShellFolder_Release(parent_folder);

        if (SUCCEEDED(ret))
        {
            if(sfgaoMask == *psfgaoAttribs)
                return S_OK;
            else
                return S_FALSE;
        }
    }

    return ret;
}

static HRESULT WINAPI ShellItem_Compare(IShellItem2 *iface, IShellItem *oth,
    SICHINTF hint, int *piOrder)
{
    LPWSTR dispname, dispname_oth;
    HRESULT ret;
    TRACE("(%p,%p,%x,%p)\n", iface, oth, hint, piOrder);

    if(hint & (SICHINT_CANONICAL | SICHINT_ALLFIELDS))
        FIXME("Unsupported flags 0x%08x\n", hint);

    ret = IShellItem2_GetDisplayName(iface, SIGDN_DESKTOPABSOLUTEEDITING, &dispname);
    if(SUCCEEDED(ret))
    {
        ret = IShellItem_GetDisplayName(oth, SIGDN_DESKTOPABSOLUTEEDITING, &dispname_oth);
        if(SUCCEEDED(ret))
        {
            *piOrder = lstrcmpiW(dispname, dispname_oth);
            CoTaskMemFree(dispname_oth);
        }
        CoTaskMemFree(dispname);
    }

    if(SUCCEEDED(ret) && *piOrder &&
       (hint & SICHINT_TEST_FILESYSPATH_IF_NOT_EQUAL))
    {
        LPWSTR dispname, dispname_oth;

        TRACE("Testing filesystem path.\n");
        ret = IShellItem2_GetDisplayName(iface, SIGDN_FILESYSPATH, &dispname);
        if(SUCCEEDED(ret))
        {
            ret = IShellItem_GetDisplayName(oth, SIGDN_FILESYSPATH, &dispname_oth);
            if(SUCCEEDED(ret))
            {
                *piOrder = lstrcmpiW(dispname, dispname_oth);
                CoTaskMemFree(dispname_oth);
            }
            CoTaskMemFree(dispname);
        }
    }

    if(FAILED(ret))
        return ret;

    if(*piOrder)
        return S_FALSE;
    else
        return S_OK;
}

static HRESULT WINAPI ShellItem2_GetPropertyStore(IShellItem2 *iface, GETPROPERTYSTOREFLAGS flags,
    REFIID riid, void **ppv)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%d, %s, %p)\n", This, flags, shdebugstr_guid(riid), ppv);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_GetPropertyStoreWithCreateObject(IShellItem2 *iface,
    GETPROPERTYSTOREFLAGS flags, IUnknown *punkCreateObject, REFIID riid, void **ppv)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%08x, %p, %s, %p)\n",
          This, flags, punkCreateObject, shdebugstr_guid(riid), ppv);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_GetPropertyStoreForKeys(IShellItem2 *iface, const PROPERTYKEY *rgKeys,
    UINT cKeys, GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%p, %d, %08x, %s, %p)\n",
          This, rgKeys, cKeys, flags, shdebugstr_guid(riid), ppv);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_GetPropertyDescriptionList(IShellItem2 *iface,
    REFPROPERTYKEY keyType, REFIID riid, void **ppv)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%p, %s, %p)\n", This, keyType, debugstr_guid(riid), ppv);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_Update(IShellItem2 *iface, IBindCtx *pbc)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%p)\n", This, pbc);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_GetProperty(IShellItem2 *iface, REFPROPERTYKEY key, PROPVARIANT *ppropvar)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%p, %p)\n", This, key, ppropvar);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_GetCLSID(IShellItem2 *iface, REFPROPERTYKEY key, CLSID *pclsid)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%p, %p)\n", This, key, pclsid);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_GetFileTime(IShellItem2 *iface, REFPROPERTYKEY key, FILETIME *pft)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%p, %p)\n", This, key, pft);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_GetInt32(IShellItem2 *iface, REFPROPERTYKEY key, int *pi)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%p, %p)\n", This, key, pi);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_GetString(IShellItem2 *iface, REFPROPERTYKEY key, LPWSTR *ppsz)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%p, %p)\n", This, key, ppsz);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_GetUInt32(IShellItem2 *iface, REFPROPERTYKEY key, ULONG *pui)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%p, %p)\n", This, key, pui);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_GetUInt64(IShellItem2 *iface, REFPROPERTYKEY key, ULONGLONG *pull)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%p, %p)\n", This, key, pull);
    return E_NOTIMPL;
}

static HRESULT WINAPI ShellItem2_GetBool(IShellItem2 *iface, REFPROPERTYKEY key, BOOL *pf)
{
    ShellItem *This = impl_from_IShellItem2(iface);
    FIXME("Stub: %p (%p, %p)\n", This, key, pf);
    return E_NOTIMPL;
}


static const IShellItem2Vtbl ShellItem2_Vtbl = {
    ShellItem_QueryInterface,
    ShellItem_AddRef,
    ShellItem_Release,
    ShellItem_BindToHandler,
    ShellItem_GetParent,
    ShellItem_GetDisplayName,
    ShellItem_GetAttributes,
    ShellItem_Compare,
    ShellItem2_GetPropertyStore,
    ShellItem2_GetPropertyStoreWithCreateObject,
    ShellItem2_GetPropertyStoreForKeys,
    ShellItem2_GetPropertyDescriptionList,
    ShellItem2_Update,
    ShellItem2_GetProperty,
    ShellItem2_GetCLSID,
    ShellItem2_GetFileTime,
    ShellItem2_GetInt32,
    ShellItem2_GetString,
    ShellItem2_GetUInt32,
    ShellItem2_GetUInt64,
    ShellItem2_GetBool
};

static HRESULT WINAPI ShellItem_IPersistIDList_QueryInterface(IPersistIDList *iface,
    REFIID riid, void **ppv)
{
    ShellItem *This = impl_from_IPersistIDList(iface);
    return IShellItem2_QueryInterface(&This->IShellItem2_iface, riid, ppv);
}

static ULONG WINAPI ShellItem_IPersistIDList_AddRef(IPersistIDList *iface)
{
    ShellItem *This = impl_from_IPersistIDList(iface);
    return IShellItem2_AddRef(&This->IShellItem2_iface);
}

static ULONG WINAPI ShellItem_IPersistIDList_Release(IPersistIDList *iface)
{
    ShellItem *This = impl_from_IPersistIDList(iface);
    return IShellItem2_Release(&This->IShellItem2_iface);
}

static HRESULT WINAPI ShellItem_IPersistIDList_GetClassID(IPersistIDList* iface,
    CLSID *pClassID)
{
    *pClassID = CLSID_ShellItem;
    return S_OK;
}

static HRESULT WINAPI ShellItem_IPersistIDList_SetIDList(IPersistIDList* iface,
    LPCITEMIDLIST pidl)
{
    ShellItem *This = impl_from_IPersistIDList(iface);
    LPITEMIDLIST new_pidl;

    TRACE("(%p,%p)\n", This, pidl);

    new_pidl = ILClone(pidl);

    if (new_pidl)
    {
        ILFree(This->pidl);
        This->pidl = new_pidl;
        return S_OK;
    }
    else
        return E_OUTOFMEMORY;
}

static HRESULT WINAPI ShellItem_IPersistIDList_GetIDList(IPersistIDList* iface,
    LPITEMIDLIST *ppidl)
{
    ShellItem *This = impl_from_IPersistIDList(iface);

    TRACE("(%p,%p)\n", This, ppidl);

    *ppidl = ILClone(This->pidl);
    if (*ppidl)
        return S_OK;
    else
        return E_OUTOFMEMORY;
}

static const IPersistIDListVtbl ShellItem_IPersistIDList_Vtbl = {
    ShellItem_IPersistIDList_QueryInterface,
    ShellItem_IPersistIDList_AddRef,
    ShellItem_IPersistIDList_Release,
    ShellItem_IPersistIDList_GetClassID,
    ShellItem_IPersistIDList_SetIDList,
    ShellItem_IPersistIDList_GetIDList
};


HRESULT WINAPI IShellItem_Constructor(IUnknown *pUnkOuter, REFIID riid, void **ppv)
{
    ShellItem *This;
    HRESULT ret;

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

    *ppv = NULL;

    if (pUnkOuter) return CLASS_E_NOAGGREGATION;

    This = heap_alloc(sizeof(*This));
    This->IShellItem2_iface.lpVtbl = &ShellItem2_Vtbl;
    This->ref = 1;
    This->pidl = NULL;
    This->IPersistIDList_iface.lpVtbl = &ShellItem_IPersistIDList_Vtbl;

    ret = IShellItem2_QueryInterface(&This->IShellItem2_iface, riid, ppv);
    IShellItem2_Release(&This->IShellItem2_iface);

    return ret;
}

HRESULT WINAPI SHCreateShellItem(LPCITEMIDLIST pidlParent,
    IShellFolder *psfParent, LPCITEMIDLIST pidl, IShellItem **ppsi)
{
    LPITEMIDLIST new_pidl;
    HRESULT ret;

    TRACE("(%p,%p,%p,%p)\n", pidlParent, psfParent, pidl, ppsi);

    *ppsi = NULL;

    if (!pidl)
    {
        return E_INVALIDARG;
    }
    else if (pidlParent || psfParent)
    {
        LPITEMIDLIST temp_parent=NULL;
        if (!pidlParent)
        {
            IPersistFolder2* ppf2Parent;

            if (FAILED(IShellFolder_QueryInterface(psfParent, &IID_IPersistFolder2, (void**)&ppf2Parent)))
            {
                FIXME("couldn't get IPersistFolder2 interface of parent\n");
                return E_NOINTERFACE;
            }

            if (FAILED(IPersistFolder2_GetCurFolder(ppf2Parent, &temp_parent)))
            {
                FIXME("couldn't get parent PIDL\n");
                IPersistFolder2_Release(ppf2Parent);
                return E_NOINTERFACE;
            }

            pidlParent = temp_parent;
            IPersistFolder2_Release(ppf2Parent);
        }

        new_pidl = ILCombine(pidlParent, pidl);
        ILFree(temp_parent);

        if (!new_pidl)
            return E_OUTOFMEMORY;
    }
    else
    {
        new_pidl = ILClone(pidl);
        if (!new_pidl)
            return E_OUTOFMEMORY;
    }

    ret = SHCreateItemFromIDList(new_pidl, &IID_IShellItem, (void**)ppsi);
    ILFree(new_pidl);

    return ret;
}

HRESULT WINAPI SHCreateItemFromParsingName(PCWSTR pszPath,
    IBindCtx *pbc, REFIID riid, void **ppv)
{
    LPITEMIDLIST pidl;
    HRESULT ret;

    *ppv = NULL;

    ret = SHParseDisplayName(pszPath, pbc, &pidl, 0, NULL);
    if(SUCCEEDED(ret))
    {
        ret = SHCreateItemFromIDList(pidl, riid, ppv);
        ILFree(pidl);
    }
    return ret;
}

HRESULT WINAPI SHCreateItemFromRelativeName(IShellItem *parent, PCWSTR name, IBindCtx *pbc,
                                            REFIID riid, void **ppv)
{
    LPITEMIDLIST pidl_folder = NULL, pidl = NULL;
    IShellFolder *desktop = NULL, *folder = NULL;
    HRESULT hr;

    TRACE("(%p, %s, %p, %s, %p)\n", parent, wine_dbgstr_w(name), pbc, debugstr_guid(riid), ppv);

    if(!ppv)
        return E_INVALIDARG;
    *ppv = NULL;
    if(!name)
        return E_INVALIDARG;

    hr = SHGetIDListFromObject((IUnknown*)parent, &pidl_folder);
    if(hr != S_OK)
        return hr;

    hr = SHGetDesktopFolder(&desktop);
    if(hr != S_OK)
        goto cleanup;

    if(!_ILIsDesktop(pidl_folder))
    {
        hr = IShellFolder_BindToObject(desktop, pidl_folder, NULL, &IID_IShellFolder,
                                       (void**)&folder);
        if(hr != S_OK)
            goto cleanup;
    }

    hr = IShellFolder_ParseDisplayName(folder ? folder : desktop, NULL, pbc, (LPWSTR)name,
                                       NULL, &pidl, NULL);
    if(hr != S_OK)
        goto cleanup;
    hr = SHCreateItemFromIDList(pidl, riid, ppv);

cleanup:
    if(pidl_folder)
        ILFree(pidl_folder);
    if(pidl)
        ILFree(pidl);
    if(desktop)
        IShellFolder_Release(desktop);
    if(folder)
        IShellFolder_Release(folder);
    return hr;
}

HRESULT WINAPI SHCreateItemFromIDList(PCIDLIST_ABSOLUTE pidl, REFIID riid, void **ppv)
{
    IPersistIDList *persist;
    HRESULT ret;

    if(!pidl)
        return E_INVALIDARG;

    *ppv = NULL;
    ret = IShellItem_Constructor(NULL, &IID_IPersistIDList, (void**)&persist);
    if(FAILED(ret))
        return ret;

    ret = IPersistIDList_SetIDList(persist, pidl);
    if(FAILED(ret))
    {
        IPersistIDList_Release(persist);
        return ret;
    }

    ret = IPersistIDList_QueryInterface(persist, riid, ppv);
    IPersistIDList_Release(persist);
    return ret;
}

HRESULT WINAPI SHCreateItemInKnownFolder(REFKNOWNFOLDERID rfid, DWORD flags,
                                         PCWSTR filename, REFIID riid, void **ppv)
{
    HRESULT hr;
    IShellItem *parent = NULL;
    LPITEMIDLIST pidl = NULL;

    TRACE("(%p, %x, %s, %s, %p)\n", rfid, flags, wine_dbgstr_w(filename),
          debugstr_guid(riid), ppv);

    if(!rfid || !ppv)
        return E_INVALIDARG;

    *ppv = NULL;
    hr = SHGetKnownFolderIDList(rfid, flags, NULL, &pidl);
    if(hr != S_OK)
        return hr;

    hr = SHCreateItemFromIDList(pidl, &IID_IShellItem, (void**)&parent);
    if(hr != S_OK)
    {
        ILFree(pidl);
        return hr;
    }

    if(filename)
        hr = SHCreateItemFromRelativeName(parent, filename, NULL, riid, ppv);
    else
        hr = IShellItem_QueryInterface(parent, riid, ppv);

    ILFree(pidl);
    IShellItem_Release(parent);
    return hr;
}

HRESULT WINAPI SHGetItemFromDataObject(IDataObject *pdtobj,
    DATAOBJ_GET_ITEM_FLAGS dwFlags, REFIID riid, void **ppv)
{
    FORMATETC fmt;
    STGMEDIUM medium;
    HRESULT ret;

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

    if(!pdtobj)
        return E_INVALIDARG;

    fmt.cfFormat = RegisterClipboardFormatW(CFSTR_SHELLIDLISTW);
    fmt.ptd = NULL;
    fmt.dwAspect = DVASPECT_CONTENT;
    fmt.lindex = -1;
    fmt.tymed = TYMED_HGLOBAL;

    ret = IDataObject_GetData(pdtobj, &fmt, &medium);
    if(SUCCEEDED(ret))
    {
        LPIDA pida = GlobalLock(medium.u.hGlobal);

        if((pida->cidl > 1 && !(dwFlags & DOGIF_ONLY_IF_ONE)) ||
           pida->cidl == 1)
        {
            LPITEMIDLIST pidl;

            /* Get the first pidl (parent + child1) */
            pidl = ILCombine((LPCITEMIDLIST) ((LPBYTE)pida+pida->aoffset[0]),
                             (LPCITEMIDLIST) ((LPBYTE)pida+pida->aoffset[1]));

            ret = SHCreateItemFromIDList(pidl, riid, ppv);
            ILFree(pidl);
        }
        else
        {
            ret = E_FAIL;
        }

        GlobalUnlock(medium.u.hGlobal);
        GlobalFree(medium.u.hGlobal);
    }

    if(FAILED(ret) && !(dwFlags & DOGIF_NO_HDROP))
    {
        TRACE("Attempting to fall back on CF_HDROP.\n");

        fmt.cfFormat = CF_HDROP;
        fmt.ptd = NULL;
        fmt.dwAspect = DVASPECT_CONTENT;
        fmt.lindex = -1;
        fmt.tymed = TYMED_HGLOBAL;

        ret = IDataObject_GetData(pdtobj, &fmt, &medium);
        if(SUCCEEDED(ret))
        {
            DROPFILES *df = GlobalLock(medium.u.hGlobal);
            LPBYTE files = (LPBYTE)df + df->pFiles;
            BOOL multiple_files = FALSE;

            ret = E_FAIL;
            if(!df->fWide)
            {
                WCHAR filename[MAX_PATH];
                PCSTR first_file = (PCSTR)files;
                if(*(files + lstrlenA(first_file) + 1) != 0)
                    multiple_files = TRUE;

                if( !(multiple_files && (dwFlags & DOGIF_ONLY_IF_ONE)) )
                {
                    MultiByteToWideChar(CP_ACP, 0, first_file, -1, filename, MAX_PATH);
                    ret = SHCreateItemFromParsingName(filename, NULL, riid, ppv);
                }
            }
            else
            {
                PCWSTR first_file = (PCWSTR)files;
                if(*((PCWSTR)files + lstrlenW(first_file) + 1) != 0)
                    multiple_files = TRUE;

                if( !(multiple_files && (dwFlags & DOGIF_ONLY_IF_ONE)) )
                    ret = SHCreateItemFromParsingName(first_file, NULL, riid, ppv);
            }

            GlobalUnlock(medium.u.hGlobal);
            GlobalFree(medium.u.hGlobal);
        }
    }

    if(FAILED(ret) && !(dwFlags & DOGIF_NO_URL))
    {
        FIXME("Failed to create item, should try CF_URL.\n");
    }

    return ret;
}

HRESULT WINAPI SHGetItemFromObject(IUnknown *punk, REFIID riid, void **ppv)
{
    LPITEMIDLIST pidl;
    HRESULT ret;

    ret = SHGetIDListFromObject(punk, &pidl);
    if(SUCCEEDED(ret))
    {
        ret = SHCreateItemFromIDList(pidl, riid, ppv);
        ILFree(pidl);
    }

    return ret;
}

/*************************************************************************
 * IEnumShellItems implementation
 */
typedef struct {
    IEnumShellItems IEnumShellItems_iface;
    LONG ref;

    IShellItemArray *array;
    DWORD count;
    DWORD position;
} IEnumShellItemsImpl;

static inline IEnumShellItemsImpl *impl_from_IEnumShellItems(IEnumShellItems *iface)
{
    return CONTAINING_RECORD(iface, IEnumShellItemsImpl, IEnumShellItems_iface);
}

static HRESULT WINAPI IEnumShellItems_fnQueryInterface(IEnumShellItems *iface,
                                                       REFIID riid,
                                                       void **ppvObject)
{
    IEnumShellItemsImpl *This = impl_from_IEnumShellItems(iface);
    TRACE("%p (%s, %p)\n", This, shdebugstr_guid(riid), ppvObject);

    *ppvObject = NULL;
    if(IsEqualIID(riid, &IID_IEnumShellItems) ||
       IsEqualIID(riid, &IID_IUnknown))
    {
        *ppvObject = &This->IEnumShellItems_iface;
    }

    if(*ppvObject)
    {
        IUnknown_AddRef((IUnknown*)*ppvObject);
        return S_OK;
    }

    return E_NOINTERFACE;
}

static ULONG WINAPI IEnumShellItems_fnAddRef(IEnumShellItems *iface)
{
    IEnumShellItemsImpl *This = impl_from_IEnumShellItems(iface);
    LONG ref = InterlockedIncrement(&This->ref);
    TRACE("%p - ref %d\n", This, ref);

    return ref;
}

static ULONG WINAPI IEnumShellItems_fnRelease(IEnumShellItems *iface)
{
    IEnumShellItemsImpl *This = impl_from_IEnumShellItems(iface);
    LONG ref = InterlockedDecrement(&This->ref);
    TRACE("%p - ref %d\n", This, ref);

    if(!ref)
    {
        TRACE("Freeing.\n");
        IShellItemArray_Release(This->array);
        heap_free(This);
        return 0;
    }

    return ref;
}

static HRESULT WINAPI IEnumShellItems_fnNext(IEnumShellItems* iface,
                                             ULONG celt,
                                             IShellItem **rgelt,
                                             ULONG *pceltFetched)
{
    IEnumShellItemsImpl *This = impl_from_IEnumShellItems(iface);
    HRESULT hr = S_FALSE;
    UINT i;
    ULONG fetched = 0;
    TRACE("%p (%d %p %p)\n", This, celt, rgelt, pceltFetched);

    if(pceltFetched == NULL && celt != 1)
        return E_INVALIDARG;

    for(i = This->position; fetched < celt && i < This->count; i++) {
        hr = IShellItemArray_GetItemAt(This->array, i, &rgelt[fetched]);
        if(FAILED(hr))
            break;
        fetched++;
        This->position++;
    }

    if(SUCCEEDED(hr))
    {
        if(pceltFetched != NULL)
            *pceltFetched = fetched;

        if(fetched > 0)
            return S_OK;

        return S_FALSE;
    }

    return hr;
}

static HRESULT WINAPI IEnumShellItems_fnSkip(IEnumShellItems* iface, ULONG celt)
{
    IEnumShellItemsImpl *This = impl_from_IEnumShellItems(iface);
    TRACE("%p (%d)\n", This, celt);

    This->position = min(This->position + celt, This->count-1);

    return S_OK;
}

static HRESULT WINAPI IEnumShellItems_fnReset(IEnumShellItems* iface)
{
    IEnumShellItemsImpl *This = impl_from_IEnumShellItems(iface);
    TRACE("%p\n", This);

    This->position = 0;

    return S_OK;
}

static HRESULT WINAPI IEnumShellItems_fnClone(IEnumShellItems* iface, IEnumShellItems **ppenum)
{
    IEnumShellItemsImpl *This = impl_from_IEnumShellItems(iface);
    TRACE("%p (%p)\n", This, ppenum);

    /* Not implemented anywhere */
    *ppenum = NULL;

    return E_NOTIMPL;
}

static const IEnumShellItemsVtbl vt_IEnumShellItems = {
    IEnumShellItems_fnQueryInterface,
    IEnumShellItems_fnAddRef,
    IEnumShellItems_fnRelease,
    IEnumShellItems_fnNext,
    IEnumShellItems_fnSkip,
    IEnumShellItems_fnReset,
    IEnumShellItems_fnClone
};

static HRESULT IEnumShellItems_Constructor(IShellItemArray *array, IEnumShellItems **ppesi)
{
    IEnumShellItemsImpl *This;
    HRESULT ret;

    This = heap_alloc(sizeof(*This));
    if(!This)
        return E_OUTOFMEMORY;

    This->ref = 1;
    This->IEnumShellItems_iface.lpVtbl = &vt_IEnumShellItems;
    This->array = array;
    This->position = 0;

    IShellItemArray_AddRef(This->array);
    IShellItemArray_GetCount(This->array, &This->count);

    ret = IEnumShellItems_QueryInterface(&This->IEnumShellItems_iface, &IID_IEnumShellItems, (void**)ppesi);
    IEnumShellItems_Release(&This->IEnumShellItems_iface);

    return ret;
}


/*************************************************************************
 * IShellItemArray implementation
 */
typedef struct {
    IShellItemArray IShellItemArray_iface;
    LONG ref;

    IShellItem **array;
    DWORD item_count;
} IShellItemArrayImpl;

static inline IShellItemArrayImpl *impl_from_IShellItemArray(IShellItemArray *iface)
{
    return CONTAINING_RECORD(iface, IShellItemArrayImpl, IShellItemArray_iface);
}

static HRESULT WINAPI IShellItemArray_fnQueryInterface(IShellItemArray *iface,
                                                       REFIID riid,
                                                       void **ppvObject)
{
    IShellItemArrayImpl *This = impl_from_IShellItemArray(iface);
    TRACE("%p (%s, %p)\n", This, shdebugstr_guid(riid), ppvObject);

    *ppvObject = NULL;
    if(IsEqualIID(riid, &IID_IShellItemArray) ||
       IsEqualIID(riid, &IID_IUnknown))
    {
        *ppvObject = &This->IShellItemArray_iface;
    }

    if(*ppvObject)
    {
        IUnknown_AddRef((IUnknown*)*ppvObject);
        return S_OK;
    }

    return E_NOINTERFACE;
}

static ULONG WINAPI IShellItemArray_fnAddRef(IShellItemArray *iface)
{
    IShellItemArrayImpl *This = impl_from_IShellItemArray(iface);
    LONG ref = InterlockedIncrement(&This->ref);
    TRACE("%p - ref %d\n", This, ref);

    return ref;
}

static ULONG WINAPI IShellItemArray_fnRelease(IShellItemArray *iface)
{
    IShellItemArrayImpl *This = impl_from_IShellItemArray(iface);
    LONG ref = InterlockedDecrement(&This->ref);
    TRACE("%p - ref %d\n", This, ref);

    if(!ref)
    {
        UINT i;
        TRACE("Freeing.\n");

        for(i = 0; i < This->item_count; i++)
            IShellItem_Release(This->array[i]);

        heap_free(This->array);
        heap_free(This);
        return 0;
    }

    return ref;
}

static HRESULT WINAPI IShellItemArray_fnBindToHandler(IShellItemArray *iface,
                                                      IBindCtx *pbc,
                                                      REFGUID bhid,
                                                      REFIID riid,
                                                      void **ppvOut)
{
    IShellItemArrayImpl *This = impl_from_IShellItemArray(iface);
    FIXME("Stub: %p (%p, %s, %s, %p)\n",
          This, pbc, shdebugstr_guid(bhid), shdebugstr_guid(riid), ppvOut);

    return E_NOTIMPL;
}

static HRESULT WINAPI IShellItemArray_fnGetPropertyStore(IShellItemArray *iface,
                                                         GETPROPERTYSTOREFLAGS flags,
                                                         REFIID riid,
                                                         void **ppv)
{
    IShellItemArrayImpl *This = impl_from_IShellItemArray(iface);
    FIXME("Stub: %p (%x, %s, %p)\n", This, flags, shdebugstr_guid(riid), ppv);

    return E_NOTIMPL;
}

static HRESULT WINAPI IShellItemArray_fnGetPropertyDescriptionList(IShellItemArray *iface,
                                                                   REFPROPERTYKEY keyType,
                                                                   REFIID riid,
                                                                   void **ppv)
{
    IShellItemArrayImpl *This = impl_from_IShellItemArray(iface);
    FIXME("Stub: %p (%p, %s, %p)\n",
          This, keyType, shdebugstr_guid(riid), ppv);

    return E_NOTIMPL;
}

static HRESULT WINAPI IShellItemArray_fnGetAttributes(IShellItemArray *iface,
                                                      SIATTRIBFLAGS AttribFlags,
                                                      SFGAOF sfgaoMask,
                                                      SFGAOF *psfgaoAttribs)
{
    IShellItemArrayImpl *This = impl_from_IShellItemArray(iface);
    HRESULT hr = S_OK;
    SFGAOF attr;
    UINT i;
    TRACE("%p (%x, %x, %p)\n", This, AttribFlags, sfgaoMask, psfgaoAttribs);

    if(AttribFlags & ~(SIATTRIBFLAGS_AND|SIATTRIBFLAGS_OR))
        FIXME("%08x contains unsupported attribution flags\n", AttribFlags);

    for(i = 0; i < This->item_count; i++)
    {
        hr = IShellItem_GetAttributes(This->array[i], sfgaoMask, &attr);
        if(FAILED(hr))
            break;

        if(i == 0)
        {
            *psfgaoAttribs = attr;
            continue;
        }

        switch(AttribFlags & SIATTRIBFLAGS_MASK)
        {
        case SIATTRIBFLAGS_AND:
            *psfgaoAttribs &= attr;
            break;
        case SIATTRIBFLAGS_OR:
            *psfgaoAttribs |= attr;
            break;
        }
    }

    if(SUCCEEDED(hr))
    {
        if(*psfgaoAttribs == sfgaoMask)
            return S_OK;

        return S_FALSE;
    }

    return hr;
}

static HRESULT WINAPI IShellItemArray_fnGetCount(IShellItemArray *iface,
                                                 DWORD *pdwNumItems)
{
    IShellItemArrayImpl *This = impl_from_IShellItemArray(iface);
    TRACE("%p (%p)\n", This, pdwNumItems);

    *pdwNumItems = This->item_count;

    return S_OK;
}

static HRESULT WINAPI IShellItemArray_fnGetItemAt(IShellItemArray *iface,
                                                  DWORD dwIndex,
                                                  IShellItem **ppsi)
{
    IShellItemArrayImpl *This = impl_from_IShellItemArray(iface);
    TRACE("%p (%x, %p)\n", This, dwIndex, ppsi);

    /* zero indexed */
    if(dwIndex + 1 > This->item_count)
        return E_FAIL;

    *ppsi = This->array[dwIndex];
    IShellItem_AddRef(*ppsi);

    return S_OK;
}

static HRESULT WINAPI IShellItemArray_fnEnumItems(IShellItemArray *iface,
                                                  IEnumShellItems **ppenumShellItems)
{
    IShellItemArrayImpl *This = impl_from_IShellItemArray(iface);
    TRACE("%p (%p)\n", This, ppenumShellItems);
    return IEnumShellItems_Constructor(iface, ppenumShellItems);
}

static const IShellItemArrayVtbl vt_IShellItemArray = {
    IShellItemArray_fnQueryInterface,
    IShellItemArray_fnAddRef,
    IShellItemArray_fnRelease,
    IShellItemArray_fnBindToHandler,
    IShellItemArray_fnGetPropertyStore,
    IShellItemArray_fnGetPropertyDescriptionList,
    IShellItemArray_fnGetAttributes,
    IShellItemArray_fnGetCount,
    IShellItemArray_fnGetItemAt,
    IShellItemArray_fnEnumItems
};

/* Caller is responsible to AddRef all items */
static HRESULT create_shellitemarray(IShellItem **items, DWORD count, IShellItemArray **ret)
{
    IShellItemArrayImpl *This;

    TRACE("(%p, %d, %p)\n", items, count, ret);

    This = heap_alloc(sizeof(*This));
    if(!This)
        return E_OUTOFMEMORY;

    This->IShellItemArray_iface.lpVtbl = &vt_IShellItemArray;
    This->ref = 1;

    This->array = heap_alloc(count*sizeof(IShellItem*));
    if (!This->array)
    {
        heap_free(This);
        return E_OUTOFMEMORY;
    }
    memcpy(This->array, items, count*sizeof(IShellItem*));
    This->item_count = count;

    *ret = &This->IShellItemArray_iface;
    return S_OK;
}

HRESULT WINAPI SHCreateShellItemArray(PCIDLIST_ABSOLUTE pidlParent,
                                      IShellFolder *psf,
                                      UINT cidl,
                                      PCUITEMID_CHILD_ARRAY ppidl,
                                      IShellItemArray **ppsiItemArray)
{
    IShellItem **array;
    HRESULT ret = E_FAIL;
    UINT i;

    TRACE("%p, %p, %d, %p, %p\n", pidlParent, psf, cidl, ppidl, ppsiItemArray);

    *ppsiItemArray = NULL;

    if(!pidlParent && !psf)
        return E_POINTER;

    if(!ppidl)
        return E_INVALIDARG;

    array = heap_alloc_zero(cidl*sizeof(IShellItem*));
    if(!array)
        return E_OUTOFMEMORY;

    for(i = 0; i < cidl; i++)
    {
        ret = SHCreateShellItem(pidlParent, psf, ppidl[i], &array[i]);
        if(FAILED(ret)) break;
    }

    if(SUCCEEDED(ret))
    {
        ret = create_shellitemarray(array, cidl, ppsiItemArray);
        if(SUCCEEDED(ret))
        {
            heap_free(array);
            return ret;
        }
    }

    /* Something failed, clean up. */
    for(i = 0; i < cidl; i++)
        if(array[i]) IShellItem_Release(array[i]);
    heap_free(array);
    return ret;
}

HRESULT WINAPI SHCreateShellItemArrayFromShellItem(IShellItem *item, REFIID riid, void **ppv)
{
    IShellItemArray *array;
    HRESULT ret;

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

    *ppv = NULL;

    IShellItem_AddRef(item);
    ret = create_shellitemarray(&item, 1, &array);
    if(FAILED(ret))
    {
        IShellItem_Release(item);
        return ret;
    }

    ret = IShellItemArray_QueryInterface(array, riid, ppv);
    IShellItemArray_Release(array);
    return ret;
}

HRESULT WINAPI SHCreateShellItemArrayFromDataObject(IDataObject *pdo, REFIID riid, void **ppv)
{
    IShellItemArray *psia;
    FORMATETC fmt;
    STGMEDIUM medium;
    HRESULT ret;

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

    if(!pdo)
        return E_INVALIDARG;

    *ppv = NULL;

    fmt.cfFormat = RegisterClipboardFormatW(CFSTR_SHELLIDLISTW);
    fmt.ptd = NULL;
    fmt.dwAspect = DVASPECT_CONTENT;
    fmt.lindex = -1;
    fmt.tymed = TYMED_HGLOBAL;

    ret = IDataObject_GetData(pdo, &fmt, &medium);
    if(SUCCEEDED(ret))
    {
        LPIDA pida = GlobalLock(medium.u.hGlobal);
        LPCITEMIDLIST parent_pidl;
        LPCITEMIDLIST *children;
        UINT i;
        TRACE("Converting %d objects.\n", pida->cidl);

        parent_pidl = (LPCITEMIDLIST) ((LPBYTE)pida+pida->aoffset[0]);

        children = heap_alloc(sizeof(LPCITEMIDLIST)*pida->cidl);
        for(i = 0; i < pida->cidl; i++)
            children[i] = (LPCITEMIDLIST) ((LPBYTE)pida+pida->aoffset[i+1]);

        ret = SHCreateShellItemArray(parent_pidl, NULL, pida->cidl, children, &psia);

        heap_free(children);

        GlobalUnlock(medium.u.hGlobal);
        GlobalFree(medium.u.hGlobal);
    }

    if(SUCCEEDED(ret))
    {
        ret = IShellItemArray_QueryInterface(psia, riid, ppv);
        IShellItemArray_Release(psia);
    }

    return ret;
}

HRESULT WINAPI SHCreateShellItemArrayFromIDLists(UINT cidl,
                                                 PCIDLIST_ABSOLUTE_ARRAY pidl_array,
                                                 IShellItemArray **psia)
{
    IShellItem **array;
    HRESULT ret;
    UINT i;
    TRACE("%d, %p, %p\n", cidl, pidl_array, psia);

    *psia = NULL;

    if(cidl == 0)
        return E_INVALIDARG;

    array = heap_alloc_zero(cidl*sizeof(IShellItem*));
    if(!array)
        return E_OUTOFMEMORY;

    for(i = 0; i < cidl; i++)
    {
        ret = SHCreateShellItem(NULL, NULL, pidl_array[i], &array[i]);
        if(FAILED(ret))
            break;
    }

    if(SUCCEEDED(ret))
    {
        ret = create_shellitemarray(array, cidl, psia);
        heap_free(array);
        if(SUCCEEDED(ret))
            return ret;
    }

    for(i = 0; i < cidl; i++)
        if(array[i]) IShellItem_Release(array[i]);
    heap_free(array);
    *psia = NULL;
    return ret;
}

HRESULT WINAPI SHGetPropertyStoreFromParsingName(const WCHAR *path, IBindCtx *pbc, GETPROPERTYSTOREFLAGS flags,
    REFIID riid, void **ppv)
{
    IShellItem2 *item;
    HRESULT hr;

    TRACE("(%s %p %#x %p %p)\n", debugstr_w(path), pbc, flags, riid, ppv);

    hr = SHCreateItemFromParsingName(path, pbc, &IID_IShellItem2, (void **)&item);
    if(SUCCEEDED(hr))
    {
        hr = IShellItem2_GetPropertyStore(item, flags, riid, ppv);
        IShellItem2_Release(item);
    }

    return hr;
}

static HRESULT WINAPI CustomDestinationList_QueryInterface(ICustomDestinationList *iface, REFIID riid, void **obj)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);

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

    if (IsEqualIID(&IID_ICustomDestinationList, riid) || IsEqualIID(&IID_IUnknown, riid))
    {
        *obj = &This->ICustomDestinationList_iface;
    }
    else {
        FIXME("not implemented for %s\n", shdebugstr_guid(riid));
        *obj = NULL;
        return E_NOINTERFACE;
    }

    IUnknown_AddRef((IUnknown*)*obj);
    return S_OK;
}

static ULONG WINAPI CustomDestinationList_AddRef(ICustomDestinationList *iface)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);
    ULONG ref = InterlockedIncrement(&This->ref);

    TRACE("(%p), new refcount=%i\n", This, ref);

    return ref;
}

static ULONG WINAPI CustomDestinationList_Release(ICustomDestinationList *iface)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p), new refcount=%i\n", This, ref);

    if (ref == 0)
        heap_free(This);

    return ref;
}

static HRESULT WINAPI CustomDestinationList_SetAppID(ICustomDestinationList *iface, const WCHAR *appid)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);

    FIXME("%p (%s): stub\n", This, debugstr_w(appid));

    return E_NOTIMPL;
}

static HRESULT WINAPI CustomDestinationList_BeginList(ICustomDestinationList *iface, UINT *min_slots, REFIID riid, void **obj)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);

    FIXME("%p (%p %s %p): stub\n", This, min_slots, debugstr_guid(riid), obj);

    return E_NOTIMPL;
}

static HRESULT WINAPI CustomDestinationList_AppendCategory(ICustomDestinationList *iface, const WCHAR *category, IObjectArray *array)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);

    FIXME("%p (%s %p): stub\n", This, debugstr_w(category), array);

    return E_NOTIMPL;
}

static HRESULT WINAPI CustomDestinationList_AppendKnownCategory(ICustomDestinationList *iface, KNOWNDESTCATEGORY category)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);

    FIXME("%p (%d): stub\n", This, category);

    return E_NOTIMPL;
}

static HRESULT WINAPI CustomDestinationList_AddUserTasks(ICustomDestinationList *iface, IObjectArray *tasks)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);

    FIXME("%p (%p): stub\n", This, tasks);

    return E_NOTIMPL;
}

static HRESULT WINAPI CustomDestinationList_CommitList(ICustomDestinationList *iface)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);

    FIXME("%p: stub\n", This);

    return E_NOTIMPL;
}

static HRESULT WINAPI CustomDestinationList_GetRemovedDestinations(ICustomDestinationList *iface, REFIID riid, void **obj)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);

    FIXME("%p (%s %p): stub\n", This, debugstr_guid(riid), obj);

    return E_NOTIMPL;
}

static HRESULT WINAPI CustomDestinationList_DeleteList(ICustomDestinationList *iface, const WCHAR *appid)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);

    FIXME("%p (%s): stub\n", This, debugstr_w(appid));

    return E_NOTIMPL;
}

static HRESULT WINAPI CustomDestinationList_AbortList(ICustomDestinationList *iface)
{
    CustomDestinationList *This = impl_from_ICustomDestinationList(iface);

    FIXME("%p: stub\n", This);

    return E_NOTIMPL;
}

static const ICustomDestinationListVtbl CustomDestinationListVtbl =
{
    CustomDestinationList_QueryInterface,
    CustomDestinationList_AddRef,
    CustomDestinationList_Release,
    CustomDestinationList_SetAppID,
    CustomDestinationList_BeginList,
    CustomDestinationList_AppendCategory,
    CustomDestinationList_AppendKnownCategory,
    CustomDestinationList_AddUserTasks,
    CustomDestinationList_CommitList,
    CustomDestinationList_GetRemovedDestinations,
    CustomDestinationList_DeleteList,
    CustomDestinationList_AbortList
};

HRESULT WINAPI CustomDestinationList_Constructor(IUnknown *outer, REFIID riid, void **obj)
{
    CustomDestinationList *list;
    HRESULT hr;

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

    if (outer)
        return CLASS_E_NOAGGREGATION;

    if(!(list = heap_alloc(sizeof(*list))))
        return E_OUTOFMEMORY;

    list->ICustomDestinationList_iface.lpVtbl = &CustomDestinationListVtbl;
    list->ref = 1;

    hr = ICustomDestinationList_QueryInterface(&list->ICustomDestinationList_iface, riid, obj);
    ICustomDestinationList_Release(&list->ICustomDestinationList_iface);
    return hr;
}