/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */
#include "common.h"
#include <wchar.h>
#include <Tpcshrd.h>
#include <vector>

#include "ViewContainer.h"
#include "GlassView.h"
#include "KeyTable.h"
#include "Utils.h"
#include "GlassDnD.h"
#include "GlassInputTextInfo.h"
#include "ManipulationEvents.h"

#include "com_sun_glass_events_ViewEvent.h"
#include "com_sun_glass_events_KeyEvent.h"
#include "com_sun_glass_events_MouseEvent.h"
#include "com_sun_glass_events_DndEvent.h"
#include "com_sun_glass_events_TouchEvent.h"


static UINT LangToCodePage(LANGID idLang)
{
    WCHAR strCodePage[8];
    // use the LANGID to create a LCID
    LCID idLocale = MAKELCID(idLang, SORT_DEFAULT);
    // get the ANSI code page associated with this locale
    if (::GetLocaleInfo(idLocale, LOCALE_IDEFAULTANSICODEPAGE,
                        strCodePage, sizeof(strCodePage) / sizeof(WCHAR)) > 0)
    {
        return _wtoi(strCodePage);
    } else {
        return ::GetACP();
    }
}

namespace {

bool IsPenEvent()
{
    // http://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx

    enum {
        SIGNATURE = 0xFF515700,
        MASK = 0xFFFFFF00
    };

    const LPARAM v = GetMessageExtraInfo();

    return ((v & MASK) == SIGNATURE);
}

} // namespace

ViewContainer::ViewContainer() :
    m_view(NULL),
    m_bTrackingMouse(FALSE),
    m_manipProc(NULL),
    m_inertiaProc(NULL),
    m_gestureSupportCls(NULL)
{
    m_kbLayout = ::GetKeyboardLayout(0);
    m_idLang = LOWORD(m_kbLayout);
    m_codePage = LangToCodePage(m_idLang);
}

jobject ViewContainer::GetView()
{
    return GetGlassView() != NULL ? GetGlassView()->GetView() : NULL;
}

void ViewContainer::InitDropTarget(HWND hwnd)
{
    if (!hwnd) {
        return;
    }

    m_spDropTarget = 
        std::auto_ptr<IDropTarget>(new GlassDropTarget(this, hwnd));
}

void ViewContainer::ReleaseDropTarget()
{
    m_spDropTarget = std::auto_ptr<IDropTarget>();
}

void ViewContainer::InitManipProcessor(HWND hwnd)
{
    if (IS_WIN7) {
        ::RegisterTouchWindow(hwnd, TWF_FINETOUCH);

        ::CoCreateInstance(CLSID_ManipulationProcessor,
                           NULL,
                           CLSCTX_INPROC_SERVER,
                           IID_IUnknown,
                           (VOID**)(&m_manipProc)
                           );
        
        ::CoCreateInstance(CLSID_InertiaProcessor,
                           NULL,
                           CLSCTX_INPROC_SERVER,
                           IID_IUnknown,
                           (VOID**)(&m_inertiaProc)
                           );
        
        m_manipEventSink = 
            new ManipulationEventSinkWithInertia(m_manipProc, m_inertiaProc, this, hwnd);

        const DWORD_PTR dwHwndTabletProperty =
             TABLET_DISABLE_PENTAPFEEDBACK |
             TABLET_DISABLE_PENBARRELFEEDBACK |
             TABLET_DISABLE_FLICKS;
        ::SetProp(hwnd, MICROSOFT_TABLETPENSERVICE_PROPERTY, reinterpret_cast<HANDLE>(dwHwndTabletProperty));

        if (!m_gestureSupportCls) {
            JNIEnv *env = GetEnv();
            const jclass cls = env->FindClass("com/sun/glass/ui/win/WinGestureSupport");

            m_gestureSupportCls = (jclass)env->NewGlobalRef(cls);
            env->DeleteLocalRef(cls);
            ASSERT(m_gestureSupportCls);
        }
    }
}

void ViewContainer::ReleaseManipProcessor()
{
    if (IS_WIN7) {
        if (m_manipProc) {
            m_manipProc->Release();
            m_manipProc = NULL;
        }
        if (m_inertiaProc) {
            m_inertiaProc->Release();
            m_inertiaProc = NULL;
        }
        if (m_manipEventSink) {
            m_manipEventSink->Release();
            m_manipEventSink = NULL;
        }
    }
    
    if (m_gestureSupportCls) {
        JNIEnv *env = GetEnv();
        env->DeleteGlobalRef(m_gestureSupportCls);
        m_gestureSupportCls = 0;
    }
}

void ViewContainer::HandleViewInputLangChange(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (!GetGlassView()) {
        return;
    }

    m_kbLayout = reinterpret_cast<HKL>(lParam);
    m_idLang = LOWORD(m_kbLayout);
    m_codePage = LangToCodePage(m_idLang);
}

void ViewContainer::NotifyViewSize(HWND hwnd)
{
    if (!hwnd || !GetGlassView()) {
        return;
    }

    RECT r;
    if (::GetClientRect(hwnd, &r)) {
        JNIEnv* env = GetEnv();
        env->CallVoidMethod(GetView(), javaIDs.View.notifyResize,
                            r.right-r.left, r.bottom - r.top);
        CheckAndClearException(env);
    }
}

void ViewContainer::HandleViewPaintEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (!GetGlassView()) {
        return;
    }

    RECT r;
    if (!::GetUpdateRect(hwnd, &r, FALSE)) {
        return;
    }

    JNIEnv* env = GetEnv();
    env->CallVoidMethod(GetView(), javaIDs.View.notifyRepaint,
            r.left, r.top, r.right-r.left, r.bottom-r.top);
    CheckAndClearException(env);
}

void ViewContainer::HandleViewSizeEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (wParam == SIZE_MINIMIZED) {
        return;
    }
    NotifyViewSize(hwnd);
}

void ViewContainer::HandleViewMenuEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (!GetGlassView()) {
        return;
    }
    if ((HWND)wParam != hwnd) {
        return;
    }
    jboolean isKeyboardTrigger = lParam == (LPARAM)-1; 
    if (isKeyboardTrigger) {
        lParam = ::GetMessagePos ();
    }
    POINT pt;
    int absX = pt.x = GET_X_LPARAM(lParam);
    int absY = pt.y = GET_Y_LPARAM(lParam);
    ::ScreenToClient (hwnd, &pt);
    if (!isKeyboardTrigger) {
        RECT rect;
        ::GetClientRect(hwnd, &rect);
        if (!::PtInRect(&rect, pt)) {
            return;
        }
    }
    JNIEnv* env = GetEnv();
    env->CallVoidMethod(GetView(), javaIDs.View.notifyMenu, pt.x, pt.y, absX, absY, isKeyboardTrigger);
    CheckAndClearException(env);
}

void ViewContainer::HandleViewKeyEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (!GetGlassView()) {
        return;
    }

    static const BYTE KEY_STATE_DOWN = 0x80;

    UINT wKey = static_cast<UINT>(wParam);
    UINT flags = HIWORD(lParam);

    jint jKeyCode = WindowsKeyToJavaKey(wKey);

    BYTE kbState[256];
    if (!::GetKeyboardState(kbState)) {
        return;
    }

    jint jModifiers = GetModifiers();

    if (jModifiers & com_sun_glass_events_KeyEvent_MODIFIER_CONTROL) {
        kbState[VK_CONTROL] &= ~KEY_STATE_DOWN;
    }

    WORD mbChar;
    UINT scancode = ::MapVirtualKey(wKey, 0);
    int converted = ::ToAsciiEx(wKey, scancode, kbState,
                                &mbChar, 0, m_kbLayout);

    int keyCharCount = 0;
    jchar keyChars[4];
    const bool isAutoRepeat = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN)
        && (lParam & (1 << 30));

    if (converted < 0) {
        // Dead key
        return;
    } else if (converted == 0) {
        // No translation available
        keyCharCount = 0;
        // This includes SHIFT, CONTROL, ALT, etc.
        // RT-17062: suppress auto-repeated events for modifier keys
        if (isAutoRepeat) {
            switch (jKeyCode) {
                case com_sun_glass_events_KeyEvent_VK_SHIFT:
                case com_sun_glass_events_KeyEvent_VK_CONTROL:
                case com_sun_glass_events_KeyEvent_VK_ALT:
                case com_sun_glass_events_KeyEvent_VK_WINDOWS:
                    return;
            }
        }
    } else {
        // Handle some special cases
        if ((wKey == VK_BACK) ||
            (wKey == VK_ESCAPE))
        {
            keyCharCount = 0;
        } else {
            keyCharCount = ::MultiByteToWideChar(m_codePage, MB_PRECOMPOSED,
                                                 (LPCSTR)&mbChar, 2, (LPWSTR)keyChars,
                                                 4 * sizeof(jchar)) - 1;
            if (keyCharCount <= 0) {
                return;
            }
        }
    }

    JNIEnv* env = GetEnv();

    jcharArray jKeyChars = env->NewCharArray(keyCharCount);
    if (keyCharCount) {
        env->SetCharArrayRegion(jKeyChars, 0, keyCharCount, keyChars);
    }

    env->CallVoidMethod(GetView(), javaIDs.View.notifyKey,
                        (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) ?
                        com_sun_glass_events_KeyEvent_PRESS : com_sun_glass_events_KeyEvent_RELEASE,
                        jKeyCode, jKeyChars, jModifiers);
    CheckAndClearException(env);

    env->DeleteLocalRef(jKeyChars);
}

void ViewContainer::HandleViewTypedEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 
{
    if (!GetGlassView()) {
        return;
    }

    int repCount = LOWORD(lParam);
    JNIEnv* env = GetEnv();
    jcharArray jKeyChars = env->NewCharArray(repCount);
    jchar* nKeyChars = env->GetCharArrayElements(jKeyChars, NULL);
    for (int i = 0; i < repCount; i++) {
        nKeyChars[i] = (jchar)wParam;
    }
    env->ReleaseCharArrayElements(jKeyChars, nKeyChars, 0);

    env->CallVoidMethod(GetView(), javaIDs.View.notifyKey,
                        com_sun_glass_events_KeyEvent_TYPED,
                        com_sun_glass_events_KeyEvent_VK_UNDEFINED, jKeyChars, 
                        GetModifiers());
    CheckAndClearException(env);

    env->DeleteLocalRef(jKeyChars);
}

BOOL ViewContainer::HandleViewMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (!GetGlassView()) {
        return FALSE;
    }

    int type = 0;
    int button = com_sun_glass_events_MouseEvent_BUTTON_NONE;
    POINT pt;   // client coords
    jdouble wheelRotation = 0.0;

    if (msg == WM_MOUSELEAVE) {
        type = com_sun_glass_events_MouseEvent_EXIT;
        // get the coords (the message does not contain them)
        lParam = ::GetMessagePos();
        pt.x = GET_X_LPARAM(lParam);
        pt.y = GET_Y_LPARAM(lParam);
        // this is screen coords, convert to client
        ::ScreenToClient(hwnd, &pt);

        // Windows has finished tracking mouse pointer already
        m_bTrackingMouse = FALSE;
    } else {
        // for all other messages lParam contains cursor coords
        pt.x = GET_X_LPARAM(lParam);
        pt.y = GET_Y_LPARAM(lParam);

        switch (msg) {
            case WM_MOUSEMOVE:
                // See RT-11305 regarding the GetCapture() check
                if ((wParam & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)) != 0 && ::GetCapture() == hwnd) {
                    type = com_sun_glass_events_MouseEvent_DRAG;
                } else {
                    type = com_sun_glass_events_MouseEvent_MOVE;
                }
                // Due to RT-11305 we should report the pressed button for both
                // MOVE and DRAG. This also enables one to filter out these
                // events in client code in case they're undesired.
                if (wParam & MK_RBUTTON) {
                    button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT;
                } else if (wParam & MK_LBUTTON) {
                    button = com_sun_glass_events_MouseEvent_BUTTON_LEFT;
                } else if (wParam & MK_MBUTTON) {
                    button = com_sun_glass_events_MouseEvent_BUTTON_OTHER;
                }
                break;
            case WM_LBUTTONDOWN:
                type = com_sun_glass_events_MouseEvent_DOWN;
                button = com_sun_glass_events_MouseEvent_BUTTON_LEFT;
                break;
            case WM_LBUTTONUP:
                type = com_sun_glass_events_MouseEvent_UP;
                button = com_sun_glass_events_MouseEvent_BUTTON_LEFT;
                break;
            case WM_RBUTTONDOWN:
                type = com_sun_glass_events_MouseEvent_DOWN;
                button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT;
                break;
            case WM_RBUTTONUP:
                type = com_sun_glass_events_MouseEvent_UP;
                button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT;
                break;
            case WM_MBUTTONDOWN:
                type = com_sun_glass_events_MouseEvent_DOWN;
                button = com_sun_glass_events_MouseEvent_BUTTON_OTHER;
                break;
            case WM_MBUTTONUP:
                type = com_sun_glass_events_MouseEvent_UP;
                button = com_sun_glass_events_MouseEvent_BUTTON_OTHER;
                break;
            case WM_MOUSEWHEEL:
            case WM_MOUSEHWHEEL:
                {
                    // MS Windows always sends WHEEL events to the focused window.
                    // Redirect the message to a Glass window under the mouse
                    // cursor instead to match Mac behavior
                    HWND hwndUnderCursor = ::ChildWindowFromPointEx(
                            ::GetDesktopWindow(), pt,
                            CWP_SKIPDISABLED | CWP_SKIPINVISIBLE);

                    if (hwndUnderCursor && hwndUnderCursor != hwnd &&
                            BaseWnd::FromHandle(hwndUnderCursor))
                    {
                        return (BOOL)::SendMessage(hwndUnderCursor, msg, wParam, lParam);
                    }

                    // if there's none, proceed as usual
                    type = com_sun_glass_events_MouseEvent_WHEEL;
                    wheelRotation = (jdouble)GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;
                }
                break;
        }
    }

    switch (type) {
        case 0:
            // not handled
            return FALSE;
        case com_sun_glass_events_MouseEvent_DOWN:
            if (::GetCapture() != hwnd) {
                ::SetCapture(hwnd);
            }
            break;
        case com_sun_glass_events_MouseEvent_UP:
            if (::GetCapture() == hwnd) {
                ::ReleaseCapture();
            }
            break;
    }

    // get screen coords
    POINT ptAbs = pt;
    if (type == com_sun_glass_events_MouseEvent_WHEEL) {
        ::ScreenToClient(hwnd, &pt);
    } else {
        ::ClientToScreen(hwnd, &ptAbs);
    }

    jint jModifiers = GetModifiers();
    
    const jboolean isSynthesized = jboolean(IsPenEvent());

    JNIEnv *env = GetEnv();

    if (!m_bTrackingMouse && type != com_sun_glass_events_MouseEvent_EXIT) {
        TRACKMOUSEEVENT trackData;
        trackData.cbSize = sizeof(trackData);
        trackData.dwFlags = TME_LEAVE;
        trackData.hwndTrack = hwnd;
        trackData.dwHoverTime = HOVER_DEFAULT;
        if (::TrackMouseEvent(&trackData)) {
            // Mouse tracking will be canceled automatically upon receiving WM_MOUSELEAVE
            m_bTrackingMouse = TRUE;
        }
        env->CallVoidMethod(GetView(), javaIDs.View.notifyMouse,
                com_sun_glass_events_MouseEvent_ENTER,
                com_sun_glass_events_MouseEvent_BUTTON_NONE,
                pt.x, pt.y, ptAbs.x, ptAbs.y,
                jModifiers, JNI_FALSE, isSynthesized);
        CheckAndClearException(env);
    }

    switch (type) {
        case com_sun_glass_events_MouseEvent_DOWN:
            GlassDropSource::SetDragButton(button);
            break;
        case com_sun_glass_events_MouseEvent_UP:
            GlassDropSource::SetDragButton(0);
            break;
    }

    if (type == com_sun_glass_events_MouseEvent_WHEEL) {
        jdouble dx, dy;
        if (msg == WM_MOUSEWHEEL) {
            dx = 0.0;
            dy = wheelRotation;
        } else { // WM_MOUSEHWHEEL
            // Negate the value to be more "natural"
            dx = -wheelRotation;
            dy = 0.0;
        }

        jint ls, cs;

        UINT val = 0;
        ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &val, 0);
        ls = (jint)val;

        val = 0;
        ::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &val, 0);
        cs = (jint)val;

        env->CallVoidMethod(GetView(), javaIDs.View.notifyScroll,
                pt.x, pt.y, ptAbs.x, ptAbs.y,
                dx, dy, jModifiers, ls, cs, 3, 3, (jdouble)40.0, (jdouble)40.0);
    } else {
        env->CallVoidMethod(GetView(), javaIDs.View.notifyMouse,
                type, button, pt.x, pt.y, ptAbs.x, ptAbs.y,
                jModifiers,
                type == com_sun_glass_events_MouseEvent_UP && button == com_sun_glass_events_MouseEvent_BUTTON_RIGHT,
                isSynthesized);
    }
    CheckAndClearException(env);

    return TRUE;
}

void ViewContainer::ResetMouseTracking(HWND hwnd)
{
    if (!m_bTrackingMouse) {
        return;
    }

    // We don't expect WM_MOUSELEAVE anymore, so we cancel mouse tracking manually
    TRACKMOUSEEVENT trackData;
    trackData.cbSize = sizeof(trackData);
    trackData.dwFlags = TME_LEAVE | TME_CANCEL;
    trackData.hwndTrack = hwnd;
    trackData.dwHoverTime = HOVER_DEFAULT;
    ::TrackMouseEvent(&trackData);

    m_bTrackingMouse = FALSE;

    if (!GetGlassView()) {
        return;
    }

    POINT ptAbs;
    ::GetCursorPos(&ptAbs);

    POINT pt = ptAbs;
    ::ScreenToClient(hwnd, &pt);

    JNIEnv *env = GetEnv();
    env->CallVoidMethod(GetView(), javaIDs.View.notifyMouse,
            com_sun_glass_events_MouseEvent_EXIT, 0, pt.x, pt.y, ptAbs.x, ptAbs.y,
            GetModifiers(),
            JNI_FALSE,
            JNI_FALSE);
    CheckAndClearException(env);
}

BOOL ViewContainer::HandleViewInputMethodEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    GlassView* gv = GetGlassView();

    if (!gv) {
        return FALSE;
    }

    switch (msg) {
    case WM_IME_ENDCOMPOSITION:
        SendInputMethodEvent(NULL, 0, NULL, 0, NULL, NULL, 0, 0, 0);
    case WM_IME_STARTCOMPOSITION:
        return gv->IsInputMethodEventEnabled();

    case WM_IME_COMPOSITION:
        if (gv->IsInputMethodEventEnabled()) {
            WmImeComposition(hwnd, wParam, lParam);
            return TRUE;
        }
        break;

    case WM_IME_NOTIFY:
        if (gv->IsInputMethodEventEnabled()) {
            WmImeNotify(hwnd, wParam, lParam);
        }
        break;

    default:
        return FALSE;
    }

    return FALSE;
}

void ViewContainer::WmImeComposition(HWND hwnd, WPARAM wParam, LPARAM lParam) 
{
    BOOL ret = FALSE;

    JNIEnv *env = GetEnv();

    int*      bndClauseW = NULL;
    int*      bndAttrW = NULL;
    BYTE*     valAttrW = NULL;
    int       cClauseW = 0;
    GlassInputTextInfo textInfo = GlassInputTextInfo(this);
    HIMC hIMC = ImmGetContext(hwnd);
    ASSERT(hIMC!=0);

    try {
        textInfo.GetContextData(hIMC, lParam);

        jstring jtextString = textInfo.GetText();
        if ((lParam & GCS_RESULTSTR && jtextString != NULL) ||
            (lParam & GCS_COMPSTR)) {
            int       cursorPosW = textInfo.GetCursorPosition();
            int       cAttrW = textInfo.GetAttributeInfo(bndAttrW, valAttrW);
            cClauseW = textInfo.GetClauseInfo(bndClauseW);

            SendInputMethodEvent(jtextString,
                                 cClauseW, bndClauseW,
                                 cAttrW, bndAttrW, valAttrW,
                                 textInfo.GetCommittedTextLength(),
                                 cursorPosW, cursorPosW);

        }
        ImmReleaseContext(hwnd, hIMC);
    } catch (...) {
        // since GetClauseInfo and GetAttributeInfo could throw exception, we have to release
        // the pointer here.
        delete [] bndClauseW;
        delete [] bndAttrW;
        delete [] valAttrW;
        ImmReleaseContext(hwnd, hIMC);
        throw;
    }

    /* Free the storage allocated. Since jtextString won't be passed from threads
     *  to threads, we just use the local ref and it will be deleted within the destructor
     *  of GlassInputTextInfo object.
     */

    delete [] bndClauseW;
    delete [] bndAttrW;
    delete [] valAttrW;
    CheckAndClearException(env);
}

void ViewContainer::WmImeNotify(HWND hwnd, WPARAM wParam, LPARAM lParam) 
{
    if (wParam == IMN_OPENCANDIDATE) {
        JNIEnv *env = GetEnv();
        RECT curPos;
        RECT winPos;
        UINT bits = 1;
        HIMC hIMC = ImmGetContext(hwnd);
        CANDIDATEFORM cf;

        GetWindowRect(hwnd, &winPos);        
        GetCandidatePos(&curPos);

        for (int iCandType=0; iCandType<32; iCandType++, bits<<=1) {
            if (lParam & bits) {
                cf.dwIndex = iCandType;
                cf.dwStyle = CFS_CANDIDATEPOS;
                cf.ptCurrentPos.x = curPos.left - winPos.left;
                cf.ptCurrentPos.y = curPos.top - winPos.top;
                ImmSetCandidateWindow(hIMC, &cf);
            }
        }
        ImmReleaseContext(hwnd, hIMC);
    }
}

//
// generate and post InputMethodEvent
//
void ViewContainer::SendInputMethodEvent(jstring text,
    int cClause, int* rgClauseBoundary,
    int cAttrBlock, int* rgAttrBoundary, BYTE *rgAttrValue,
    int commitedTextLength, int caretPos, int visiblePos)
{
    JNIEnv *env = GetEnv();

    // assumption for array type casting
    ASSERT(sizeof(int)==sizeof(jint));
    ASSERT(sizeof(BYTE)==sizeof(jbyte));

    // caluse information
    jintArray clauseBoundary = NULL;
    if (cClause && rgClauseBoundary) {
        // convert clause boundary offset array to java array
        clauseBoundary = env->NewIntArray(cClause+1);
        env->SetIntArrayRegion(clauseBoundary, 0, cClause+1, (jint *)rgClauseBoundary);
    }

    // attribute information
    jintArray attrBoundary = NULL;
    jbyteArray attrValue = NULL;
    if (cAttrBlock && rgAttrBoundary && rgAttrValue) {
        // convert attribute boundary offset array to java array
        attrBoundary = env->NewIntArray(cAttrBlock+1);
        env->SetIntArrayRegion(attrBoundary, 0, cAttrBlock+1, (jint *)rgAttrBoundary);
        // convert attribute value byte array to java array
        attrValue = env->NewByteArray(cAttrBlock);
        env->SetByteArrayRegion(attrValue, 0, cAttrBlock, (jbyte *)rgAttrValue);
    }

    env->CallBooleanMethod(GetView(), javaIDs.View.notifyInputMethod,
                        text, clauseBoundary, attrBoundary,
                        attrValue, commitedTextLength, caretPos, visiblePos);

    if (clauseBoundary) {
        env->DeleteLocalRef(clauseBoundary);
    }
    if (attrBoundary) {
        env->DeleteLocalRef(attrBoundary);
    }
    if (attrValue) {
        env->DeleteLocalRef(attrValue);
    }
}

// Gets the candidate position
void ViewContainer::GetCandidatePos(RECT* curPos) 
{
    JNIEnv *env = GetEnv();
    double* nativePos; 

    jdoubleArray pos = (jdoubleArray)env->CallObjectMethod(GetView(), 
                        javaIDs.View.notifyInputMethodCandidatePosRequest,
                        0);
    nativePos = env->GetDoubleArrayElements(pos, NULL);

    curPos->left = (int)nativePos[0];
    curPos->top  = (int)nativePos[1];

    env->ReleaseDoubleArrayElements(pos, nativePos, 0);
}

namespace {

class AutoTouchInputHandle {
    HTOUCHINPUT m_h;
private:
    AutoTouchInputHandle(const AutoTouchInputHandle&);
    AutoTouchInputHandle& operator=(const AutoTouchInputHandle&);
public:
    explicit AutoTouchInputHandle(LPARAM lParam): m_h((HTOUCHINPUT)lParam) {
    }
    ~AutoTouchInputHandle() {
        if (m_h) {
            ::CloseTouchInputHandle(m_h);
        }
    }
    operator HTOUCHINPUT() const {
        return m_h;
    }
};

void NotifyTouchInput(
        HWND hWnd, jobject view, jclass gestureSupportCls, 
        const TOUCHINPUT* ti, unsigned count) 
{

    JNIEnv *env = GetEnv();
    
    // TBD: set to 'true' if source device is a touch screen 
    // and to 'false' if source device is a touch pad.
    // So far assume source device on Windows is always a touch screen.
    const bool isDirect = true;

    jint modifiers = GetModifiers();
    env->CallStaticObjectMethod(gestureSupportCls, 
                                javaIDs.Gestures.notifyBeginTouchEventMID,
                                view, modifiers, jboolean(isDirect), 
                                jint(count));
    CheckAndClearException(env);

    for (; count; --count, ++ti) {
        jlong touchID = jlong(ti->dwID);

        jint eventID = 0;
        if (ti->dwFlags & TOUCHEVENTF_MOVE) {
            eventID = com_sun_glass_events_TouchEvent_TOUCH_MOVED;
        }
        if (ti->dwFlags & TOUCHEVENTF_DOWN) {
            eventID = com_sun_glass_events_TouchEvent_TOUCH_PRESSED;
        }
        if (ti->dwFlags & TOUCHEVENTF_UP) {
            eventID = com_sun_glass_events_TouchEvent_TOUCH_RELEASED;
        }

        POINT screen;
        POINT client;
        client.x = screen.x = LONG(ti->x / 100);
        client.y = screen.y = LONG(ti->y / 100);
        ScreenToClient(hWnd, &client);
        
        env->CallStaticObjectMethod(gestureSupportCls, 
                                    javaIDs.Gestures.notifyNextTouchEventMID,
                                    view, eventID, touchID, 
                                    jint(client.x), jint(client.y),
                                    jint(screen.x), jint(screen.y));
        CheckAndClearException(env);
    }

    env->CallStaticObjectMethod(
            gestureSupportCls, javaIDs.Gestures.notifyEndTouchEventMID, view);
    CheckAndClearException(env);
}

void NotifyManipulationProcessor(
        IManipulationProcessor& manipProc, 
        const TOUCHINPUT* ti, unsigned count) 
{
    for (; count; --count, ++ti) {
        if (ti->dwFlags & TOUCHEVENTF_DOWN) {
            manipProc.ProcessDownWithTime(ti->dwID, FLOAT(ti->x), FLOAT(ti->y), ti->dwTime);
        }
        if (ti->dwFlags & TOUCHEVENTF_MOVE) {
            manipProc.ProcessMoveWithTime(ti->dwID, FLOAT(ti->x), FLOAT(ti->y), ti->dwTime);
        }
        if (ti->dwFlags & TOUCHEVENTF_UP) {
            manipProc.ProcessUpWithTime(ti->dwID, FLOAT(ti->x), FLOAT(ti->y), ti->dwTime);
        }
    }
}

} // namespace

void ViewContainer::HandleViewTouchEvent(
        HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    // Preallocated arrays. One element per finger. Though so far
    // Win 7 can handle at most four touch points simultaneously, 
    // so the buffer is excessive.
    TOUCHINPUT staticTouchInputBuf[10];

    std::vector<TOUCHINPUT> dynamicTouchInputBuf;
    PTOUCHINPUT pInputs = staticTouchInputBuf;

    const UINT cInputs = static_cast<UINT>(LOWORD(wParam));
    if (ARRAYSIZE(staticTouchInputBuf) < cInputs) {
        dynamicTouchInputBuf.resize(cInputs);
        pInputs = &*dynamicTouchInputBuf.begin();
    }

    do {
        AutoTouchInputHandle inputInfo(lParam);
        if (!::GetTouchInputInfo(inputInfo, cInputs, 
                                 pInputs, sizeof(TOUCHINPUT))) {
            return;
        }
    } while(0); // scope for 'inputInfo'

    NotifyTouchInput(hWnd, GetView(), m_gestureSupportCls, pInputs, cInputs);

    if (m_manipProc) {
        NotifyManipulationProcessor(*m_manipProc, pInputs, cInputs);
    }
}

void ViewContainer::HandleViewTimerEvent(HWND hwnd, UINT_PTR timerID)
{
    if (IDT_GLASS_INERTIAPROCESSOR == timerID) {
        BOOL completed = FALSE;
        HRESULT hr = m_inertiaProc->Process(&completed);
        if (SUCCEEDED(hr) && completed) {
            StopTouchInputInertia(hwnd);
            
            JNIEnv *env = GetEnv();
            env->CallStaticVoidMethod(m_gestureSupportCls, 
                    javaIDs.Gestures.inertiaGestureFinishedMID, GetView());
            CheckAndClearException(env);
        }
    }
}

void ViewContainer::NotifyGesturePerformed(HWND hWnd, 
        bool isDirect, bool isInertia,
        FLOAT x, FLOAT y, FLOAT deltaX, FLOAT deltaY,
        FLOAT scaleDelta, FLOAT expansionDelta, FLOAT rotationDelta,
        FLOAT cumulativeDeltaX, FLOAT cumulativeDeltaY,
        FLOAT cumulativeScale, FLOAT cumulativeExpansion, 
        FLOAT cumulativeRotation) 
{
    JNIEnv *env = GetEnv();

    POINT screen;
    screen.x = LONG((x + 0.5) / 100);
    screen.y = LONG((y + 0.5) / 100);

    POINT client;
    client.x = screen.x;
    client.y = screen.y;
    ScreenToClient(hWnd, &client);

    jint modifiers = GetModifiers();
    env->CallStaticVoidMethod(m_gestureSupportCls, 
                              javaIDs.Gestures.gesturePerformedMID,
                              GetView(), modifiers, 
                              jboolean(isDirect), jboolean(isInertia),
                              jint(client.x), jint(client.y),
                              jint(screen.x), jint(screen.y),
                              deltaX / 100, deltaY / 100,
                              cumulativeDeltaX / 100, cumulativeDeltaY / 100,
                              cumulativeScale, cumulativeExpansion / 100, 
                              cumulativeRotation);
    CheckAndClearException(env);
}

void ViewContainer::StartTouchInputInertia(HWND hwnd)
{
    // TBD: check errors

    //
    // Collect initial inertia data 
    //

    FLOAT vX, vY;
    m_manipProc->GetVelocityX(&vX);
    m_manipProc->GetVelocityY(&vY);
    
    const FLOAT VELOCITY_THRESHOLD = 10.0f;

    if (fabs(vX) < VELOCITY_THRESHOLD && fabs(vY) < VELOCITY_THRESHOLD) {
        return;
    }

    // TBD: check errors
    POINT origin;
    GetCursorPos(&origin);

    //
    // Setup inertia.
    //
    
    m_inertiaProc->Reset();

    m_inertiaProc->put_DesiredDeceleration(0.23f);

    // Set initial origins.
    m_inertiaProc->put_InitialOriginX(origin.x * 100.0f);
    m_inertiaProc->put_InitialOriginY(origin.y * 100.0f);
    
    // Set initial velocities.    
    m_inertiaProc->put_InitialVelocityX(vX);
    m_inertiaProc->put_InitialVelocityY(vY);
    
    // TBD: check errors
    ::SetTimer(hwnd, IDT_GLASS_INERTIAPROCESSOR, 16, NULL);
}

void ViewContainer::StopTouchInputInertia(HWND hwnd)
{
    // TBD: check errors
    ::KillTimer(hwnd, IDT_GLASS_INERTIAPROCESSOR);
}


extern "C" {
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinGestureSupport__1initIDs(
        JNIEnv *env, jclass cls) 
{
    javaIDs.Gestures.gesturePerformedMID = 
        env->GetStaticMethodID(cls, "gesturePerformed",
                                "(Lcom/sun/glass/ui/View;IZZIIIIFFFFFFF)V");
    CheckAndClearException(env);
    
    javaIDs.Gestures.inertiaGestureFinishedMID = 
        env->GetStaticMethodID(cls, "inertiaGestureFinished",
                                "(Lcom/sun/glass/ui/View;)V");
    CheckAndClearException(env);

    javaIDs.Gestures.notifyBeginTouchEventMID = 
        env->GetStaticMethodID(cls, "notifyBeginTouchEvent", 
                                "(Lcom/sun/glass/ui/View;IZI)V");
    CheckAndClearException(env);

    javaIDs.Gestures.notifyNextTouchEventMID = 
        env->GetStaticMethodID(cls, "notifyNextTouchEvent", 
                                "(Lcom/sun/glass/ui/View;IJIIII)V");
    CheckAndClearException(env);

    javaIDs.Gestures.notifyEndTouchEventMID = 
        env->GetStaticMethodID(cls, "notifyEndTouchEvent", 
                                "(Lcom/sun/glass/ui/View;)V");
    CheckAndClearException(env);
}

} // extern "C"
