#include "shellprv.h"
#include "cowsite.h"
#include "enumidlist.h"

typedef enum
{
    MAYBEBOOL_MAYBE = 0,
    MAYBEBOOL_TRUE,
    MAYBEBOOL_FALSE,
} MAYBEBOOL;

#define _GetBindWindow(p) NULL


class CShellItem    : public IShellItem 
                    , public IPersistIDList
                    , public IParentAndItem
{
public:
    CShellItem();
    
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // IShellItem
    STDMETHODIMP BindToHandler(IBindCtx *pbc, REFGUID rguidHandler, REFIID riid, void **ppv);
    STDMETHODIMP GetParent(IShellItem **ppsi);
    STDMETHODIMP GetDisplayName(SIGDN sigdnName, LPOLESTR *ppszName);        
    STDMETHODIMP GetAttributes(SFGAOF sfgaoMask, SFGAOF *psfgaoFlags);    
    STDMETHODIMP Compare(IShellItem *psi, SICHINTF hint, int *piOrder);

    // IPersist
    STDMETHODIMP GetClassID(LPCLSID lpClassID) {*lpClassID = CLSID_ShellItem; return S_OK;}
    
    // IPersistIDList
    STDMETHODIMP SetIDList(LPCITEMIDLIST pidl);
    STDMETHODIMP GetIDList(LPITEMIDLIST *ppidl);

    // IParentAndItem
    STDMETHODIMP SetParentAndItem(LPCITEMIDLIST pidlParent, IShellFolder *psf,  LPCITEMIDLIST pidlChild);
    STDMETHODIMP GetParentAndItem(LPITEMIDLIST *ppidlParent, IShellFolder **ppsf, LPITEMIDLIST *ppidlChild);

#if 0
    // IPersistStream
    STDMETHODIMP IsDirty(void);
    STDMETHODIMP Load(IStream *pStm);
    STDMETHODIMP Save(IStream *pStm, BOOL fClearDirty);
    STDMETHODIMP GetSizeMax(ULARGE_INTEGER *pcbSize);

    // implement or we cant ask for the IShellFolder in GetParentAndItem()
    // IMarshal
    STDMETHODIMP GetUnmarshalClass(
        REFIID riid,
        void *pv,
        DWORD dwDestContext,
        void *pvDestContext,
        DWORD mshlflags,
        CLSID *pCid);

    STDMETHODIMP GetMarshalSizeMax(
        REFIID riid,
        void *pv,
        DWORD dwDestContext,
        void *pvDestContext,
        DWORD mshlflags,
        DWORD *pSize);

    STDMETHODIMP MarshalInterface(
        IStream *pStm,
        REFIID riid,
        void *pv,
        dwDestContext,
        void *pvDestContext,
        DWORD mshlflags);

    STDMETHODIMP UnmarshalInterface(
        IStream *pStm,
        REFIID riid,
        void **ppv);

    STDMETHODIMP ReleaseMarshalData(IStream *pStm);

    STDMETHODIMP DisconnectObject(DWORD dwReserved);
#endif // 0 

private:  // methods
    ~CShellItem();

    void _Reset(void);
    //  BindToHandler() helpers
    HRESULT _BindToParent(REFIID riid, void **ppv);
    HRESULT _BindToSelf(REFIID riid, void **ppv);
    //  GetAttributes() helpers
    inline BOOL _IsAttrib(SFGAOF sfgao);
    //  GetDisplayName() helpers
    BOOL _SupportedName(SIGDN sigdnName, SHGDNF *pflags);
    HRESULT _FixupName(SIGDN sigdnName, LPOLESTR *ppszName);
    void _FixupAttributes(IShellFolder *psf, SFGAOF sfgaoMask);

    LONG _cRef;
    LPITEMIDLIST _pidlSelf;
    LPCITEMIDLIST _pidlChild;
    LPITEMIDLIST _pidlParent;
    IShellFolder *_psfSelf;
    IShellFolder *_psfParent;
    BOOL _fInited;
    SFGAOF _sfgaoTried;
    SFGAOF _sfgaoKnown;
};

CShellItem::CShellItem() : _cRef(1)
{
    ASSERT(!_pidlSelf);
    ASSERT(!_pidlChild);
    ASSERT(!_pidlParent);
    ASSERT(!_psfSelf);
    ASSERT(!_psfParent);
}

CShellItem::~CShellItem()
{
    _Reset();
}

void CShellItem::_Reset(void)
{
    ATOMICRELEASE(_psfSelf);
    ATOMICRELEASE(_psfParent);

    ILFree(_pidlSelf);
    ILFree(_pidlParent);

    _pidlSelf = NULL;
    _pidlParent = NULL;
    _pidlChild = NULL;      // alias into _pidlParent
}
    
STDMETHODIMP CShellItem::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = 
    {
        QITABENT(CShellItem, IShellItem),
        QITABENT(CShellItem, IPersistIDList),
        QITABENT(CShellItem, IParentAndItem),
        { 0 },
    };

    return QISearch(this, qit, riid, ppv);
}

STDMETHODIMP_(ULONG) CShellItem::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

STDMETHODIMP_(ULONG) CShellItem::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

STDMETHODIMP CShellItem::SetIDList(LPCITEMIDLIST pidl)
{
    if (!pidl)
    {
        RIPMSG(0, "Tried to Call SetIDList with a NULL pidl");
        return E_INVALIDARG;
    }

    _Reset();

    HRESULT hr = SHILClone(pidl, &_pidlSelf);
    if (SUCCEEDED(hr))
    {
        // possible this item is the desktop in which case
        // there is no parent.
        if (ILIsEmpty(_pidlSelf))
        {
            _pidlParent = NULL;
            _pidlChild = _pidlSelf;
        }
        else
        {
            _pidlParent = ILCloneParent(_pidlSelf);
            _pidlChild = ILFindLastID(_pidlSelf);

            if (NULL == _pidlParent)
            {
                hr = E_OUTOFMEMORY;
            }
        }
    }
    return hr;
}

STDMETHODIMP CShellItem::GetIDList(LPITEMIDLIST *ppidl)
{
    HRESULT hr = E_UNEXPECTED;
    
    if (_pidlSelf)
    {
        hr = SHILClone(_pidlSelf, ppidl);
    }

    return hr;
}

HRESULT CShellItem::_BindToParent(REFIID riid, void **ppv)
{
    ASSERT(_pidlChild); // we should already have a child setup

    if (!_psfParent && _pidlParent && _pidlSelf) // check pidlParent to check in case the item is the desktop
    {
        HRESULT hr;
        LPCITEMIDLIST pidlChild;

        hr = SHBindToIDListParent(_pidlSelf, IID_PPV_ARG(IShellFolder, &_psfParent), &pidlChild);

#ifdef DEBUG
        if (SUCCEEDED(hr))
        {
            ASSERT(pidlChild == _pidlChild);
        }
#endif // DEBUG
    }

    if (_psfParent)
    {
        return _psfParent->QueryInterface(riid, ppv);
    }

    return E_FAIL;
}

HRESULT CShellItem::_BindToSelf(REFIID riid, void **ppv)
{
    HRESULT hr = E_FAIL;

    if (!_psfSelf)
    {
        hr = BindToHandler(NULL, BHID_SFObject, IID_PPV_ARG(IShellFolder, &_psfSelf));
    }

    if (_psfSelf)
    {
        hr = _psfSelf->QueryInterface(riid, ppv);
    }

    return hr;
}

HRESULT _CreateLinkTargetItem(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv)
{
    SFGAOF flags = SFGAO_LINK;
    if (SUCCEEDED(psi->GetAttributes(flags, &flags)) && (flags & SFGAO_LINK))
    {
        //  this is indeed a link
        //  get the target and 
        IShellLink *psl;
        HRESULT hr = psi->BindToHandler(pbc, BHID_SFUIObject, IID_PPV_ARG(IShellLink, &psl));

        if (SUCCEEDED(hr))
        {
            DWORD slr = 0;
            HWND hwnd = _GetBindWindow(pbc);
            
            if (pbc)
            {
                BIND_OPTS2 bo;  
                bo.cbStruct = sizeof(BIND_OPTS2); // Requires size filled in.
                if (SUCCEEDED(pbc->GetBindOptions(&bo)))
                {
                    //  these are the flags to pass to resolve
                    slr = bo.dwTrackFlags;
                }
            }

            hr = psl->Resolve(hwnd, slr);

            if (S_OK == hr)
            {
                LPITEMIDLIST pidl;
                hr = psl->GetIDList(&pidl);

                if (SUCCEEDED(hr))
                {
                    IShellItem *psiTarget;
                    hr = SHCreateShellItem(NULL, NULL, pidl, &psiTarget);

                    if (SUCCEEDED(hr))
                    {
                        hr = psiTarget->QueryInterface(riid, ppv);
                        psiTarget->Release();
                    }
                    ILFree(pidl);
                }
            }
            else if (SUCCEEDED(hr))
                hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);

            psl->Release();
        }

        return hr;
    }

    return E_INVALIDARG;
}

BOOL _IsWebfolders(IShellItem *psi);
HRESULT _CreateStorageHelper(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv);
HRESULT _CreateStream(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv);
HRESULT _CreateEnumHelper(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv);

HRESULT _CreateHelperInstance(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv)
{
    IItemHandler *pih;
    HRESULT hr = SHCoCreateInstance(NULL, &rbhid, NULL, IID_PPV_ARG(IItemHandler, &pih));

    if (SUCCEEDED(hr))
    {
        hr = pih->SetItem(psi);

        if (SUCCEEDED(hr))
        {
            hr = pih->QueryInterface(riid, ppv);
        }
        pih->Release();
    }

    return hr;
}
    
enum 
{
    BNF_OBJECT          = 0x0001,
    BNF_UIOBJECT        = 0x0002,
    BNF_VIEWOBJECT      = 0x0004,
    BNF_USE_RIID        = 0x0008,
    BNF_REFLEXIVE       = 0x0010,
};
typedef DWORD BNF;

typedef HRESULT (* PFNCREATEHELPER)(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv);

typedef struct
{
    const GUID *pbhid;
    BNF bnf;
    const IID *piid;
    PFNCREATEHELPER pfn;
} BINDNONSENSE;

#define BINDHANDLER(bhid, flags, piid, pfn) { &bhid, flags, piid, pfn},
#define SFBINDHANDLER(bhid, flags, piid)    BINDHANDLER(bhid, flags, piid, NULL)
#define BINDHELPER(bhid, flags, pfn)        BINDHANDLER(bhid, flags, NULL, pfn)

const BINDNONSENSE c_bnList[] = 
{
    SFBINDHANDLER(BHID_SFObject, BNF_OBJECT | BNF_USE_RIID, NULL)
    SFBINDHANDLER(BHID_SFUIObject, BNF_UIOBJECT | BNF_USE_RIID, NULL)
    SFBINDHANDLER(BHID_SFViewObject, BNF_VIEWOBJECT | BNF_USE_RIID, NULL)
    BINDHELPER(BHID_LinkTargetItem, 0, _CreateLinkTargetItem)
    BINDHELPER(BHID_LocalCopyHelper, 0, _CreateHelperInstance)
    BINDHELPER(BHID_Storage, BNF_OBJECT | BNF_USE_RIID, _CreateStorageHelper)
    BINDHELPER(BHID_Stream, BNF_OBJECT | BNF_USE_RIID, NULL)
    BINDHELPER(BHID_StorageEnum, 0, _CreateEnumHelper)
};
    
HRESULT _GetBindNonsense(const GUID *pbhid, const IID *piid, BINDNONSENSE *pbn)
{
    HRESULT hr = MK_E_NOOBJECT;
    for (int i = 0; i < ARRAYSIZE(c_bnList); i++)
    {
        if (IsEqualGUID(*pbhid, *(c_bnList[i].pbhid)))
        {
            *pbn = c_bnList[i];
            hr = S_OK;

            if (pbn->bnf & BNF_USE_RIID)
            {
                pbn->piid = piid;
            }

            if (pbn->piid && IsEqualGUID(*(pbn->piid), *piid))
                pbn->bnf |= BNF_REFLEXIVE;

            break;
        }
    }
    return hr;
}

//  the SafeBC functions will use the pbc passed in or
//  create a new one if necessary.  either way, if 
//  the *ppbc is returned non-NULL then it is ref'd
STDAPI SHSafeRegisterObjectParam(LPCWSTR psz, IUnknown *punk, IBindCtx *pbcIn, IBindCtx **ppbc)
{
    IBindCtx *pbc = pbcIn;
    
    if (!pbc)
        CreateBindCtx(0, &pbc);
    else
        pbc->AddRef();

    *ppbc = NULL;
    
    HRESULT hr;
    if (pbc)
    {
        hr = pbc->RegisterObjectParam((LPOLESTR)psz, punk);
        if (SUCCEEDED(hr))
        {
            //  pass our ref to the caller
            *ppbc = pbc;
        }
        else
        {
            pbc->Release();
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    return hr;
}

STDMETHODIMP CShellItem::BindToHandler(IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv)
{
    //  look up handler for bind flags
    //  use the flags to determine BTO GUIO BTS CVO
    BINDNONSENSE bn = {0};
    HRESULT hr = _GetBindNonsense(&rbhid, &riid, &bn);

    *ppv = NULL;
    
    if (SUCCEEDED(hr))
    {
        hr = E_NOINTERFACE;

        if (_pidlParent && (bn.bnf & (BNF_OBJECT | BNF_UIOBJECT)))
        {
            IShellFolder *psf;
            if (SUCCEEDED(_BindToParent(IID_PPV_ARG(IShellFolder, &psf))))
            {
                if (bn.bnf & BNF_OBJECT)
                {
                    hr = psf->BindToObject(_pidlChild, pbc, *(bn.piid), ppv);
                }
                
                if (FAILED(hr) && (bn.bnf & BNF_UIOBJECT))
                {
                    HWND hwnd = _GetBindWindow(pbc);
                    hr = psf->GetUIObjectOf(hwnd, 1, &_pidlChild, *(bn.piid), NULL, ppv);
                }
                psf->Release();
            }
        }

        // if don't have a parent pidl then we are the desktop.
        if (FAILED(hr) && (NULL == _pidlParent) && (bn.bnf & BNF_OBJECT))
        {
            IShellFolder *psf;
            if (SUCCEEDED(SHGetDesktopFolder(&psf)))
            {
                hr = psf->QueryInterface(riid,ppv);
                psf->Release();
            }
        }


        if (FAILED(hr) && (bn.bnf & BNF_VIEWOBJECT))
        {
            IShellFolder *psf;

            if (SUCCEEDED(_BindToSelf(IID_PPV_ARG(IShellFolder, &psf))))
            {
                HWND hwnd = _GetBindWindow(pbc);
                hr = psf->CreateViewObject(hwnd, *(bn.piid), ppv);
                
                psf->Release();
            }
        }

        if (SUCCEEDED(hr))
        {
            if (!(bn.bnf & BNF_REFLEXIVE))
            {
                IUnknown *punk = (IUnknown *)*ppv;
                hr = punk->QueryInterface(riid, ppv);
                punk->Release();
            }
            //  else riid is the same as bn.piid
        }
        else if (bn.pfn)
        {
            hr = bn.pfn(this, pbc, rbhid, riid, ppv);
        }
    }

    return hr;
}

STDMETHODIMP CShellItem::GetParent(IShellItem **ppsi)
{
    HRESULT hr = MK_E_NOOBJECT;

    if (_pidlParent)
    {
        if (!ILIsEmpty(_pidlSelf))
        {
            CShellItem *psi = new CShellItem();
            if (psi)
            {
                // may already have the _psf Parent here so be nice
                // to have a way to do this in a set.
                hr = psi->SetIDList(_pidlParent);
                if (SUCCEEDED(hr))
                    hr = psi->QueryInterface(IID_PPV_ARG(IShellItem, ppsi));
                    
                psi->Release();
            }
            else
                hr = E_OUTOFMEMORY;
        }
    }

    return hr;
}

BOOL CShellItem::_IsAttrib(SFGAOF sfgao)
{
    HRESULT hr = GetAttributes(sfgao, &sfgao);
    return hr == S_OK;
}

#define SHGDNF_MASK     0xFFFF  //  bottom word

BOOL CShellItem::_SupportedName(SIGDN sigdn, SHGDNF *pflags)
{
    *pflags = (sigdn & SHGDNF_MASK);
    //  block this completely
    //  to avoid doing any binding at all 
    if (sigdn == SIGDN_FILESYSPATH && !_IsAttrib(SFGAO_FILESYSTEM))
        return FALSE;

    return TRUE;
}

HRESULT CShellItem::_FixupName(SIGDN sigdnName, LPOLESTR *ppszName)
{
    HRESULT hr = S_OK;
    if (sigdnName == SIGDN_URL && !UrlIsW(*ppszName, URLIS_URL))
    {
        WCHAR sz[MAX_URL_STRING];
        DWORD cch = ARRAYSIZE(sz);
        if (SUCCEEDED(UrlCreateFromPathW(*ppszName, sz, &cch, 0)))
        {
            CoTaskMemFree(*ppszName);
            hr = SHStrDupW(sz, ppszName);
        }
    }

    return hr;
}

STDMETHODIMP CShellItem::GetDisplayName(SIGDN sigdnName, LPOLESTR *ppszName)
{
    SHGDNF flags;
    if (_SupportedName(sigdnName, &flags))
    {
        IShellFolder *psf;
        HRESULT hr = _BindToParent(IID_PPV_ARG(IShellFolder, &psf));

        if (SUCCEEDED(hr))
        {
            STRRET str;
            hr = IShellFolder_GetDisplayNameOf(psf, _pidlChild, flags, &str, 0);

            if (SUCCEEDED(hr))
            {
                hr = StrRetToStrW(&str, _pidlChild, ppszName);

                if (SUCCEEDED(hr) && (int)flags != (int)sigdnName)
                {
                    hr = _FixupName(sigdnName, ppszName);
                }
            }
                
            psf->Release();
        }

        return hr;
    }
    
    return E_INVALIDARG;
}

void CShellItem::_FixupAttributes(IShellFolder *psf, SFGAOF sfgaoMask)
{
    // APPCOMPAT: The following if statement and its associated body is an APP HACK for pagis pro
    // folder. Which specifies SFGAO_FOLDER and SFGAO_FILESYSTEM but it doesn't specify SFGAO_STORAGEANCESTOR
    // This APP HACK basically checks for this condition and provides SFGAO_STORAGEANCESTOR bit.
    if (_sfgaoKnown & SFGAO_FOLDER)
    {
        if ((!(_sfgaoKnown & SFGAO_FILESYSANCESTOR) && (sfgaoMask & SFGAO_FILESYSANCESTOR))
        || ((_sfgaoKnown & SFGAO_CANMONIKER) && !(_sfgaoKnown & SFGAO_STORAGEANCESTOR) && (sfgaoMask & SFGAO_STORAGEANCESTOR)))
        {
            OBJCOMPATFLAGS ocf = SHGetObjectCompatFlags(psf, NULL);
            if (ocf & OBJCOMPATF_NEEDSFILESYSANCESTOR)
            {
                _sfgaoKnown |= SFGAO_FILESYSANCESTOR;
            }
            if (ocf & OBJCOMPATF_NEEDSSTORAGEANCESTOR)
            {
                //  switch SFGAO_CANMONIKER -> SFGAO_STORAGEANCESTOR
                _sfgaoKnown |= SFGAO_STORAGEANCESTOR;
                _sfgaoKnown &= ~SFGAO_CANMONIKER;
            }
        }
    }
}

STDMETHODIMP CShellItem::GetAttributes(SFGAOF sfgaoMask, SFGAOF *psfgaoFlags)
{
    HRESULT hr = S_OK;

    //  see if we cached this bits before...
    if ((sfgaoMask & _sfgaoTried) != sfgaoMask)
    {
        IShellFolder *psf;
        hr = _BindToParent(IID_PPV_ARG(IShellFolder, &psf));

        if (SUCCEEDED(hr))
        {
            //  we cache all the bits except VALIDATE
            _sfgaoTried |= (sfgaoMask & ~SFGAO_VALIDATE);
            SFGAOF sfgao = sfgaoMask;

            hr = psf->GetAttributesOf(1, &_pidlChild, &sfgao);

            if (SUCCEEDED(hr))
            {
                //  we cache all the bits except VALIDATE
                _sfgaoKnown |= (sfgao & ~SFGAO_VALIDATE);
                _FixupAttributes(psf, sfgaoMask);
            }

            psf->Release();
        }
    }

    *psfgaoFlags = _sfgaoKnown & sfgaoMask;

    if (SUCCEEDED(hr))
    {
        //  we return S_OK 
        //  only if the bits set match
        //  exactly the bits requested
        if (*psfgaoFlags == sfgaoMask)
            hr = S_OK;
        else
            hr = S_FALSE;
    }
        
    return hr;
}

STDMETHODIMP CShellItem::Compare(IShellItem *psi, SICHINTF hint, int *piOrder)
{
    *piOrder = 0;
    HRESULT hr = IsSameObject(SAFECAST(this, IShellItem *), psi) ? S_OK : E_FAIL;
    if (FAILED(hr))
    {
        IShellFolder *psf;
        hr = _BindToParent(IID_PPV_ARG(IShellFolder, &psf));
        if (SUCCEEDED(hr))
        {
            IParentAndItem *pfai;
            hr = psi->QueryInterface(IID_PPV_ARG(IParentAndItem, &pfai));
            if (SUCCEEDED(hr))
            {
                IShellFolder *psfOther;
                LPITEMIDLIST pidlParent, pidlChild;
                hr = pfai->GetParentAndItem(&pidlParent, &psfOther, &pidlChild);
                if (SUCCEEDED(hr))
                {
                    if (IsSameObject(psf, psfOther) || ILIsEqual(_pidlParent, pidlParent))
                    {
                        hr = psf->CompareIDs(hint & 0xf0000000, _pidlChild, pidlChild);
                    }
                    else
                    {
                        //  these items have a different parent
                        //  compare the absolute pidls
                        LPITEMIDLIST pidlOther;
                        hr = SHGetIDListFromUnk(psi, &pidlOther);
                        if (SUCCEEDED(hr))
                        {
                            IShellFolder *psfDesktop;
                            hr = SHGetDesktopFolder(&psfDesktop);
                            if (SUCCEEDED(hr))
                            {
                                hr = psfDesktop->CompareIDs(hint & 0xf0000000, _pidlSelf, pidlOther);
                                psfDesktop->Release();
                            }
                            ILFree(pidlOther);
                        }
                    }
                        
                    if (SUCCEEDED(hr))
                    {
                        *piOrder = ShortFromResult(hr);
                        if (*piOrder)
                            hr = S_FALSE;
                        else
                            hr = S_OK;
                    }
                    
                    psfOther->Release();
                    ILFree(pidlParent);
                    ILFree(pidlChild);
                }
                pfai->Release();
            }
            psf->Release();
        }
    }

    return hr;
}

// IParentAndItem
STDMETHODIMP CShellItem::SetParentAndItem(LPCITEMIDLIST pidlParent, IShellFolder *psfParent, LPCITEMIDLIST pidlChild) 
{ 
    // require to have a Parent if making this call. If don't then use SetIDList
    if (!pidlParent && !psfParent)
    {
        RIPMSG(0, "Tried to Call SetParent without a parent");
        return E_INVALIDARG;
    }

    LPITEMIDLIST pidlFree = NULL;

    if ((NULL == pidlParent) && psfParent)
    {
        if (SUCCEEDED(SHGetIDListFromUnk(psfParent, &pidlFree)))
        {
            pidlParent = pidlFree;
        }
    }
 
    if (!ILIsEmpty(_ILNext(pidlChild))) 
    {
        // if more than on item in the child pidl don't use the parent IShellFolder*
        // could revist and bind from this parent to get a new parent so don't have
        // to BindObject through the entire pidl path.

        psfParent = NULL; 
    }

    HRESULT hr = E_FAIL;
    if (pidlParent)
    {
        _Reset();

        hr = SHILCombine(pidlParent, pidlChild, &_pidlSelf);
        if (SUCCEEDED(hr))
        {
            // setup pidls so _pidlChild is a single item.
            if (_pidlParent = ILCloneParent(_pidlSelf))
            {
                _pidlChild = ILFindLastID(_pidlSelf);

                PPUNK_SET(&_psfParent, psfParent);

#ifdef DEBUG
                if (psfParent)
                {
                    LPITEMIDLIST pidlD;
                    if (SUCCEEDED(SHGetIDListFromUnk(psfParent, &pidlD)))
                    {
                        ASSERT(ILIsEqual(pidlD, pidlParent));
                        ILFree(pidlD);
                    }
                }
#endif  //DEBUG
            }
            else
            {
                hr = E_OUTOFMEMORY;
            }
        }
    }

    ILFree(pidlFree);   // maybe NULL

    return hr;
}

STDMETHODIMP CShellItem::GetParentAndItem(LPITEMIDLIST *ppidlParent, IShellFolder **ppsf, LPITEMIDLIST *ppidl)
{
    if (ppsf)
    {
        _BindToParent(IID_PPV_ARG(IShellFolder, ppsf));
    }
    
    if (ppidlParent)
    {
        if (_pidlParent)
        {
            *ppidlParent = ILClone(_pidlParent);
        }
        else
        {
            *ppidlParent = NULL;
        }
    }
    
    if (ppidl)
        *ppidl = ILClone(_pidlChild);


    HRESULT hr = S_OK;
    if ((ppidlParent && !*ppidlParent)
    ||  (ppsf && !*ppsf)
    ||  (ppidl && !*ppidl))
    {
        //  this is failure
        //  but we dont know what failed
        if (ppsf && *ppsf)
        {
            (*ppsf)->Release();
            *ppsf = NULL;
        }

        if (ppidlParent)
        {
            ILFree(*ppidlParent);
            *ppidlParent = NULL;
        }

        if (ppidl)
        {
            ILFree(*ppidl);
            *ppidl = NULL;
        }
        hr = E_OUTOFMEMORY;
    }
    return hr;
}

STDAPI CShellItem_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
{
    CShellItem *psi = new CShellItem();
    if (psi)
    {
        HRESULT hr = psi->QueryInterface(riid, ppv);
        psi->Release();
        return hr;
    }
    return E_OUTOFMEMORY;
}


class CShellItemEnum : IEnumShellItems, public CObjectWithSite
{
public:
    CShellItemEnum();
    STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder,IShellFolder *psf, DWORD dwFlags,UINT cidl,LPCITEMIDLIST *apidl);
    
    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID riid, void **ppvOut);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    STDMETHODIMP Next(ULONG celt, IShellItem **rgelt, ULONG *pceltFetched);
    STDMETHODIMP Skip(ULONG celt);
    STDMETHODIMP Reset();
    STDMETHODIMP Clone(IEnumShellItems **ppenum);

private:

    virtual ~CShellItemEnum();
    HRESULT _EnsureEnum();

    LONG _cRef;
    DWORD _dwFlags;

    IShellFolder *_psf;
    IEnumIDList *_penum;
    LPITEMIDLIST _pidlFolder;
};



STDMETHODIMP CShellItemEnum::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = 
    {
        QITABENT(CShellItemEnum, IEnumShellItems),
        QITABENT(CShellItemEnum, IObjectWithSite),
        { 0 },
    };

    return QISearch(this, qit, riid, ppv);
}

STDMETHODIMP_(ULONG) CShellItemEnum::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

STDMETHODIMP_(ULONG) CShellItemEnum::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

STDMETHODIMP CShellItemEnum::Next(ULONG celt, IShellItem **rgelt, ULONG *pceltFetched)
{
    HRESULT hr = _EnsureEnum();
    if (FAILED(hr))
        return hr;

    ULONG uTemp;
    if (!pceltFetched)
        pceltFetched = &uTemp;
    
    *pceltFetched = 0;
    
    while (celt--)
    {
        LPITEMIDLIST pidl;
        ULONG cFetched;
        hr = _penum->Next(1, &pidl, &cFetched);
        if (S_OK == hr)
        {
            hr = SHCreateShellItem(_pidlFolder, _psf, pidl, &rgelt[*pceltFetched]);
            if (SUCCEEDED(hr))
                (*pceltFetched)++;
                
            ILFree(pidl);
        }

        if (S_OK != hr)
            break;
    }

    if (SUCCEEDED(hr))
    {
        hr = *pceltFetched ? S_OK : S_FALSE;
    }
    else
    {
        for (UINT i = 0; i < *pceltFetched; i++)
        {
            ATOMICRELEASE(rgelt[i]);
        }
        *pceltFetched = 0;
    }


    return hr;
}

STDMETHODIMP CShellItemEnum::Skip(ULONG celt)
{
    HRESULT hr = _EnsureEnum();
    if (SUCCEEDED(hr))
        hr = _penum->Skip(celt);

    return hr;
}

STDMETHODIMP CShellItemEnum::Reset()
{
    HRESULT hr = _EnsureEnum();
    if (SUCCEEDED(hr))
        hr = _penum->Reset();

    return hr;
}

STDMETHODIMP CShellItemEnum::Clone(IEnumShellItems **ppenum)
{
    return E_NOTIMPL;
}

HRESULT CShellItemEnum::_EnsureEnum()
{
    if (_penum)
        return S_OK;

    HRESULT hr = E_FAIL;

    if (_psf)
    {
        HWND hwnd = NULL;
        IUnknown_GetWindow(_punkSite, &hwnd);

        // if didn't get an enum in Initialize then enumerate the
        // entire folder.
        hr = _psf->EnumObjects(hwnd, _dwFlags, &_penum);
    }

    return hr;
}


CShellItemEnum::CShellItemEnum() 
        : _cRef(1)
{
    ASSERT(NULL == _psf);
    ASSERT(NULL == _penum);
    ASSERT(NULL == _pidlFolder);
}

STDMETHODIMP CShellItemEnum::Initialize(LPCITEMIDLIST pidlFolder, IShellFolder *psf, DWORD dwFlags, UINT cidl, LPCITEMIDLIST *apidl)
{
    HRESULT hr = E_FAIL;

    _dwFlags = dwFlags;

    _psf = psf;
    _psf->AddRef();

    if (NULL == _pidlFolder)
    {
        hr = SHGetIDListFromUnk(_psf, &_pidlFolder);
    }
    else
    {
        hr = SHILClone(pidlFolder, &_pidlFolder);
    }

    if (SUCCEEDED(hr) && cidl)
    {
        ASSERT(apidl);

        // if want to enum with other flags or combos need to implement the filter
        ASSERT(_dwFlags == (SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN));

        hr = CreateIEnumIDListOnIDLists(apidl, cidl, &_penum);
    }

    // on error let our destructor do the cleanup
    
    return hr;
}

CShellItemEnum::~CShellItemEnum()
{
    ATOMICRELEASE(_penum);
    ATOMICRELEASE(_psf);
    ILFree(_pidlFolder);
}

HRESULT _CreateShellItemEnum(LPCITEMIDLIST pidlFolder,IShellFolder *psf,IBindCtx *pbc, REFGUID rbhid, 
                             UINT cidl, LPCITEMIDLIST *apidl,
                             REFIID riid, void **ppv)
{
    DWORD dwFlags;
    HRESULT hr = E_FAIL;
    LPCITEMIDLIST *pidlEnum = NULL;

    UINT mycidl = 0;
    LPITEMIDLIST *myppidl = NULL;;

    if (IsEqualGUID(rbhid, BHID_StorageEnum))
        dwFlags = SHCONTF_STORAGE;
    else
        dwFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN;

    CShellItemEnum *psie = new CShellItemEnum();

    if (psie)
    {
        hr = psie->Initialize(pidlFolder, psf, dwFlags, cidl, apidl);

        if (SUCCEEDED(hr))
        {
            hr = psie->QueryInterface(riid, ppv);
        }

        psie->Release();
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

HRESULT _CreateEnumHelper(IShellItem *psi, IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppv)
{   
    HRESULT hr = E_FAIL;
    IShellFolder *psf;

    ASSERT(psi);
    
    if (psi)
    {
        hr = psi->BindToHandler(NULL, BHID_SFObject, IID_PPV_ARG(IShellFolder, &psf));

        if (SUCCEEDED(hr))
        {
            hr =  _CreateShellItemEnum(NULL,psf,pbc,rbhid,0,NULL,riid,ppv);
            psf->Release();
        }
    }

    return hr;
}

class CShellItemArray : public IShellItemArray
{
public:
    CShellItemArray();
    ~CShellItemArray();
    HRESULT Initialize(LPCITEMIDLIST pidlParent,IShellFolder *psf,UINT cidl,LPCITEMIDLIST *ppidl);


    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void) ;
    STDMETHODIMP_(ULONG) Release(void);

    // IShellItemArray 
    STDMETHODIMP BindToHandler(
        IBindCtx *pbc, 
        REFGUID rbhid,
        REFIID riid, 
        void **ppvOut);

    STDMETHODIMP GetAttributes(
        SIATTRIBFLAGS dwAttribFlags,
        SFGAOF sfgaoMask, 
        SFGAOF *psfgaoAttribs);

    STDMETHODIMP GetCount(DWORD *pdwNumItems);
    STDMETHODIMP GetItemAt(DWORD dwIndex,IShellItem **ppsi);
    STDMETHODIMP EnumItems(IEnumShellItems **ppenumShellItems);

private:
    HRESULT _CloneIDListArray(UINT cidl, LPCITEMIDLIST *apidl, UINT *pcidl, LPITEMIDLIST **papidl);

    IShellFolder *_pshf;
    LPITEMIDLIST _pidlParent;
    LPITEMIDLIST *_ppidl;
    UINT _cidl;
    LONG _cRef;
    IDataObject *_pdo; // cached data object.
    DWORD _dwAttribAndCacheResults;
    DWORD _dwAttribAndCacheMask;
    DWORD _dwAttribCompatCacheResults;
    DWORD _dwAttribCompatCacheMask;
    BOOL _fItemPidlsRagged; // set to true if have any rugged pidls.
};
                

CShellItemArray::CShellItemArray()
{
    ASSERT(0 == _cidl);
    ASSERT(NULL == _ppidl);
    ASSERT(NULL == _pshf);
    ASSERT(NULL == _pdo);

    _fItemPidlsRagged = TRUE;
    _cRef = 1;
}

CShellItemArray::~CShellItemArray()
{
    ATOMICRELEASE(_pdo);
    ATOMICRELEASE(_pshf);

    ILFree(_pidlParent); // may be null

    if (NULL != _ppidl)
    {
        FreeIDListArray(_ppidl,_cidl);
    }
}

HRESULT CShellItemArray::Initialize(LPCITEMIDLIST pidlParent, IShellFolder *psf, UINT cidl, LPCITEMIDLIST *ppidl)
{
    if ((cidl > 1) && !ppidl || !psf)
    {
        return E_INVALIDARG;
    }

    if (pidlParent)
    {
        _pidlParent = ILClone(pidlParent);  // proceed on alloc failure, just won't use.
    }

    _pshf = psf;
    _pshf->AddRef();

    HRESULT hr = S_OK;
    if (cidl)
    {
        // if there are items then make a copy
        hr = _CloneIDListArray(cidl, ppidl, &_cidl, &_ppidl);
    }

    // on error rely on destructor to do the cleanup
    return hr;
}   

// IUnknown
STDMETHODIMP CShellItemArray::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = 
    {
        QITABENT(CShellItemArray, IShellItemArray),
        { 0 },
    };

    return QISearch(this, qit, riid, ppv);
}

STDMETHODIMP_(ULONG) CShellItemArray::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

STDMETHODIMP_(ULONG) CShellItemArray::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

STDMETHODIMP CShellItemArray::BindToHandler(IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppvOut)
{
    HRESULT hr = E_FAIL;

    if (_pshf)
    {
        // currently only allow bind to IDataObject and
        // cache the result.        
        if (BHID_DataObject == rbhid)
        {
            if (NULL == _pdo)
            {
                _pshf->GetUIObjectOf(NULL, _cidl, (LPCITEMIDLIST *)_ppidl, IID_PPV_ARG_NULL(IDataObject, &_pdo));
            }

            if (_pdo)
            {
                hr = _pdo->QueryInterface(riid, ppvOut);
            }
        }
        else
        {
            hr = E_NOINTERFACE;
        }
    }

    return hr;
}

// This should probably take a flag that does an or'ing of attributes but this
// currrently isn't implemented. Do have comments on what the changes would be.
HRESULT CShellItemArray::GetAttributes(SIATTRIBFLAGS dwAttribFlags, SFGAOF sfgaoMask, SFGAOF *psfgaoAttribs)
{
    DWORD dwAttrib;
    HRESULT hr = E_FAIL;
    
    if (dwAttribFlags > (dwAttribFlags & SIATTRIBFLAGS_MASK))
    {
        ASSERT(dwAttribFlags <= (dwAttribFlags & SIATTRIBFLAGS_MASK));
        return E_INVALIDARG;
    }
    
    if (SIATTRIBFLAGS_OR == dwAttribFlags)
    {
        ASSERT(SIATTRIBFLAGS_OR != dwAttribFlags); // or'ing is currently not implemented.
        return E_INVALIDARG;
    }

    if (_pshf)
    {
        DWORD dwAttribMask = sfgaoMask;
        DWORD *pdwCacheMask = NULL;
        DWORD *pdwCacheResults = NULL;

        // setup to point to proper Cached values.
        switch(dwAttribFlags)
        {
        case SIATTRIBFLAGS_AND:
            pdwCacheMask = &_dwAttribAndCacheMask;
            pdwCacheResults = &_dwAttribAndCacheResults;
            break;
        case SIATTRIBFLAGS_APPCOMPAT:
            pdwCacheMask = &_dwAttribCompatCacheMask;
            pdwCacheResults = &_dwAttribCompatCacheResults;
            break;
        default:
            ASSERT(0); // i don't know how to handle this flag.
            break;
        }

        dwAttribMask &= ~(*pdwCacheMask); // only ask for the bits we don't already have.

        dwAttrib = dwAttribMask;

        if (dwAttrib) 
        {
            if (0 == _cidl)
            { 
                dwAttrib = 0;
            }
            else
            {
                // if know this is not a ragged pidl and calling with the APPCOMPAT flag
                // then calls GetAttributesOf for all the items in one call to the
                // shellFolder.
                    
                if (!_fItemPidlsRagged && (SIATTRIBFLAGS_APPCOMPAT == dwAttribFlags))
                {
                    hr = _pshf->GetAttributesOf(_cidl, (LPCITEMIDLIST *)_ppidl, &dwAttrib);
                }
                else
                {
                    LPITEMIDLIST *pCurItem = _ppidl;
                    UINT itemCount = _cidl;
                    DWORD dwAttribLoopResult = -1; // set all result bits for and, if going to or set to zero

                    while (itemCount--)
                    {
                        DWORD dwAttribTemp = dwAttrib;
                        IShellFolder *psfNew;
                        LPCITEMIDLIST pidlChild;

                        hr = SHBindToFolderIDListParent(_pshf, *pCurItem, IID_PPV_ARG(IShellFolder, &psfNew), &pidlChild);

                        if (SUCCEEDED(hr))
                        {
                            hr = psfNew->GetAttributesOf(1, &pidlChild, &dwAttribTemp);
                            psfNew->Release();
                        }

                        if (FAILED(hr))
                        {
                            break;
                        }

                        dwAttribLoopResult &= dwAttribTemp; // could also do an or'ing here
                        
                        if (0 == dwAttribLoopResult) // if no attribs set and doing an and we can stop.
                        {
                            break;
                        }

                        ++pCurItem;
                    }

                    dwAttrib = dwAttribLoopResult; // update the attrib
                }
            }
        }
        else
        {
            hr = S_OK;
        }

        if (SUCCEEDED(hr))
        {
            // remember those bits that we just got + 
            // those that we computed before
            *pdwCacheResults = dwAttrib | (*pdwCacheResults & *pdwCacheMask);

            // we know these are now valid, keep track of those +
            // if they gave us more than we asked for, cache them too
            *pdwCacheMask |= dwAttribMask | dwAttrib;

            // don't return anything that wasn't asked for. defview code relies on this.
            *psfgaoAttribs = (*pdwCacheResults & sfgaoMask); 
        }
    }

    return hr;
}

STDMETHODIMP CShellItemArray::GetCount(DWORD *pdwNumItems)
{
    *pdwNumItems = _cidl;
    return S_OK;
}

// way to get zero based index ShellItem without having to
// go through enumerator overhead.
STDMETHODIMP CShellItemArray::GetItemAt(DWORD dwIndex, IShellItem **ppsi)
{
    *ppsi = NULL;

    if (dwIndex >= _cidl)
    {
        return E_FAIL;
    }
    
    ASSERT(_ppidl);

    LPITEMIDLIST pidl = *(_ppidl + dwIndex);

    // if GetItemAt is called a lot may want to
    // a) get the pshf pidl to pass to SHCreateshellItem so doesn't have to create each time
    // b) see if always asking for first item and is so maybe cache the shellItem
    return SHCreateShellItem(NULL, _pshf, pidl, ppsi);
}

STDMETHODIMP CShellItemArray::EnumItems(IEnumShellItems **ppenumShellItems)
{
    return _CreateShellItemEnum(_pidlParent, _pshf, NULL, GUID_NULL, _cidl, 
        (LPCITEMIDLIST *) _ppidl, IID_PPV_ARG(IEnumShellItems, ppenumShellItems));
}

HRESULT CShellItemArray::_CloneIDListArray(UINT cidl, LPCITEMIDLIST *apidl, UINT *pcidl, LPITEMIDLIST **papidl)
{
    HRESULT hr;
    LPITEMIDLIST *ppidl;

    *papidl = NULL;

    _fItemPidlsRagged = FALSE;

    if (cidl && apidl)
    {
        ppidl = (LPITEMIDLIST *)LocalAlloc(LPTR, cidl * sizeof(*ppidl));
        if (ppidl)
        {
            LPITEMIDLIST *apidlFrom = (LPITEMIDLIST *) apidl;
            LPITEMIDLIST *apidlTo = ppidl;

            hr = S_OK;
            for (UINT i = 0; i < cidl ; i++)
            {
                hr = SHILClone(*apidlFrom, apidlTo);
                if (FAILED(hr))
                {
                    FreeIDListArray(ppidl, i);
                    ppidl = NULL;
                    break;
                }
                
                // if more than one item in list then set singeItemPidls to false
                if (!ILIsEmpty(_ILNext(*apidlTo)))
                {
                    _fItemPidlsRagged = TRUE;
                }

                ++apidlFrom;
                ++apidlTo;
            }   
        }
        else
            hr = E_OUTOFMEMORY;
    }
    else
    {
        ppidl = NULL;
        hr = S_FALSE;   // success by empty
    }

    if (SUCCEEDED(hr))
    {
        *papidl = ppidl;
        *pcidl = cidl;
    }
    else
    {
        _fItemPidlsRagged = TRUE;
    }
    return hr;
}

SHSTDAPI SHCreateShellItemArray(LPCITEMIDLIST pidlParent, IShellFolder *psf, UINT cidl,
                                LPCITEMIDLIST *ppidl, IShellItemArray **ppsiItemArray)
{
    HRESULT hr = E_OUTOFMEMORY;
    CShellItemArray *pItemArray = new CShellItemArray();
    if (pItemArray)
    {
        hr = pItemArray->Initialize(pidlParent, psf, cidl, ppidl);
        if (FAILED(hr))
        {
            pItemArray->Release();
            pItemArray = NULL;
        }
    }
    *ppsiItemArray = pItemArray;
    return hr;
}