#include "droptgt.h"

#define TF_DRAGDROP TF_BAND


#define MAX_DROPTARGETS 3

class CDropTargetWrap : public IDropTarget
{
public:
    // *** IUnknown ***
    virtual STDMETHODIMP_(ULONG) AddRef(void);
    virtual STDMETHODIMP_(ULONG) Release(void);
    virtual STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj);

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

    CDropTargetWrap(IDropTarget** ppdtg, HWND hwnd);
protected:
    ~CDropTargetWrap();

private:
    int             _cRef;

    int             _count;
    IDropTarget*    _rgpdt[MAX_DROPTARGETS];
    DWORD           _rgdwEffect[MAX_DROPTARGETS];
    HWND            _hwnd;
};

CDropTargetWrap::CDropTargetWrap(IDropTarget** ppdt, HWND hwnd)
    : _hwnd(hwnd)
{
    _cRef = 1;

    for (int i = 0; i < MAX_DROPTARGETS; i++, ppdt++) {
        if (*ppdt) {
            _rgpdt[_count] = *ppdt;
            _rgpdt[_count]->AddRef();
            _count++;
        }
    }
}

CDropTargetWrap::~CDropTargetWrap()
{
    for (int i = 0 ; i < _count ; i++)
    {
        _rgpdt[i]->Release();
    }
}

IDropTarget* DropTargetWrap_CreateInstance(IDropTarget* pdtPrimary, IDropTarget* pdtSecondary, HWND hwnd, IDropTarget* pdt3)
{
    // no point in wrapping nothing...
    if (pdtPrimary || pdtSecondary || pdt3)
    {
        IDropTarget* pdt[MAX_DROPTARGETS] = { pdtPrimary, pdtSecondary, pdt3 };
        
        CDropTargetWrap* pdtw = new CDropTargetWrap(pdt, hwnd);
        if (pdtw)
        {
            return SAFECAST(pdtw, IDropTarget*);
        }
    }
    return NULL;
}

HRESULT CDropTargetWrap::QueryInterface(REFIID riid, void **ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CDropTargetWrap, IDropTarget),
        { 0 },
    };

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

ULONG CDropTargetWrap::AddRef(void)
{
    _cRef++;
    return _cRef;
}

ULONG CDropTargetWrap::Release(void)
{
    _cRef--;
    if (_cRef > 0)
        return _cRef;

    delete this;
    return 0;
}


/*----------------------------------------------------------
Purpose: IDropTarget::DragEnter method

         The *pdwEffect that is returned is the first valid value
         of all the drop targets' returned effects.

*/
HRESULT CDropTargetWrap::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
{
    DWORD dwEffectOut = DROPEFFECT_NONE;

    for (int i = 0 ; i < _count ; i++)
    {
        _rgdwEffect[i] = *pdwEffect;

        if (SUCCEEDED(_rgpdt[i]->DragEnter(pdtobj, grfKeyState, ptl, &_rgdwEffect[i])))
        {
            if (dwEffectOut == DROPEFFECT_NONE)
            {
                dwEffectOut = _rgdwEffect[i];
            }
        }
        else
        {
            _rgdwEffect[i] = DROPEFFECT_NONE;
        }
    }
    *pdwEffect = dwEffectOut;
    return(S_OK);
}


/*----------------------------------------------------------
Purpose: IDropTarget::DragOver method

*/
HRESULT CDropTargetWrap::DragOver(DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
{
    DWORD dwEffectOut = DROPEFFECT_NONE;
        
    for (int i = 0 ; i < _count ; i++)
    {
        _rgdwEffect[i] = *pdwEffect;

        if (SUCCEEDED(_rgpdt[i]->DragOver(grfKeyState, ptl, &_rgdwEffect[i])))
        {
            if (dwEffectOut == DROPEFFECT_NONE)
                dwEffectOut = _rgdwEffect[i];
        }
        else
        {
            _rgdwEffect[i] = DROPEFFECT_NONE;
        }
    }

    *pdwEffect = dwEffectOut;
    return(S_OK);
}


/*----------------------------------------------------------
Purpose: IDropTarget::DragLeave method

*/
HRESULT CDropTargetWrap::DragLeave(void)
{
    for (int i = 0 ; i < _count ; i++)
    {
        _rgpdt[i]->DragLeave();
    }

    return(S_OK);
}


/*----------------------------------------------------------
Purpose: IDropTarget::Drop method

*/
HRESULT CDropTargetWrap::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
    DWORD dwEffectOut = DROPEFFECT_NONE;
    int i;
    BOOL fDropTried = FALSE;

    for (i = 0 ; (DROPEFFECT_NONE == dwEffectOut) && i < _count ; i++)
    {
        if ((_rgdwEffect[i] && *pdwEffect) && !fDropTried)
        {
            dwEffectOut = *pdwEffect;
            _rgpdt[i]->Drop(pdtobj, grfKeyState, pt, &dwEffectOut);
            fDropTried = TRUE;
        }
        else
        {
            _rgpdt[i]->DragLeave();
        }
    }

    *pdwEffect = dwEffectOut;
    return(S_OK);
}


//=============================================================================
// CDelegateDropTarget
//
// This class implements IDropTarget given an IDelegateDropTargetCB interface.
// It handles all hit testing, caching, and scrolling for you.
//
//=============================================================================
#undef  CDropTargetWrap

CDelegateDropTarget::CDelegateDropTarget()
{
    TraceMsg(TF_SHDLIFE, "ctor CDelegateDropTarget %x", this);

}

CDelegateDropTarget::~CDelegateDropTarget()
{
    TraceMsg(TF_SHDLIFE, "dtor CDelegateDropTarget %x", this);

    ASSERT(!_pDataObj);
    ATOMICRELEASE(_pDataObj);
    ASSERT(!_pdtCur);
    ATOMICRELEASE(_pdtCur);
}

HRESULT CDelegateDropTarget::Init()
{
    HRESULT hres = GetWindowsDDT(&_hwndLock, &_hwndScroll);
    // We lock _hwndLock and do scrolling against _hwndScroll.
    // These can be different hwnds, but certain restrictions apply:
    if (_hwndLock != _hwndScroll)
    {
        BOOL fValid = IsChild(_hwndLock, _hwndScroll);
        if (!fValid)
        {
            TraceMsg(TF_DRAGDROP, "ctor CDelegateDropTarget: invalid windows %x and %x!", _hwndLock, _hwndScroll);
            _hwndLock = _hwndScroll = NULL;
        }
    }
    return hres;
}

void CDelegateDropTarget::_ReleaseCurrentDropTarget()
{
    if (_pdtCur)
    {
        _pdtCur->DragLeave();
        ATOMICRELEASE(_pdtCur);
    }
}

/*----------------------------------------------------------
Purpose: IDropTarget::DragEnter method

*/
HRESULT CDelegateDropTarget::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL ptl, LPDWORD pdwEffect)
{
    // We can be re-entered due to ui on thread
    if (_pDataObj != NULL)       
    {
        TraceMsg(TF_DRAGDROP, "CDelegateDropTarget::DragEnter called a second time!");
        *pdwEffect = DROPEFFECT_NONE;
        return S_OK;
    }
    TraceMsg(TF_DRAGDROP, "CDelegateDropTarget::DragEnter with *pdwEffect=%x", *pdwEffect);

    ASSERT(!_pDataObj);
    _pDataObj = pdtobj;
    _pDataObj->AddRef();

    // cache state
    //
    // wait until first DragOver to get valid info
    //
    _fPrime = FALSE;
    _dwEffectOut = DROPEFFECT_NONE;

    // set up auto-scroll info
    //
    ASSERT(pdtobj);
    _DragEnter(_hwndLock, ptl, pdtobj);

    DAD_InitScrollData(&_asd);

    _ptLast.x = _ptLast.y = 0x7fffffff; // put bogus value to force redraw

    HitTestDDT(HTDDT_ENTER, NULL, NULL, NULL);

    return S_OK;
}


/*----------------------------------------------------------
Purpose: IDropTarget::DragOver method

*/
HRESULT CDelegateDropTarget::DragOver(DWORD grfKeyState, POINTL ptl, LPDWORD pdwEffect)
{
    HRESULT hres = S_OK;
    DWORD_PTR itemNew;
    POINT pt;
    DWORD dwEffectScroll = 0;
    DWORD dwEffectOut = 0;
    BOOL fSameImage = FALSE;
    DWORD   dwCustDropEffect = 0;

    if (_pDataObj == NULL)
    {
        ASSERT(0);      // DragEnter should be called before.
        return E_FAIL;
    }

    // convert to window coords
    pt.x = ptl.x;
    pt.y = ptl.y;
    ScreenToClient(_hwndScroll, &pt);

    if (DAD_AutoScroll(_hwndScroll, &_asd, &pt))
        dwEffectScroll = DROPEFFECT_SCROLL;

    //
    //  If we are dragging over on a different item, get its IDropTarget
    // interface or adjust itemNew to -1.
    //
    if (SUCCEEDED(HitTestDDT(HTDDT_OVER, &pt, &itemNew, &dwCustDropEffect)) &&
        (itemNew != _itemOver || !_fPrime))
    {
        _fPrime = TRUE;

        _ReleaseCurrentDropTarget();

        _itemOver = itemNew;
        GetObjectDDT(_itemOver, IID_IDropTarget, (LPVOID*)&_pdtCur);

        if (_pdtCur)
        {
            // There's an IDropTarget for this hit, use it
            dwEffectOut = *pdwEffect;

            hres = _pdtCur->DragEnter(_pDataObj, grfKeyState, ptl, &dwEffectOut);
            if (FAILED(hres))
                dwEffectOut = DROPEFFECT_NONE;
        }
        else
        {
            // No IDropTarget, no effect
            dwEffectOut = DROPEFFECT_NONE;
        }
    }
    else
    {
        //
        // No change in the selection. We assume that *pdwEffect stays
        // the same during the same drag-loop as long as the key state doesn't change.
        //
        if ((_grfKeyState != grfKeyState) && _pdtCur)
        {
            dwEffectOut = *pdwEffect;

            hres = _pdtCur->DragOver(grfKeyState, ptl, &dwEffectOut);

            TraceMsg(TF_DRAGDROP, "CDelegateDropTarget::DragOver DragOver()d id:%d dwEffect:%4x hres:%d", _itemOver, dwEffectOut, hres);
        }
        else
        {
            // Same item and same key state. Use the previous dwEffectOut.
            dwEffectOut = _dwEffectOut;
            fSameImage = TRUE;
        }
    }

    _grfKeyState = grfKeyState;    // store these for the next Drop
    _dwEffectOut = dwEffectOut;    // and DragOver

    // Is the Custdrop effect valid ?
    if (dwCustDropEffect != DROPEFFECT_NONE)    
    {
        //Yes then set the effect to Custdrop effect along with scroll effect
        *pdwEffect = dwCustDropEffect | dwEffectScroll;
    }
    else 
    {
        //No , set the effect to dwEffectOut along with scroll effect
        *pdwEffect = dwEffectOut | dwEffectScroll;
    }
        TraceMsg(TF_DRAGDROP, "CDelegateDropTarget::DragOver (*pdwEffect=%x)", *pdwEffect);


    if (!(fSameImage && pt.x==_ptLast.x && pt.y==_ptLast.y))
    {
        _DragMove(_hwndLock, ptl);
        _ptLast.x = ptl.x;
        _ptLast.y = ptl.y;
    }
    return hres;
}


/*----------------------------------------------------------
Purpose: IDropTarget::DragLeave method

*/
HRESULT CDelegateDropTarget::DragLeave()
{
    HitTestDDT(HTDDT_LEAVE, NULL, NULL, NULL);
    _ReleaseCurrentDropTarget();

    TraceMsg(TF_DRAGDROP, "CDelegateDropTarget::DragLeave");
    ATOMICRELEASE(_pDataObj);

    DAD_DragLeave();

    return S_OK;
}


/*----------------------------------------------------------
Purpose: IDropTarget::Drop method

*/
HRESULT CDelegateDropTarget::Drop(IDataObject *pdtobj,
                             DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
    HRESULT hres = S_OK;
    BOOL bDropHandled = FALSE;

    TraceMsg(TF_DRAGDROP, "CDelegateDropTarget::Drop (*pdwEffect=%x)", *pdwEffect);

    //
    // According to AlexGo (OLE), this is by-design. We should make it sure
    // that we use pdtobj instead of pdtobj.
    //
    //ASSERT(pdtobj == _pDataObj);
    pdtobj->AddRef();
    _pDataObj->Release();
    _pDataObj = pdtobj;

    //
    // Note that we don't use the drop position intentionally,
    // so that it matches to the last destination feedback.
    //
    if (_pdtCur)
    {
        // use this local because if _pdtCur::Drop does a UnlockWindow
        // then hits an error and needs to put up a dialog,
        // we could get re-entered
        IDropTarget *pdtCur = _pdtCur;
        _pdtCur = NULL;

        // HACK ALERT!!!!
        //
        //  If we don't call LVUtil_DragEnd here, we'll be able to leave
        // dragged icons visible when the menu is displayed. However, because
        // we are calling IDropTarget::Drop() which may create some modeless
        // dialog box or something, we can not ensure the locked state of
        // the list view -- LockWindowUpdate() can lock only one window at
        // a time. Therefore, we skip this call only if the _pdtCur
        // is a subclass of CIDLDropTarget, assuming its Drop calls
        // CDefView::DragEnd (or CIDLDropTarget_DragDropMenu) appropriately.
        //
#if 0 // later
        if (!IsIDLDropTarget(pdtCur))
#endif
        {
            //
            // This will hide the dragged image.
            //
            DAD_DragLeave();

            //
            //  We need to reset the drag image list so that the user
            // can start another drag&drop while we are in this
            // Drop() member function call.
            //
            // NOTE: we don't have to worry about the DAD_DragLeave
            // (called from during the DragLeave call at the end of
            // this function) cancelling the potential above-mentioned
            // drag&drop loop. If such a beast is going on, it should
            // complete before pdtCur->Drop returns.
            //
            DAD_SetDragImage(NULL, NULL);
        }

        if (S_FALSE != OnDropDDT(pdtCur, _pDataObj, &grfKeyState, pt, pdwEffect))
            pdtCur->Drop(_pDataObj, grfKeyState, pt, pdwEffect);
        else
            pdtCur->DragLeave(); // should be okay even if OnDrop did this already

        pdtCur->Release();
    }
    else
    {
        //
        // We come here if Drop is called without DragMove (with DragEnter).
        //
        *pdwEffect = DROPEFFECT_NONE;
    }

    //
    // Clean up everything (OLE won't call DragLeave after Drop).
    //
    DragLeave();

    return hres;
}

// ******************************************************************
// dummy drop target to only call DAD_DragEnterEx() on DragEnter();
// ******************************************************************

HRESULT CDropDummy::QueryInterface(REFIID riid, void **ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CDropDummy, IDropTarget),
        { 0 },
    };

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

ULONG CDropDummy::AddRef(void)
{
    _cRef++;
    return _cRef;
}

ULONG CDropDummy::Release(void)
{
    _cRef--;
    if (_cRef > 0)
        return _cRef;

    delete this;
    return 0;
}


/*----------------------------------------------------------
Purpose: IDropTarget::DragEnter method

         simply call DAD_DragEnterEx2() to get custom drag cursor 
         drawing.

*/
HRESULT CDropDummy::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
{
    ASSERT(pdtobj);
    _DragEnter(_hwndLock, ptl, pdtobj);
    *pdwEffect = DROPEFFECT_NONE;
    return(S_OK);
}

HRESULT CDropDummy::DragOver(DWORD grfKeyState, POINTL ptl, DWORD *pdwEffect)
{
    _DragMove(_hwndLock, ptl);
    *pdwEffect = DROPEFFECT_NONE;
    return  S_OK;
}

