#include "priv.h"
#include "browmenu.h"
#include "resource.h"
#include "uemapp.h"
#include "mluisupp.h"
#include <varutil.h>
#include "legacy.h"

#define UEM_NEWITEMCOUNT 2
// Exported by shdocvw
STDAPI GetLinkInfo(IShellFolder* psf, LPCITEMIDLIST pidlItem, BOOL* pfAvailable, BOOL* pfSticky);

#define REG_STR_MAIN TEXT("SOFTWARE\\Microsoft\\Internet Explorer\\Main")

BOOL AreIntelliMenusEnbaled()
{
    // This is only garenteed to work on version 5 shell because the session 
    // incrementer is located in the tray
    if (GetUIVersion() >= 5)
    {
        DWORD dwRest = SHRestricted(REST_INTELLIMENUS);
        if (dwRest != RESTOPT_INTELLIMENUS_USER)
            return (dwRest == RESTOPT_INTELLIMENUS_ENABLED);

        return SHRegGetBoolUSValue(REG_STR_MAIN, TEXT("FavIntelliMenus"),
                                   FALSE, FALSE); // Don't ignore HKCU, Disable Menus by default
    }
    else
        return FALSE;
}


CFavoritesCallback::CFavoritesCallback() : _cRef(1)
{
    _fOffline = BOOLIFY(SHIsGlobalOffline());
}

CFavoritesCallback::~CFavoritesCallback()
{
    ASSERT(_punkSite == NULL);

    ASSERT(_psmFavCache == NULL);
}

/*----------------------------------------------------------
Purpose: IUnknown::QueryInterface method

*/
STDMETHODIMP CFavoritesCallback::QueryInterface (REFIID riid, LPVOID * ppvObj)
{
    static const QITAB qit[] = 
    {
        QITABENT(CFavoritesCallback, IShellMenuCallback),
        QITABENT(CFavoritesCallback, IObjectWithSite),
        { 0 },
    };

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


/*----------------------------------------------------------
Purpose: IUnknown::AddRef method

*/
STDMETHODIMP_(ULONG) CFavoritesCallback::AddRef ()
{
    return ++_cRef;
}

/*----------------------------------------------------------
Purpose: IUnknown::Release method

*/
STDMETHODIMP_(ULONG) CFavoritesCallback::Release()
{
    ASSERT(_cRef > 0);
    _cRef--;

    if( _cRef > 0)
        return _cRef;

    delete this;
    return 0;
}

/*----------------------------------------------------------
Purpose: IObjectWithSite::SetSite method

*/
STDMETHODIMP CFavoritesCallback::SetSite(IUnknown* punk)
{
    ATOMICRELEASE(_punkSite);
    _punkSite = punk;
    if (_punkSite)
    {
        _punkSite->AddRef();
    }
    else if (_psmFavCache)
    {
        // Since the top level menu is being destroyed, they are removing
        // our site. We should cleanup.
        DWORD dwFlags;
        UINT uId;
        UINT uIdA;

        _psmFavCache->GetMenuInfo(NULL, &uId, &uIdA, &dwFlags);

        // Tell menuband we're no longer caching it. We need to do this so ClowseDW
        // cleans up the menus.
        dwFlags &= ~SMINIT_CACHED;
        _psmFavCache->Initialize(NULL, uId, uIdA, dwFlags); 

        IDeskBand* pdesk;
        if (SUCCEEDED(_psmFavCache->QueryInterface(IID_IDeskBand, (LPVOID*)&pdesk)))
        {
            pdesk->CloseDW(0);
            pdesk->Release();
        }

        ATOMICRELEASE(_psmFavCache);
    }

    return NOERROR;

}

/*----------------------------------------------------------
Purpose: IShellMenuCallback::CallbackSM method

*/
STDMETHODIMP CFavoritesCallback::CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HRESULT hres = S_FALSE;
    switch (uMsg)
    {
    case SMC_INITMENU:
        hres = _Init(psmd->hmenu, psmd->uIdParent, psmd->punk);
        break;

    case SMC_EXITMENU:
        hres = _Exit();
        break;

    case SMC_CREATE:
        if (psmd->uIdParent == FCIDM_MENU_FAVORITES)
            _fExpandoMenus = AreIntelliMenusEnbaled();
        break;

     case SMC_DEMOTE:
         hres = _Demote(psmd);
         break;
 
     case SMC_PROMOTE:
         hres = _Promote(psmd);
         break;
 
     case SMC_NEWITEM:
         hres = _HandleNew(psmd);
         break;

    case SMC_SFEXEC:
        hres = SHNavigateToFavorite(psmd->psf, psmd->pidlItem, _punkSite, SBSP_DEFBROWSER | SBSP_DEFMODE);
        break;

    case SMC_GETINFO:
        hres = _GetHmenuInfo(psmd->hmenu, psmd->uId, (SMINFO*)lParam);
        break;

    case SMC_SFSELECTITEM:
        hres = _SelectItem(psmd->pidlFolder, psmd->pidlItem);
        break;

    case SMC_GETOBJECT:
        hres = _GetObject(psmd, (GUID)*((GUID*)wParam), (void**)lParam);
        break;

    case SMC_DEFAULTICON:
        hres = _GetDefaultIcon((LPTSTR)wParam, (int*)lParam);
        break;

    case SMC_GETSFINFO:
        hres = _GetSFInfo(psmd, (SMINFO*)lParam);
        break;

    case SMC_SHCHANGENOTIFY:
        {
            PSMCSHCHANGENOTIFYSTRUCT pshf = (PSMCSHCHANGENOTIFYSTRUCT)lParam;
            hres = _ProcessChangeNotify(psmd, pshf->lEvent, pshf->pidl1, pshf->pidl2);
        }
        break;

    case SMC_REFRESH:
        _fExpandoMenus = AreIntelliMenusEnbaled();
        break;

    case SMC_CHEVRONGETTIP:
        hres = _GetTip((LPTSTR)wParam, (LPTSTR)lParam);
        break;

    case SMC_CHEVRONEXPAND:
        {
            if (_fShowingTip)
            {
                LPTSTR pszExpanded = TEXT("NO");

                SHRegSetUSValue(REG_STR_MAIN, TEXT("FavChevron"),
                    REG_SZ, pszExpanded, lstrlen(pszExpanded) * sizeof(TCHAR), SHREGSET_FORCE_HKCU);
            }

            _fShowingTip = FALSE;

            hres = S_OK;
        }
        break;

    case SMC_DISPLAYCHEVRONTIP:

        // Should we show the tip?
        _fShowingTip = SHRegGetBoolUSValue(REG_STR_MAIN, TEXT("FavChevron"), FALSE, TRUE);    // Default to YES.

        if (_fShowingTip)
        {
            hres = S_OK;
        }
        break;

    case SMC_SFDDRESTRICTED:
        hres = _AllowDrop((IDataObject*)wParam, (HWND)lParam) ? S_FALSE : S_OK;
        break;
    }

    return hres;
}


HRESULT CFavoritesCallback::_Init(HMENU hMenu, UINT uIdParent, IUnknown* punk)
{
#ifdef DEBUG
    if (GetAsyncKeyState(VK_SHIFT) < 0)
    {
        UEMFireEvent(&UEMIID_BROWSER, UEME_CTLSESSION, UEMF_XEVENT, TRUE, -1);
    }
#endif

    HRESULT hres = S_FALSE;

    if (SUCCEEDED(IUnknown_QueryServiceExec(_punkSite, SID_STopLevelBrowser, &CGID_MenuBand, MBANDCID_ENTERMENU, 0, NULL, NULL)))
        hres = S_OK;

    // Only do this for the favorites dropdown. This was causing 
    // the chevron menu to be invalidated before it was created. This caused some
    // resize problems because the metrics were unavailable.
    if (uIdParent == FCIDM_MENU_FAVORITES)
    {
        // If we switched between online and offline, we need to re-init the menu
        BOOL fOffline = BOOLIFY(SHIsGlobalOffline());
        if (fOffline ^ _fOffline || _fRefresh)
        {
            _fOffline = fOffline;
            IShellMenu* psm;
            if (SUCCEEDED(punk->QueryInterface(IID_IShellMenu, (void**)&psm)))
            {
                psm->InvalidateItem(NULL, SMINV_REFRESH);
                psm->Release();
            }
            _fRefresh = FALSE;
        }
    }
    return hres;
}


HRESULT CFavoritesCallback::_Exit()
{
    HRESULT hr = IUnknown_QueryServiceExec(_punkSite, SID_STopLevelBrowser, &CGID_MenuBand, MBANDCID_EXITMENU, 0, NULL, NULL);

    return SUCCEEDED(hr) ? S_OK : S_FALSE;
}

HRESULT CFavoritesCallback::_GetHmenuInfo(HMENU hMenu, UINT uId, SMINFO* psminfo)
{
    if (uId == FCIDM_MENU_FAVORITES)
    {
        if (psminfo->dwMask & SMIM_FLAGS)
            psminfo->dwFlags |= SMIF_DROPCASCADE;
    }
    else
    {
        if (psminfo->dwMask & SMIM_FLAGS)
            psminfo->dwFlags |= SMIF_TRACKPOPUP;
    }

    // No item has icons
    if (psminfo->dwMask & SMIM_ICON)
        psminfo->iIcon = -1;
    
    return S_OK;
}


HRESULT CFavoritesCallback::_GetSFInfo(SMDATA* psmd, SMINFO* psminfo)
{
    BOOL fAvailable;

    //
    // If we are offline and the item is not available, we set the
    // SMIF_ALTSTATE so that the menu item is greyed
    //
    if (psminfo->dwMask & SMIM_FLAGS)
    {
        if (_fOffline &&
            SUCCEEDED(GetLinkInfo(psmd->psf, psmd->pidlItem, &fAvailable, NULL)) &&
            fAvailable == FALSE)
        {
            // Not available, so grey the item
            psminfo->dwFlags |= SMIF_ALTSTATE;
        }

        if (_fExpandoMenus)
            psminfo->dwFlags |= _GetDemote(psmd);
    }
    return S_OK;
}

HRESULT CFavoritesCallback::_SelectItem(LPCITEMIDLIST pidlFolder, LPCITEMIDLIST pidl)
{
    HRESULT hres = S_FALSE;
    LPITEMIDLIST pidlFull = ILCombine(pidlFolder, pidl);
    if (pidlFull)
    {
        VARIANTARG vargIn;
        hres = InitVariantFromIDList(&vargIn, pidlFull);
        if (SUCCEEDED(hres))
        {
            hres = IUnknown_QueryServiceExec(_punkSite, SID_SMenuBandHandler,
                &CGID_MenuBandHandler, MBHANDCID_PIDLSELECT, 0, &vargIn, NULL);
            VariantClearLazy(&vargIn);
        }
        ILFree(pidlFull);
    }
    return hres;
}

void CFavoritesCallback::_RefreshItem(HMENU hmenu, int idCmd, IShellMenu* psm)
{
    SMDATA smd;
    smd.dwMask = SMDM_HMENU;
    smd.hmenu = hmenu;
    smd.uId = idCmd;

    psm->InvalidateItem(&smd, SMINV_ID | SMINV_REFRESH);
}

HRESULT CFavoritesCallback::_GetObject(LPSMDATA psmd, REFIID riid, void** ppvOut)
{
    HRESULT hres = S_FALSE;
    *ppvOut = NULL;

    if (IsEqualIID(IID_IShellMenu, riid))
    {
        if (psmd->uId == FCIDM_MENU_FAVORITES)
        {
            // Do we have a cached Favorites menu?
            if (_psmFavCache)
            {
                // Yes we do, return it
                _psmFavCache->AddRef();
                *ppvOut = (LPVOID)_psmFavCache;
                hres = S_OK;
            }
            else
            {
                // Nope; We need to create one...
                hres = CoCreateInstance(CLSID_MenuBand, NULL, CLSCTX_INPROC, 
                    IID_IShellMenu, (void**)&_psmFavCache);

                if (SUCCEEDED(hres))
                {
                    HMENU hmenu = NULL;
                    HWND hwnd;

                    _psmFavCache->Initialize(this, FCIDM_MENU_FAVORITES, ANCESTORDEFAULT, 
                        SMINIT_CACHED | SMINIT_VERTICAL); 

                    // We need to grab the Top HMENU portion of the Favorites menu from the current band
                    IShellMenu* psm;
                    if (SUCCEEDED(psmd->punk->QueryInterface(IID_IShellMenu, (LPVOID*)&psm)))
                    {
                        psm->GetMenu(&hmenu, &hwnd, NULL);

                        hmenu = GetSubMenu(hmenu, GetMenuPosFromID(hmenu, FCIDM_MENU_FAVORITES));

                        // Delete the placeholder item (there to keep the separator from getting
                        // lost during shbrowse menu merging, which deletes trailing separators).
                        int iPos = GetMenuPosFromID(hmenu, FCIDM_FAVPLACEHOLDER);
                        if (iPos >= 0)
                            DeleteMenu(hmenu, iPos, MF_BYPOSITION);

                        psm->Release();
                    }

                    if (hmenu)
                    {
                        hres = _psmFavCache->SetMenu(hmenu, hwnd, SMSET_TOP | SMSET_DONTOWN);
                    }
 
                    LPITEMIDLIST pidlFav;
                    if (SUCCEEDED(hres) &&
                        SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_FAVORITES, &pidlFav)))
                    {
                        IShellFolder* psf;
                        if (SUCCEEDED(IEBindToObject(pidlFav, &psf)))
                        {
                            HKEY hMenuKey;
                            DWORD dwDisp;

                            RegCreateKeyEx(HKEY_CURRENT_USER, STRREG_FAVORITES, NULL, NULL,
                                REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE,
                                NULL, &hMenuKey, &dwDisp);

                            hres = _psmFavCache->SetShellFolder(psf, pidlFav, hMenuKey, 
                                SMSET_BOTTOM | SMSET_USEBKICONEXTRACTION | SMSET_HASEXPANDABLEFOLDERS);
                            psf->Release();
                        }
                        ILFree(pidlFav);
                    }

                    if (SUCCEEDED(hres))
                    {
                        _psmFavCache->AddRef(); // We're caching this.
                        *ppvOut = _psmFavCache;
                    }
                }
            }
        }
    }
    else if (IsEqualIID(IID_IShellMenuCallback, riid))
    {
        IShellMenuCallback* psmcb = (IShellMenuCallback*) new CFavoritesCallback;

        if (psmcb)
        {
            *ppvOut = (LPVOID)psmcb;
            hres = S_OK;
        }
    }

    return hres;
}


// Short circuit the looking up of a default icon. We're going to assume that all of them
// are URLs, even folders, for the sake of speed. It gives the user feedback directly, then
// we asyncronously render the real icons.
HRESULT CFavoritesCallback::_GetDefaultIcon(TCHAR* psz, int* piIndex)
{
    HRESULT hr;
    DWORD cchSize = MAX_PATH;
    
    if (SUCCEEDED(hr = AssocQueryString(0, ASSOCSTR_DEFAULTICON, TEXT("InternetShortcut"), NULL, psz, &cchSize)))
        *piIndex = PathParseIconLocation(psz);
        
    return hr;
}

DWORD CFavoritesCallback::_GetDemote(SMDATA* psmd)
{
    UEMINFO uei;
    DWORD dwFlags = 0;
    if (_fExpandoMenus)
    {
        uei.cbSize = SIZEOF(uei);
        uei.dwMask = UEIM_HIT;
        if (SUCCEEDED(UEMQueryEvent(&UEMIID_BROWSER, UEME_RUNPIDL, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem, &uei)))
        {
            if (uei.cHit == 0) 
            {
                dwFlags |= SMIF_DEMOTED;
            }
        }
    }

    return dwFlags;
}

HRESULT CFavoritesCallback::_Demote(LPSMDATA psmd)
{
    HRESULT hres = S_FALSE;

    if (_fExpandoMenus)
    {
        UEMINFO uei;
        uei.cbSize = SIZEOF(uei);
        uei.dwMask = UEIM_HIT;
        uei.cHit = 0;
        hres = UEMSetEvent(&UEMIID_BROWSER, UEME_RUNPIDL, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem, &uei);
    }
    return hres;
}

HRESULT CFavoritesCallback::_Promote(LPSMDATA psmd)
{
    if (_fExpandoMenus) 
    {
        UEMFireEvent(&UEMIID_BROWSER, UEME_RUNPIDL, UEMF_XEVENT, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem);
    }
    return S_OK;
}

HRESULT CFavoritesCallback::_HandleNew(LPSMDATA psmd)
{
    HRESULT hres = S_FALSE;
    if (_fExpandoMenus)
    {
        UEMINFO uei;
        uei.cbSize = SIZEOF(uei);
        uei.dwMask = UEIM_HIT;
        uei.cHit = UEM_NEWITEMCOUNT;
        hres = UEMSetEvent(&UEMIID_BROWSER, UEME_RUNPIDL, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem, &uei);
    }

    return hres;
}

HRESULT CFavoritesCallback::_GetTip(LPTSTR pstrTitle, LPTSTR pstrTip)
{
    MLLoadString(IDS_CHEVRONTIPTITLE, pstrTitle, MAX_PATH);
    MLLoadString(IDS_CHEVRONTIP, pstrTip, MAX_PATH);

    // Why would this fail?
    if (EVAL(pstrTitle[0] != TEXT('\0') && pstrTip[0] != TEXT('\0')))
        return S_OK;

    return S_FALSE;
}

// There is a duplicate of this helper in shell32\unicpp\startmnu.cpp
//                   When modifying this, rev that one as well.
void UEMRenamePidl(const GUID *pguidGrp1, IShellFolder* psf1, LPCITEMIDLIST pidl1,
                   const GUID *pguidGrp2, IShellFolder* psf2, LPCITEMIDLIST pidl2)
{
    UEMINFO uei;
    uei.cbSize = SIZEOF(uei);
    uei.dwMask = UEIM_HIT | UEIM_FILETIME;
    if (SUCCEEDED(UEMQueryEvent(pguidGrp1, 
                                UEME_RUNPIDL, (WPARAM)psf1, 
                                (LPARAM)pidl1, &uei)) &&
                                uei.cHit > 0)
    {
        UEMSetEvent(pguidGrp2, 
            UEME_RUNPIDL, (WPARAM)psf2, (LPARAM)pidl2, &uei);

        uei.cHit = 0;
        UEMSetEvent(pguidGrp1, 
            UEME_RUNPIDL, (WPARAM)psf1, (LPARAM)pidl1, &uei);
    }
}

// There is a duplicate of this helper in shell32\unicpp\startmnu.cpp
//                   When modifying this, rev that one as well.
void UEMDeletePidl(const GUID *pguidGrp, IShellFolder* psf, LPCITEMIDLIST pidl)
{
    UEMINFO uei;
    uei.cbSize = SIZEOF(uei);
    uei.dwMask = UEIM_HIT;
    uei.cHit = 0;
    UEMSetEvent(pguidGrp, UEME_RUNPIDL, (WPARAM)psf, (LPARAM)pidl, &uei);
}

HRESULT CFavoritesCallback::_ProcessChangeNotify(SMDATA* psmd, LONG lEvent, 
                                                 LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
{
    switch (lEvent)
    {
    case SHCNE_RENAMEFOLDER:
    case SHCNE_RENAMEITEM:
        {
            LPITEMIDLIST pidlFavorites;
            if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_FAVORITES, &pidlFavorites)))
            {
                if (ILIsParent(pidlFavorites, pidl1, FALSE))
                {
                    IShellFolder* psfFrom;
                    LPCITEMIDLIST pidlFrom;
                    if (SUCCEEDED(IEBindToParentFolder(pidl1, &psfFrom, &pidlFrom)))
                    {
                        if (ILIsParent(pidlFavorites, pidl2, FALSE))
                        {
                            IShellFolder* psfTo;
                            LPCITEMIDLIST pidlTo;

                            if (SUCCEEDED(IEBindToParentFolder(pidl2, &psfTo, &pidlTo)))
                            {
                                // Then we need to rename it
                                UEMRenamePidl(&UEMIID_BROWSER, psfFrom, pidlFrom, 
                                              &UEMIID_BROWSER, psfTo, pidlTo);
                                psfTo->Release();
                            }
                        }
                        else
                        {
                            // Otherwise, we delete it.
                            UEMDeletePidl(&UEMIID_BROWSER, psfFrom, pidlFrom);
                        }

                        psfFrom->Release();
                    }
                }

                ILFree(pidlFavorites);
            }
        }
        break;

    case SHCNE_DELETE:
    case SHCNE_RMDIR:
        {
            IShellFolder* psf;
            LPCITEMIDLIST pidl;

            if (SUCCEEDED(IEBindToParentFolder(pidl1, &psf, &pidl)))
            {
                UEMDeletePidl(&UEMIID_BROWSER, psf, pidl);
                psf->Release();
            }

        }
        break;

    case SHCNE_CREATE:
    case SHCNE_MKDIR:
        {
            IShellFolder* psf;
            LPCITEMIDLIST pidl;

            if (SUCCEEDED(IEBindToParentFolder(pidl1, &psf, &pidl)))
            {
                UEMINFO uei;
                uei.cbSize = SIZEOF(uei);
                uei.dwMask = UEIM_HIT;
                uei.cHit = UEM_NEWITEMCOUNT;
                UEMSetEvent(&UEMIID_BROWSER, 
                    UEME_RUNPIDL, (WPARAM)psf, (LPARAM)pidl, &uei);
            }

        }
        break;
    case SHCNE_EXTENDED_EVENT:
        {
            // We get this event when we are offline and the cache was changed.
            // We need to refresh the favorites menu when we next show it so the 
            // correct items are greyed.

            SHChangeDWORDAsIDList UNALIGNED * pdwidl = (SHChangeDWORDAsIDList UNALIGNED *)pidl1;

            int iEvent = pdwidl->dwItem1;

            if (iEvent == SHCNEE_WININETCHANGED &&
                (pdwidl->dwItem2 & (CACHE_NOTIFY_ADD_URL |
                        CACHE_NOTIFY_DELETE_URL |   
                        CACHE_NOTIFY_DELETE_ALL |
                        CACHE_NOTIFY_URL_SET_STICKY |
                        CACHE_NOTIFY_URL_UNSET_STICKY)))
            {
                _fRefresh = TRUE;
            }
        }
        break;
    }

    return S_FALSE;
}

//
// _Disallow drop returns S_OK if the drop shold not be allowed.  S_FALSE if
// the drop should be allowed.
//
BOOL CFavoritesCallback::_AllowDrop(IDataObject* pIDataObject, HWND hwnd)
{
    ASSERT(NULL == hwnd || IsWindow(hwnd));

    BOOL fRet = True;  // Allow drop.

    if (hwnd && pIDataObject)
    {
        LPITEMIDLIST pidl;

        if (SUCCEEDED(SHPidlFromDataObject(pIDataObject, &pidl, NULL, 0)))
        {
            fRet = IEIsLinkSafe(hwnd, pidl, ILS_ADDTOFAV);
            ILFree(pidl);
        }
    }

    return fRet;
}
