#if defined(_MSC_VER) && (_MSC_VER >= 1200)
#pragma once
#endif

#ifndef _INC_SHIDFACT
#define _INC_SHIDFACT

#ifdef __cplusplus

#include <shobjidl.h>     // for IDelegateFolder
#include <propvarutil.h>  // for PropVariantToVariant

template <class T, DWORD dwMagic>
class CItemIDFactory : public IDelegateFolder
{
protected:
    CItemIDFactory() : _pmalloc(NULL) {}

    // if we have an IMalloc from IDelegateFolder::SetItemAlloc then clean it up

    virtual ~CItemIDFactory()
    {
        if (_pmalloc)
        {
            _pmalloc->Release();
        }
    }

public:
    // IUnknown provided by derived classes

    // IDelegateFolder
    IFACEMETHODIMP SetItemAlloc(__inout_opt IMalloc *pmalloc)
    {
        IUnknown_Set((IUnknown**)&_pmalloc, pmalloc);
        return S_OK;
    }

    BOOL IsDelegateFolder()
    {
        return (_pmalloc != NULL);
    }

    // get a read only pointer to the client provided structure in the first ItemID in the IDList.
    // returns NULL if the IDList isn't valid.

    static const UNALIGNED T* GetDataFromIDList(PCUIDLIST_RELATIVE pidl)
    {
        PCITEM pitem = _IsValid(pidl);
        return pitem ? &pitem->innerData : NULL;
    }

    static HRESULT GetDataFromIDList(PCUIDLIST_RELATIVE pidl, __out const UNALIGNED T** ppData)
    {
        *ppData = GetDataFromIDList(pidl);
        return (*ppData) ? S_OK : E_INVALIDARG;
    }

    // return a read only pointer to the serialized property storage that we use for storing metadata

    static PCUSERIALIZEDPROPSTORAGE GetPropertyStorage(PCUIDLIST_RELATIVE pidl, __out DWORD* pcb)
    {
        PCITEM pitem = _IsValid(pidl);
        if (pitem && pitem->cbPropStore)
        {
            *pcb = pitem->cbPropStore;
            return (PCUSERIALIZEDPROPSTORAGE)(pitem + 1);
        }
        return NULL;
    }

    // return a property from the IPropertyStore within the IDList, convert the resulting PROPVARIANT
    // to a variant (useful when implementing IShellFolder2::GetDetailsEx)
    // return vt == VT_EMPTY if not found

    static HRESULT GetPropertyFromIDList(PCUIDLIST_RELATIVE pidl, REFPROPERTYKEY rkey, __out VARIANT *pvar)
    {
        PROPVARIANT pv = {pvar->vt};
        HRESULT hr = GetPropertyFromIDList(pidl, rkey, &pv);
        if (SUCCEEDED(hr))
        {
            hr = PropVariantToVariant(&pv, pvar);
            PropVariantClear(&pv);
        }
        return hr;
    }

    static HRESULT GetPropertyFromIDList(PCUIDLIST_RELATIVE pidl, LPCWSTR pszName, __out VARIANT *pvar)
    {
        PROPVARIANT pv = {pvar->vt};
        HRESULT hr = GetPropertyFromIDList(pidl, pszName, &pv);
        if (SUCCEEDED(hr))
        {
            hr = PropVariantToVariant(&pv, pvar);
            PropVariantClear(&pv);
        }
        return hr;
    }

    // read a PROPVARIANT from the IPropertyStore within the first ItemID in the IDList.

    static HRESULT GetPropertyFromIDList(PCUIDLIST_RELATIVE pidl, REFPROPERTYKEY rkey, __out PROPVARIANT *pv)
    {
        PropVariantInit(pv);
        HRESULT hr = E_INVALIDARG;
        DWORD cb;
        PCUSERIALIZEDPROPSTORAGE psps = GetPropertyStorage(pidl, &cb);
        if (psps)
        {
            hr = PSGetPropertyFromPropertyStorage(psps, cb, rkey, pv);
            if (SUCCEEDED(hr) && (pv->vt == VT_EMPTY))
            {
                hr = HRESULT_FROM_WIN32(ERROR_UNKNOWN_PROPERTY);
            }
        }
        return hr;
    }

    // read a PROPVARIANT from the IPropertyStore within the first ItemID in the IDList using the named property

    static HRESULT GetPropertyFromIDList(PCUIDLIST_RELATIVE pidl, LPCWSTR pszName, __out PROPVARIANT *pv)
    {
        PropVariantInit(pv);
        HRESULT hr = E_INVALIDARG;
        DWORD cb;
        PCUSERIALIZEDPROPSTORAGE psps = GetPropertyStorage(pidl, &cb);
        if (psps)
        {
            hr = PSGetNamedPropertyFromPropertyStorage(psps, cb, pszName, pv);
            if (SUCCEEDED(hr) && (pv->vt == VT_EMPTY))
            {
                hr = HRESULT_FROM_WIN32(ERROR_UNKNOWN_PROPERTY);
            }
        }
        return hr;
    }

    // create an instance of the IPropertyStore based on the serialized property storage associated
    // with the first ItemID.

    static HRESULT GetPropertyStorageFromIDList(PCUIDLIST_RELATIVE pidl, REFIID riid, __out void **ppv)
    {
        HRESULT hr = E_INVALIDARG;
        if (pidl && ppv)
        {
            DWORD cb;
            PCUSERIALIZEDPROPSTORAGE psps = GetPropertyStorage(pidl, &cb);
            if (psps)
            {
                IPersistSerializedPropStorage* ppsps;
                hr = PSCreateMemoryPropertyStore(IID_PPV_ARGS(&ppsps));
                if (SUCCEEDED(hr))
                {
                    hr = ppsps->SetPropertyStorage(psps, cb);
                    if (SUCCEEDED(hr))
                    {
                        hr = ppsps->SetFlags(FPSPS_READONLY);
                        if (SUCCEEDED(hr))
                        {
                            hr = ppsps->QueryInterface(riid, ppv);
                        }
                    }
                    ppsps->Release();
                }
            }
        }
        return hr;
    }

    // package up the user supplied data into an ItemID.  pinner points to the clients structure
    // that should be copied, and the IPropertyStore is serialized into the ItemID.   If the
    // client has called IDelegateFolder::SetItemAlloc then we will allocate with that
    // allocator, otherwise the COM allocator is used.  the size of the user supplied data must
    // equal sizeof(T). don't use structs with variably allocated array/string members. the
    // struct must also follow standard SHITEMID for persistance and portability. see
    // shtypes.idl for details.
    HRESULT CreateItemID(const UNALIGNED T *pinner, __inout_opt IPropertyStore *pps, __out PITEMID_CHILD *ppidl)
    {
        return s_CreateItemID(pinner, pps, ppidl, _pmalloc);
    }

    static HRESULT s_CreateItemID(const UNALIGNED T *pinner, __inout_opt IPropertyStore *pps, __out PITEMID_CHILD *ppidl, IMalloc *pMalloc=NULL)
    {
        *ppidl = NULL;

        SERIALIZEDPROPSTORAGE *pspstg = NULL;
        DWORD cbPropStore = 0;

        // do we have a IPropertyStore, if so we are going to add this to the ItemID
        // so we need to get the serialized version of it.

        HRESULT hr = S_OK;
        if (pps)
        {
            IPersistSerializedPropStorage *ppsps;
            hr = pps->QueryInterface(IID_PPV_ARGS(&ppsps));
            if (SUCCEEDED(hr))
            {
                hr = ppsps->GetPropertyStorage(&pspstg, &cbPropStore);
                ppsps->Release();
            }
        }

        // either we succeeded at getting the serialized data, or an IPropertyStore wasn't passed to us.

        if (SUCCEEDED(hr))
        {
            UINT cbInner = sizeof(CHILDITEMID) - (sizeof(DELEGATEITEMID) - 1) + cbPropStore;
            CHILDITEMID *pitem = s_Alloc(cbInner + sizeof(USHORT), pMalloc);
            if (pitem)
            {
                pitem->dwMagic = dwMagic;
                pitem->cbPropStore = (WORD)cbPropStore;             // size of the property store
                pitem->cbInnerData = sizeof(pitem->innerData);      // size of the "innerData" structure (useful for debugging)

                if (pinner)
                    pitem->innerData = *pinner;                     // copy the structure provided by the user

                if (pspstg)
                {
                    BYTE *pbData = (BYTE*)(pitem+1);
                    memcpy(pbData, pspstg, cbPropStore);            // copy property store to ItemID
                }

                *ppidl = (PITEMID_CHILD)pitem;
                hr = S_OK;
            }
            else
            {
                hr = E_OUTOFMEMORY;
            }

            CoTaskMemFree(pspstg);
        }

        return hr;
    }

private:

    // ItemID form is derived from the delegate folder verison, this allows us to easily
    // switch allocators without having to recompute the ItemID structure dynamically.

    #include <pshpack1.h>
    typedef struct              // typedef struct
    {                           // {
        // these need to line up -----------------------
        WORD cbSize;            //     WORD cbSize;         // Size of entire item ID
        WORD wOuter;            //     WORD wOuter;         // Private data owned by the outer folder
        WORD cbInner;           //     WORD cbInner;        // Size of delegate's data
        // ---------------------------------------------
        DWORD dwMagic;                                      // guard word used to compare to ensure its valid
        WORD cbPropStore;                                   // size of the property store at the end of the ItemID
        WORD cbInnerData;                                   // size of the innerData structure
        T innerData;                                        // inner data returned by the client
    } CHILDITEMID;
    #include <poppack.h>

    typedef UNALIGNED CHILDITEMID * PITEM;
    typedef const UNALIGNED CHILDITEMID * PCITEM;

    // given the size of the ItemID we want, let's allocate using the supplied
    // allocator and initialize the structure

    IMalloc *_pmalloc;                                      // used to allocate ItemID when we are a delegate folder

    static CHILDITEMID* s_Alloc(SIZE_T cbInner, IMalloc *pMalloc)
    {
        DELEGATEITEMID *pidl;
        if (pMalloc)
        {
            pidl = (DELEGATEITEMID *)pMalloc->Alloc(cbInner);
        }
        else
        {
            SIZE_T cbAlloc =
                sizeof(DELEGATEITEMID) - sizeof(pidl->rgb[0]) + // header
                cbInner +                                       // inner
                sizeof(WORD);                                   // trailing null (pidl terminator)

            const SIZE_T cbSizeMax = (1 << (sizeof(pidl->cbSize)*8)) - 1;
            if (cbAlloc > cbSizeMax + sizeof(WORD))
            {
                // cbSize will overflow
                pidl = NULL;
            }
            else
            {
                pidl = (DELEGATEITEMID *)CoTaskMemAlloc(cbAlloc);
                if (pidl)
                {
                    ZeroMemory(pidl, cbAlloc);
                    pidl->cbSize = (WORD)(cbAlloc - sizeof(WORD));
                    pidl->cbInner = (WORD)cbInner;
                }
            }
        }
        return (CHILDITEMID*)pidl;
    }

    // given a relative IDList, determine if it's valid and therefore if
    // we should continue to process the contents of it.

    static PCITEM _IsValid(PCUIDLIST_RELATIVE pidl)
    {
        PCITEM pitem = (PCITEM)pidl;

        if (!pitem || pitem->cbSize < sizeof(CHILDITEMID))
            return NULL;

        if ((pitem->dwMagic != dwMagic) || (pitem->cbSize < (sizeof(CHILDITEMID) + pitem->cbPropStore)))
            return NULL;

        return pitem;
    }
};

#endif  // __cplusplus

#endif  // _INC_SHIDFACT

