//
// Wrapper functions for shell interfaces
//
//  Many ISVs mess up various IShellFolder methods, so we centralize the
//  workarounds so everybody wins.
//
//  Someday, IExtractIcon and IShellLink wrappers may also be added, should
//  the need arise.
//

#include "priv.h"
#include <shlobj.h>

//----------------------------------------------------------------------------
//
//  IShellFolder::GetDisplayNameOf was not very well documented.  Lots of
//  people don't realize that the SHGDN values are flags, so they use
//  equality tests instead of bit tests.  So whenever we add a new flag,
//  these people say "Huh?  I don't understand."  So we have to keep
//  retrying with fewer and fewer flags until finally they get something
//  they like.  SHGDN_FORPARSING has the opposite problem:  Some people
//  demand that the flag be set.

//
//  This array lists the things we try to do to get the uFlags into a state
//  that the app will eventually like.
//
//  We walk through the list and do this:
//
//      uFlags = (uFlags & AND) | OR
//
//  Most of the time, the entry will turn off a bit in the uFlags, but
//  SHGDN_FORPARSING is weird and it's a flag you actually want to turn on
//  instead of off.
//

typedef struct GDNCOMPAT {
    DWORD   dwAnd;
    DWORD   dwOr;
    DWORD   dwAllow;                    // flag to allow this rule to fire
} GDNCOMPAT;

#define GDNADDFLAG(f)   ~0, f           // Add a flag to uFlags
#define GDNDELFLAG(f)   ~f, 0           // Remove a flag from uFlags

#define ISHGDN2_CANREMOVEOTHERFLAGS 0x80000000

GDNCOMPAT c_gdnc[] = {
  { GDNDELFLAG(SHGDN_FOREDITING),       ISHGDN2_CANREMOVEOTHERFLAGS },  // Some apps don't like this flag
  { GDNDELFLAG(SHGDN_FORADDRESSBAR),    ISHGDN2_CANREMOVEOTHERFLAGS },  // Some apps don't like this flag
  { GDNADDFLAG(SHGDN_FORPARSING),       ISHGDN2_CANREMOVEOTHERFLAGS },  // Some apps require this flag
  { GDNDELFLAG(SHGDN_FORPARSING),       ISHGDN2_CANREMOVEFORPARSING },  // And others don't like it
  { GDNDELFLAG(SHGDN_INFOLDER),         ISHGDN2_CANREMOVEOTHERFLAGS },  // Desperation - remove this flag too
};

//
//  These are the return values we tend to get back when people see
//  flags they don't like.
//
BOOL __inline IsBogusHRESULT(HRESULT hres)
{
    return  hres == E_FAIL ||
            hres == E_INVALIDARG ||
            hres == E_NOTIMPL;
}

//
//  dwFlags2 controls how aggressively we try to find a working display name.
//
//  ISHGDN2_CANREMOVEFORPARSING
//      Normally, we do not turn off the SHGDN_FORPARSING flag because
//      if a caller asks for the parse name, it probably really wants the
//      parse name.  This flag indicates that we are allowed to turn off
//      SHGDN_FORPARSING if we think it'll help.
//

STDAPI IShellFolder_GetDisplayNameOf(
    IShellFolder *psf,
    LPCITEMIDLIST pidl,
    DWORD uFlags,
    LPSTRRET lpName,
    DWORD dwFlags2)
{
    HRESULT hres;

    hres = psf->GetDisplayNameOf(pidl, uFlags, lpName);
    if (!IsBogusHRESULT(hres))
        return hres;

    int i;
    DWORD uFlagsOrig = uFlags;

    //
    //  If the caller didn't pass SHGDN_FORPARSING, then clearly it's
    //  safe to remove it.
    //
    if (!(uFlags & SHGDN_FORPARSING)) {
        dwFlags2 |= ISHGDN2_CANREMOVEFORPARSING;
    }

    // We can always remove other flags.
    dwFlags2 |= ISHGDN2_CANREMOVEOTHERFLAGS;

    for (i = 0; i < ARRAYSIZE(c_gdnc); i++)
    {
        if (c_gdnc[i].dwAllow & dwFlags2)
        {
            DWORD uFlagsNew = (uFlags & c_gdnc[i].dwAnd) | c_gdnc[i].dwOr;
            if (uFlagsNew != uFlags)
            {
                uFlags = uFlagsNew;
                hres = psf->GetDisplayNameOf(pidl, uFlags, lpName);
                if (!IsBogusHRESULT(hres))
                    return hres;
            }
        }
    }

    // By now, we should've removed all the flags, except perhaps for
    // SHGDN_FORPARSING.
    if (dwFlags2 & ISHGDN2_CANREMOVEFORPARSING) {
        ASSERT(uFlags == SHGDN_NORMAL);
    } else {
        ASSERT(uFlags == SHGDN_NORMAL || uFlags == SHGDN_FORPARSING);
    }

    return hres;
}

//----------------------------------------------------------------------------
//
//  The documentation on IShellFolder::ParseDisplayName wasn't clear that
//  pchEaten and pdwAttributes can be NULL, and some people dereference
//  them unconditionally.  So make sure it's safe to dereference them.
//
//  It is also popular to forget to set *ppidl=NULL on failure, so we null
//  it out here.
//
//  We request no attributes, so people who aren't buggy won't go out of
//  their way trying to retrieve expensive attributes.
//

STDAPI IShellFolder_ParseDisplayName(
    IShellFolder *psf,
    HWND hwnd,
    LPBC pbc,
    LPOLESTR pszDisplayName,
    ULONG *pchEaten,
    LPITEMIDLIST *ppidl,
    ULONG *pdwAttributes)
{
    ULONG cchEaten;
    ULONG dwAttributes = 0;

    if (pchEaten == NULL)
        pchEaten = &cchEaten;
    if (pdwAttributes == NULL)
        pdwAttributes = &dwAttributes;

    if (ppidl)
        *ppidl = NULL;

    return psf->ParseDisplayName(hwnd, pbc, pszDisplayName, pchEaten, ppidl, pdwAttributes);
}

STDAPI IShellFolder_CompareIDs(IShellFolder *psf, LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
{
    // We have new bits here...
    if (lParam & ~SHCIDS_COLUMNMASK)
    {
        IShellFolder2* psf2;
        if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(IShellFolder2, &psf2))))
        {
            psf2->Release();
        }
        else
        {
            // But we can't send them to legacy IShellFolder implementations
            lParam &= SHCIDS_COLUMNMASK;
        }
    }

    return psf->CompareIDs(lParam, pidl1, pidl2);
}


//----------------------------------------------------------------------------
//
//  IShellFolder::EnumObjects
//
CLSID CLSID_ZipFolder =
{ 0xe88dcce0, 0xb7b3, 0x11d1, { 0xa9, 0xf0, 0x00, 0xaa, 0x00, 0x60, 0xfa, 0x31 } };

STDAPI IShellFolder_EnumObjects(
    IShellFolder *psf,
    HWND hwnd,
    DWORD grfFlags,
    IEnumIDList **ppenumIDList)
{
    if (hwnd == NULL || hwnd == GetDesktopWindow())
    {
        //  The first parameter to EnumObjects is supposed to be the window
        //  on which to parent UI, or NULL for no UI, or GetDesktopWindow()
        //  for "parentless UI".
        //
        //  Win98 Plus! Zip Folders takes the hwnd and uses it as the basis
        //  for a search for a rebar window, since they (for some bizarre
        //  reason) want to hide the address bar when an enumeration starts.
        //
        //  We used to pass NULL or GetDesktopWindow(), but this caused zip
        //  folders to start searching from the desktop, which means that
        //  it eventually finds the taskbar and tries to send it
        //  inter-process rebar messages, which causes the shell to fault.
        //
        //  When we discover we are about to pass NULL to Zip Folders,
        //  we change it to HWND_BOTTOM.  This is not a valid window handle,
        //  which causes Zip Folders' search to bail out quickly and it ends
        //  up not killing anyone.
        //

        CLSID clsid;
        if (SUCCEEDED(IUnknown_GetClassID(psf, &clsid)) &&
            IsEqualCLSID(clsid, CLSID_ZipFolder))
            hwnd = HWND_BOTTOM;
    }

    return psf->EnumObjects(hwnd, grfFlags, ppenumIDList);
}
