#include "shellprv.h"
#pragma  hdrstop

#include "ids.h"
#include "defview.h"
#include "datautil.h"
#include <cowsite.h>    // base class for IObjectWithSite
#include "idlcomm.h"

// shlexec.c
STDAPI_(BOOL) DoesAppWantUrl(LPCTSTR pszFullPathToApp);


// drop target impl for .exe files


class CExeDropTarget : public IDropTarget, IPersistFile, CObjectWithSite
{
public:
    // IUnknown
    STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    // IDropTarget
    STDMETHODIMP DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
    STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
    STDMETHODIMP DragLeave();
    STDMETHODIMP Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);

    // IPersist
    STDMETHOD(GetClassID)(CLSID *pClassID);

    // IPersistFile
    STDMETHOD(IsDirty)(void);
    STDMETHOD(Load)(LPCOLESTR pszFileName, DWORD dwMode);
    STDMETHOD(Save)(LPCOLESTR pszFileName, BOOL fRemember);
    STDMETHOD(SaveCompleted)(LPCOLESTR pszFileName);
    STDMETHOD(GetCurFile)(LPOLESTR *ppszFileName);

    // IObjectWithSite
    // STDMETHOD(SetSite)(IUnknown *punkSite);
    // STDMETHOD(GetSite)(REFIID riid, void **ppvSite);

    CExeDropTarget();

private:
    ~CExeDropTarget();
    void _FillSEIFromLinkSite(SHELLEXECUTEINFO *pei);
    void _CleanupSEIFromLinkSite(SHELLEXECUTEINFO *pei);

    LONG _cRef;
    DWORD _dwEffectLast;
    DWORD _grfKeyStateLast;
    TCHAR _szFile[MAX_PATH];
};

CExeDropTarget::CExeDropTarget() : _cRef(1)
{
}

CExeDropTarget::~CExeDropTarget()
{
}

STDMETHODIMP CExeDropTarget::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = {
        QITABENT(CExeDropTarget, IDropTarget),
        QITABENT(CExeDropTarget, IPersistFile), 
        QITABENTMULTI(CExeDropTarget, IPersist, IPersistFile),
        QITABENT(CExeDropTarget, IObjectWithSite),              // IID_IObjectWithSite
        { 0 },
    };
    return QISearch(this, qit, riid, ppv);
}

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

STDMETHODIMP_(ULONG) CExeDropTarget::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;
    delete this;
    return 0;
}

STDMETHODIMP CExeDropTarget::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
    FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
    if ((S_OK == pdtobj->QueryGetData(&fmte)) ||
        (S_OK == DataObj_GetShellURL(pdtobj, NULL, NULL)))
    {
        *pdwEffect &= (DROPEFFECT_COPY | DROPEFFECT_LINK);
    }
    else
        *pdwEffect = 0;

    _dwEffectLast = *pdwEffect;
    _grfKeyStateLast = grfKeyState;
    return S_OK;
}

STDMETHODIMP CExeDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
    *pdwEffect = _dwEffectLast;
    _grfKeyStateLast = grfKeyState;
    return S_OK;
}

STDMETHODIMP CExeDropTarget::DragLeave()
{
    return S_OK;
}

//
//  See if we were created from a shortcut.  If so, then pull the exec
//  parameters from the shortcut.
//
void CExeDropTarget::_FillSEIFromLinkSite(SHELLEXECUTEINFO *pei)
{
    ASSERT(pei->lpParameters == NULL);
    ASSERT(pei->lpDirectory == NULL);

    IShellLink *psl;
    if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_LinkSite, IID_IShellLink, (void **)&psl)))
    {
        TCHAR szBuf[MAX_PATH];

        psl->GetShowCmd(&pei->nShow);

        // Hotkeys are annoying because IShellLink::GetHotkey uses a
        // WORD as the hotkey, but SHELLEXECUTEINFO uses a DWORD.

        WORD wHotkey;
        if (SUCCEEDED(psl->GetHotkey(&wHotkey)))
        {
            pei->dwHotKey = wHotkey;
            pei->fMask |= SEE_MASK_HOTKEY;
        }

        if (SUCCEEDED(psl->GetWorkingDirectory(szBuf, ARRAYSIZE(szBuf))) &&
            szBuf[0])
        {
            Str_SetPtr(const_cast<LPTSTR *>(&pei->lpDirectory), szBuf);
        }

        if (SUCCEEDED(psl->GetArguments(szBuf, ARRAYSIZE(szBuf))) &&
            szBuf[0])
        {
            Str_SetPtr(const_cast<LPTSTR *>(&pei->lpParameters), szBuf);
        }

        psl->Release();
    }

}

void CExeDropTarget::_CleanupSEIFromLinkSite(SHELLEXECUTEINFO *pei)
{
    Str_SetPtr(const_cast<LPTSTR *>(&pei->lpDirectory), NULL);
    Str_SetPtr(const_cast<LPTSTR *>(&pei->lpParameters), NULL);
}

BOOL GetAppDropTarget(LPCTSTR pszPath, CLSID *pclsid)
{
    TCHAR sz[MAX_PATH];

    // NOTE this assumes that this is a path to the exe
    // and not a command line
    PathToAppPathKey(pszPath, sz, ARRAYSIZE(sz));
    TCHAR szClsid[64];
    DWORD cb = sizeof(szClsid);
    return (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, sz, TEXT("DropTarget"), NULL, szClsid, &cb)) &&
            GUIDFromString(szClsid, pclsid);
}


STDMETHODIMP CExeDropTarget::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
    DWORD dwEffectPerformed = 0;

    if (!(_grfKeyStateLast & MK_LBUTTON))
    {
        HMENU hmenu = SHLoadPopupMenu(HINST_THISDLL, POPUP_DROPONEXE);
        if (hmenu)
        {
            HWND hwnd;
            IUnknown_GetWindow(_punkSite, &hwnd);

            UINT idCmd = SHTrackPopupMenu(hmenu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
                    pt.x, pt.y, 0, hwnd, NULL);
            DestroyMenu(hmenu);
            if (idCmd != DDIDM_COPY)
            {
                *pdwEffect = 0; // canceled
            }
        }
    }

    if (*pdwEffect)
    {
        CLSID clsidDropTarget;
        if (GetAppDropTarget(_szFile, &clsidDropTarget))
        {
            if (SUCCEEDED(SHSimulateDropOnClsid(clsidDropTarget, _punkSite, pdtobj)))
            {
                dwEffectPerformed = DROPEFFECT_COPY;  // what we did
            }
        }
        else
        {
            SHELLEXECUTEINFO ei = {
                sizeof(ei),
                    0, NULL, NULL, _szFile, NULL, NULL, SW_SHOWNORMAL, NULL 
            };
            
            _FillSEIFromLinkSite(&ei);
            
            LPCTSTR pszLinkParams = ei.lpParameters;
            
            FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
            STGMEDIUM medium;
            HRESULT hr = pdtobj->GetData(&fmte, &medium);
            if (SUCCEEDED(hr))
            {
                TCHAR szPath[MAX_PATH];
                int cchParam = ei.lpParameters ? lstrlen(ei.lpParameters) + 1 : 0;
                BOOL fLFNAware = App_IsLFNAware(_szFile);
                
                for (UINT i = 0; DragQueryFile((HDROP)medium.hGlobal, i, szPath, ARRAYSIZE(szPath)); i++)
                {
                    if (fLFNAware)
                        PathQuoteSpaces(szPath);
                    else
                        GetShortPathName(szPath, szPath, ARRAYSIZE(szPath));
                    cchParam += lstrlen(szPath) + 2;    // space and NULL
                }
                
                if (cchParam)
                {
                    LPTSTR pszParam = (LPTSTR)LocalAlloc(LPTR, cchParam * sizeof(*pszParam));
                    if (pszParam)
                    {
                        // If the link had parameters, then put our filenames after
                        // the parameters (with an intervening space)
                        
                        if (ei.lpParameters)
                        {
                            lstrcpyn(pszParam, ei.lpParameters, cchParam);
                            lstrcat(pszParam, c_szSpace);
                        }
                        
                        for (i = 0; DragQueryFile((HDROP)medium.hGlobal, i, szPath, ARRAYSIZE(szPath)); i++)
                        {
                            if (fLFNAware)
                                PathQuoteSpaces(szPath);
                            else
                                GetShortPathName(szPath, szPath, ARRAYSIZE(szPath));
                            if (i > 0)
                                lstrcat(pszParam, c_szSpace);
                            lstrcat(pszParam, szPath);
                        }
                        
                        ei.lpParameters = pszParam;
                        
                        ShellExecuteEx(&ei);
                        
                        LocalFree((HLOCAL)pszParam);
                        
                        dwEffectPerformed = DROPEFFECT_COPY;  // what we did
                    }
                }
                ReleaseStgMedium(&medium);
            }
            else
            {
                LPCSTR pszURL;
                
                if (SUCCEEDED(DataObj_GetShellURL(pdtobj, &medium, &pszURL)))
                {
                    if (DoesAppWantUrl(_szFile))
                    {
                        TCHAR szURL[INTERNET_MAX_URL_LENGTH];
                        SHAnsiToTChar(pszURL, szURL, ARRAYSIZE(szURL));
                        
                        ei.lpParameters = szURL;
                        
                        ShellExecuteEx(&ei);
                        
                        dwEffectPerformed = DROPEFFECT_LINK;  // what we did
                    }
                    ReleaseStgMediumHGLOBAL(NULL, &medium);
                }
            }
            
            // The process of building the ShellExecuteEx parameters may have
            // messed up the ei.lpParameters, so put the original back so the
            // cleanup function won't get confused.
            ei.lpParameters = pszLinkParams;
            _CleanupSEIFromLinkSite(&ei);
        }
        
        *pdwEffect = dwEffectPerformed;
    }
    
    return S_OK;
}

STDMETHODIMP CExeDropTarget::GetClassID(CLSID *pClassID)
{
    *pClassID = CLSID_ExeDropTarget;
    return S_OK;
}

STDMETHODIMP CExeDropTarget::IsDirty(void)
{
    return S_OK;        // no
}

STDMETHODIMP CExeDropTarget::Load(LPCOLESTR pszFileName, DWORD dwMode)
{
    SHUnicodeToTChar(pszFileName, _szFile, ARRAYSIZE(_szFile));
    return S_OK;
}

STDMETHODIMP CExeDropTarget::Save(LPCOLESTR pszFileName, BOOL fRemember)
{
    return S_OK;
}

STDMETHODIMP CExeDropTarget::SaveCompleted(LPCOLESTR pszFileName)
{
    return S_OK;
}

STDMETHODIMP CExeDropTarget::GetCurFile(LPOLESTR *ppszFileName)
{
    *ppszFileName = NULL;
    return E_NOTIMPL;
}

STDAPI CExeDropTarget_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
{
    HRESULT hr;
    CExeDropTarget* pdt = new CExeDropTarget();
    if (pdt)
    {
        hr = pdt->QueryInterface(riid, ppv);
        pdt->Release();
    }
    else
    {
        *ppv = NULL;
        hr = E_OUTOFMEMORY;
    }
    return hr;
}