/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */
#include "common.h"
#include "GlassDnD.h"
#include "GlassClipboard.h"
#include "ViewContainer.h"
#include "Utils.h"

#include "com_sun_glass_events_MouseEvent.h"

//Ctrl + Shift -> ACTION_LINK
//Ctrl         -> ACTION_COPY
//Shift        -> ACTION_MOVE
GlassDropTarget::GlassDropTarget(ViewContainer *viewContainer, HWND hwnd)
{
    OLE_TRY

    m_viewContainer = viewContainer;
    m_hwnd = hwnd;

    OLE_HRT(CoCreateInstance(
        CLSID_DragDropHelper,
        NULL,
        CLSCTX_ALL,
        IID_IDropTargetHelper,
        (LPVOID*)&m_spDropTargetHelper
    ))
    OLE_HRT(::RegisterDragDrop(m_hwnd, this))
    OLE_CATCH
    STRACE(_T("{GlassDropTarget"));
}

GlassDropTarget::~GlassDropTarget()
{
    m_spDropTargetHelper = NULL;
    ::RevokeDragDrop(m_hwnd);
    STRACE(_T("}GlassDropTarget"));
}

HRESULT  GlassDropTarget::UpdateDnDClipboardData(
    IDataObject *pDataObj)
{
    JNIEnv *env = GetEnv();
    //Get "DnD" clipboard
    JLClass jcWinDnDClipboard(env, env->FindClass("com/sun/glass/ui/win/WinDnDClipboard"));
    ASSERT(jcWinDnDClipboard)
    static jmethodID midGetInstance = env->GetStaticMethodID(jcWinDnDClipboard, "getInstance",
        "()Lcom/sun/glass/ui/win/WinDnDClipboard;");
    ASSERT(midGetInstance)
    JLObject jDnDClipboard(env, env->CallStaticObjectMethod(jcWinDnDClipboard, midGetInstance));
    ASSERT(jDnDClipboard)

    IDataObjectPtr spData( getPtr(env, jDnDClipboard) );
    if (NULL != pDataObj) {
        //lock it till clipboard close
        pDataObj->AddRef();
    }
    setPtr(env, jDnDClipboard, pDataObj);

    return checkJavaException(env);
}

HRESULT  GlassDropTarget::CallbackToJava(
    /* [in] */ jmethodID method,
    /* [in] */ DWORD grfKeyState,
    /* [in] */ POINTL pt,
    /* [out][in] */ DWORD *pdwEffect)
{
    if (!m_viewContainer->GetView()) {
        return S_OK;
    }

    JNIEnv *env = GetEnv();
    POINT ptClient = *(LPPOINT)&pt;
    ::ScreenToClient(m_hwnd, &ptClient);

    SetSourceSupportedActions(getACTION(*pdwEffect));

    //We want to be Explorer-like.
    //Found the action from keyboard state.
    DWORD like = DROPEFFECT_MOVE;
    grfKeyState &= MK_CONTROL | MK_SHIFT | MK_ALT;
    if ( (MK_CONTROL | MK_SHIFT)== grfKeyState || MK_ALT == grfKeyState) {
        like = DROPEFFECT_LINK;
    } else if (MK_CONTROL == grfKeyState) {
        like = DROPEFFECT_COPY;
    }

    static DROPEFFECT DesiredActions[] = {
        //Actions in order of priority (the same order is in Explorer)
        DROPEFFECT_COPY,
        DROPEFFECT_MOVE,
        DROPEFFECT_LINK
    };

    //Let's check the target ability for selected action.
    for( int iDesiredIndex = 0;

        (like & *pdwEffect) == 0 //action is not supported by target
        && iDesiredIndex < sizeof(DesiredActions)/sizeof(*DesiredActions);

        ++iDesiredIndex)
    {
        //target cannot do the action, let's try the next
        like = DesiredActions[iDesiredIndex];
    }

    *pdwEffect = getDROPEFFECT(DROPEFFECT(env->CallIntMethod(m_viewContainer->GetView(), method,
        jint(ptClient.x), jint(ptClient.y), jint(pt.x), jint(pt.y), getACTION(like))));

    return checkJavaException(env);
}

HRESULT GlassDropTarget::DragEnter(
        /* [unique][in] */ IDataObject *pDataObj,
        /* [in] */ DWORD grfKeyState,
        /* [in] */ POINTL pt,
        /* [out][in] */ DWORD *pdwEffect)
{
    OLE_TRY
    OLE_HRT(UpdateDnDClipboardData(pDataObj))
    //dragAction = View.notifyDragEnter(...)
    OLE_HRT(CallbackToJava(javaIDs.View.notifyDragEnter, grfKeyState, pt, pdwEffect))
    //ignore HRESULT - just no image
    m_spDropTargetHelper->DragEnter(m_hwnd, pDataObj, (LPPOINT)&pt, *pdwEffect);
    OLE_CATCH
    OLE_RETURN_HR
}

HRESULT GlassDropTarget::DragOver(
    /* [in] */ DWORD grfKeyState,
    /* [in] */ POINTL pt,
    /* [out][in] */ DWORD *pdwEffect)
{
    OLE_TRY
    //dragAction = View.notifyDragOver(...)
    OLE_HRT(OLE_HRT(CallbackToJava(javaIDs.View.notifyDragOver, grfKeyState, pt, pdwEffect)))
    //ignore HRESULT - just no image
    m_spDropTargetHelper->DragOver((LPPOINT)&pt, *pdwEffect);
    OLE_CATCH
    OLE_RETURN_HR
}

HRESULT GlassDropTarget::DragLeave()
{
    if (!m_viewContainer->GetView()) {
        return S_OK;
    }

    OLE_TRY
    JNIEnv *env = GetEnv();
    //View.notifyDragLeave()
    env->CallIntMethod(m_viewContainer->GetView(), javaIDs.View.notifyDragLeave);
    OLE_HRT(checkJavaException(env))
    //ignore HRESULT - just no image
    m_spDropTargetHelper->DragLeave();
    OLE_CATCH
    OLE_RETURN_HR
}

HRESULT GlassDropTarget::Drop(
    /* [unique][in] */ IDataObject *pDataObj,
    /* [in] */ DWORD grfKeyState,
    /* [in] */ POINTL pt,
    /* [out][in] */ DWORD *pdwEffect)
{
    OLE_TRY
    OLE_HRT(UpdateDnDClipboardData(pDataObj))
    //performedAction = View.notifyDragDrop(...)
    OLE_HRT(OLE_HRT(CallbackToJava(javaIDs.View.notifyDragDrop, grfKeyState, pt, pdwEffect)))
    //ignore HRESULT - just no image
    m_spDropTargetHelper->Drop(pDataObj, (LPPOINT)&pt, *pdwEffect);
    OLE_CATCH
    OLE_RETURN_HR
}

/*static*/
HRESULT GlassDropTarget::SetSourceSupportedActions(/*in*/jint actions)
{
    JNIEnv *env = GetEnv();
    //Get "DnD" clipboard
    JLClass jcWinDnDClipboard(env, env->FindClass("com/sun/glass/ui/win/WinDnDClipboard"));
    ASSERT(jcWinDnDClipboard)
    static jmethodID midGetInstance = env->GetStaticMethodID(jcWinDnDClipboard, "getInstance",
        "()Lcom/sun/glass/ui/win/WinDnDClipboard;");
    ASSERT(midGetInstance)

    static jmethodID midSetSourceSupportedActions = env->GetMethodID(jcWinDnDClipboard, "setSourceSupportedActions",
        "(I)V");
    ASSERT(midSetSourceSupportedActions)

    JLObject jDnDClipboard(env, env->CallStaticObjectMethod(jcWinDnDClipboard, midGetInstance));
    ASSERT(jDnDClipboard)

    env->CallVoidMethod(jDnDClipboard, midSetSourceSupportedActions, actions);

    return checkJavaException(env);
}

//////////////////////////////////////////////////////////////////////////
// GlassDropSource
//////////////////////////////////////////////////////////////////////////

/*static*/
HRESULT  GlassDropSource::SetDragButton(jint button)
{
    JNIEnv *env = GetEnv();
    //Get "DnD" clipboard
    JLClass jcWinDnDClipboard(env, env->FindClass("com/sun/glass/ui/win/WinDnDClipboard"));
    ASSERT(jcWinDnDClipboard)
    static jmethodID midGetInstance = env->GetStaticMethodID(jcWinDnDClipboard, "getInstance",
        "()Lcom/sun/glass/ui/win/WinDnDClipboard;");
    ASSERT(midGetInstance)

    static jmethodID midSetDragButton = env->GetMethodID(jcWinDnDClipboard, "setDragButton",
        "(I)V");
    ASSERT(midSetDragButton)

    JLObject jDnDClipboard(env, env->CallStaticObjectMethod(jcWinDnDClipboard, midGetInstance));
    ASSERT(jDnDClipboard)

    env->CallVoidMethod(jDnDClipboard, midSetDragButton, button);

    return checkJavaException(env);
}

GlassDropSource::GlassDropSource(jobject jDnDClipboard)
{
    JNIEnv *env = GetEnv();
    static jmethodID midGetDragButton = 0;
    if (0 == midGetDragButton) {
        JLClass jcWinDnDClipboard(env, env->FindClass("com/sun/glass/ui/win/WinDnDClipboard"));
        ASSERT(jcWinDnDClipboard)

        midGetDragButton = env->GetMethodID(jcWinDnDClipboard, "getDragButton",
            "()I");
        ASSERT(midGetDragButton)
    }

    jint jbutton = env->CallIntMethod(jDnDClipboard, midGetDragButton);
    switch (jbutton) {
    case com_sun_glass_events_MouseEvent_BUTTON_LEFT:
        m_button = MK_LBUTTON;
        break;
    case com_sun_glass_events_MouseEvent_BUTTON_RIGHT:
        m_button = MK_RBUTTON;
        break;
    case com_sun_glass_events_MouseEvent_BUTTON_OTHER:
        m_button = MK_MBUTTON;
        break;
    default:
        m_button = 0;
        break;
    }
    STRACE(_T("{GlassDropSource"));
}

GlassDropSource::~GlassDropSource()
{
    STRACE(_T("}GlassDropSource"));
}

HRESULT GlassDropSource::QueryContinueDrag(
    /* [in] */ BOOL fEscapePressed,
    /* [in] */ DWORD grfKeyState)
{
    return fEscapePressed
        ? DRAGDROP_S_CANCEL
        : 0 == (grfKeyState & m_button)
          ? DRAGDROP_S_DROP
          : S_OK;
}

HRESULT GlassDropSource::GiveFeedback(/* [in] */DWORD dwEffect)
{
    return DRAGDROP_S_USEDEFAULTCURSORS;
}
