/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */
#include "common.h"
#include "GlassApplication.h"
#include "GlassView.h"
#include "GlassWindow.h"
#include "Pixels.h"

#include "com_sun_glass_events_ViewEvent.h"
#include "com_sun_glass_ui_win_WinView.h"

// Helper LEAVE_MAIN_THREAD for GlassView
#define LEAVE_MAIN_THREAD_WITH_view  \
    GlassView * view;  \
    LEAVE_MAIN_THREAD;  \
    ARG(view) = (GlassView*)jlong_to_ptr(ptr);

GlassView::GlassView(jobject jrefThis) :
    m_fullScreenWindow(NULL),
    m_hostHwnd(NULL)
{
    m_grefThis = GetEnv()->NewGlobalRef(jrefThis);
}

GlassView::~GlassView()
{
    if (m_grefThis) {
        GetEnv()->DeleteGlobalRef(m_grefThis);
    }
}

BOOL GlassView::Close()
{
    if (m_fullScreenWindow) {
        FullScreenWindow * fs = dynamic_cast<FullScreenWindow*>(m_fullScreenWindow);
        if (fs) {
            fs->Close();
        }
        m_fullScreenWindow = NULL;
    }

    return JNI_TRUE;
}

BOOL GlassView::EnterFullScreen(BOOL animate, BOOL keepRatio)
{
    GlassWindow *pWindow = GlassWindow::FromHandle(GetHostHwnd());
    if (pWindow && !pWindow->IsChild()) {
        m_fullScreenWindow = pWindow;
    } else {
        // create new FullScreen window to handle "ownerless" views
        FullScreenWindow * w = new FullScreenWindow();
        w->Create();
        m_fullScreenWindow = w;
    }

    BOOL ret = m_fullScreenWindow->EnterFullScreenMode(this, animate, keepRatio);
    if (ret) {
        NotifyFullscreen(true);
    }

    return ret;
}

void GlassView::ExitFullScreen(BOOL animate)
{
    if (!m_fullScreenWindow) {
        return;
    }

    m_fullScreenWindow->ExitFullScreenMode(animate);
    m_fullScreenWindow = NULL;
    NotifyFullscreen(false);
}

void GlassView::NotifyFullscreen(bool entered)
{
    GetEnv()->CallVoidMethod(GetView(), javaIDs.View.handleViewEvent,
            entered ? com_sun_glass_events_ViewEvent_FULLSCREEN_ENTER :
            com_sun_glass_events_ViewEvent_FULLSCREEN_EXIT);
}

void GlassView::SetHostHwnd(HWND m_hostHwnd)
{
    if (this->m_hostHwnd == m_hostHwnd) {
        return;
    }

    if (this->m_hostHwnd) {
        this->m_hostHwnd = NULL;

        GetEnv()->CallVoidMethod(GetView(), javaIDs.View.handleViewEvent,
                com_sun_glass_events_ViewEvent_REMOVE);
        CheckAndClearException(GetEnv());
    }

    this->m_hostHwnd = m_hostHwnd;

    if (m_hostHwnd) {
        GetEnv()->CallVoidMethod(GetView(), javaIDs.View.handleViewEvent,
                com_sun_glass_events_ViewEvent_ADD);
        CheckAndClearException(GetEnv());
    }
}

void GlassView::EnableInputMethodEvents(BOOL enable)
{
    m_InputMethodEventsEnabled = enable;
}

/*
 * JNI methods section
 *
 */

extern "C" {

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _initIDs
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinView__1initIDs
    (JNIEnv *env, jclass cls)
{
    javaIDs.View.notifyResize = env->GetMethodID(cls, "notifyResize", "(II)V");
    ASSERT(javaIDs.View.notifyResize);

    javaIDs.View.notifyRepaint = env->GetMethodID(cls, "notifyRepaint", "(IIII)V");
    ASSERT(javaIDs.View.notifyRepaint);

    javaIDs.View.notifyKey = env->GetMethodID(cls, "notifyKey", "(II[CI)V");
    ASSERT(javaIDs.View.notifyKey);

    javaIDs.View.notifyMouse = env->GetMethodID(cls, "notifyMouse", "(IIIIIIIZZ)V");
    ASSERT(javaIDs.View.notifyMouse);

    javaIDs.View.notifyMenu = env->GetMethodID(cls, "notifyMenu", "(IIIIZ)V");
    ASSERT(javaIDs.View.notifyMenu);

    javaIDs.View.notifyScroll = env->GetMethodID(cls, "notifyScroll", "(IIIIDDIIIIIDD)V");
    ASSERT(javaIDs.View.notifyScroll);

    javaIDs.View.notifyInputMethod = env->GetMethodID(cls, "notifyInputMethod", "(Ljava/lang/String;[I[I[BIII)V");
    ASSERT(javaIDs.View.notifyInputMethod);

    javaIDs.View.notifyDragEnter = env->GetMethodID(cls, "notifyDragEnter", "(IIIII)I");
    ASSERT(javaIDs.View.notifyDragEnter);

    javaIDs.View.notifyDragOver = env->GetMethodID(cls, "notifyDragOver", "(IIIII)I");
    ASSERT(javaIDs.View.notifyDragOver);

    javaIDs.View.notifyDragLeave = env->GetMethodID(cls, "notifyDragLeave", "()V");
    ASSERT(javaIDs.View.notifyDragLeave);

    javaIDs.View.notifyDragDrop = env->GetMethodID(cls, "notifyDragDrop", "(IIIII)I");
    ASSERT(javaIDs.View.notifyDragDrop);

    javaIDs.View.handleViewEvent = env->GetMethodID(cls, "handleViewEvent", "(I)V");
    ASSERT(javaIDs.View.handleViewEvent);

    javaIDs.View.getWidth = env->GetMethodID(cls, "getWidth", "()I");
    ASSERT(javaIDs.View.getWidth);

    javaIDs.View.getHeight = env->GetMethodID(cls, "getHeight", "()I");
    ASSERT(javaIDs.View.getHeight);

    javaIDs.View.notifyInputMethodCandidatePosRequest = env->GetMethodID(cls, "notifyInputMethodCandidatePosRequest", "(I)[D");
    ASSERT(javaIDs.View.notifyInputMethodCandidatePosRequest);

    javaIDs.View.ptr = env->GetFieldID(cls, "ptr", "J");
    ASSERT(javaIDs.View.ptr);
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _getMultiClickTime_impl
 * Signature: ()J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_win_WinView__1getMultiClickTime_1impl
    (JNIEnv *env, jclass cls)
{
    return (jlong)::GetDoubleClickTime();
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _getMultiClickMaxX_impl
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_win_WinView__1getMultiClickMaxX_1impl
    (JNIEnv *env, jclass cls)
{
    return (jint)::GetSystemMetrics(SM_CXDOUBLECLK);
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _getMultiClickMaxY_impl
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_win_WinView__1getMultiClickMaxY_1impl
    (JNIEnv *env, jclass cls)
{
    return (jint)::GetSystemMetrics(SM_CYDOUBLECLK);
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _create
 * Signature: ()J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_win_WinView__1create
    (JNIEnv *env, jobject jview, jobject caps)
{
    return (jlong)new GlassView(jview);
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _close
 * Signature: (J)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_win_WinView__1close
    (JNIEnv *env, jobject jview, jlong ptr)
{
    ENTER_MAIN_THREAD_AND_RETURN(jboolean)
    {
        jboolean result = bool_to_jbool(view->Close());
        delete view;
        return result;
    }
    LEAVE_MAIN_THREAD_WITH_view;

    return PERFORM_AND_RETURN();
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _getNativeView
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_win_WinView__1getNativeView
  (JNIEnv *env, jobject _this, jlong viewPtr)
{
    GlassView * view = (GlassView *)viewPtr;

    return (jlong)view->GetHostHwnd();
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _setParent
 * Signature: (JJ)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinView__1setParent
  (JNIEnv *env, jobject _this, jlong ptr, jlong parentPtr)
{
    ENTER_MAIN_THREAD()
    {
        // The action may send ADD/REMOVE events. Let them be on the main thread
        view->SetHostHwnd((HWND)parentPtr);
    }
    jlong parentPtr;
    LEAVE_MAIN_THREAD_WITH_view;

    ARG(parentPtr) = parentPtr;
    PERFORM();
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _getX
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_win_WinView__1getX
  (JNIEnv *env, jobject _this, jlong ptr)
{
    ENTER_MAIN_THREAD_AND_RETURN(jint)
    {
        HWND hWnd = view->GetHostHwnd();

        if (!hWnd) {
            return 0;
        }

        RECT rect;
        POINT p = {0, 0};

        ::GetWindowRect(hWnd, &rect);
        ::ClientToScreen(hWnd, &p);

        return p.x - rect.left;
    }
    LEAVE_MAIN_THREAD_WITH_view;

    return PERFORM_AND_RETURN();
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _getY
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_win_WinView__1getY
  (JNIEnv *env, jobject _this, jlong ptr)
{
    ENTER_MAIN_THREAD_AND_RETURN(jint)
    {
        HWND hWnd = view->GetHostHwnd();

        if (!hWnd) {
            return 0;
        }

        RECT rect;
        POINT p = {0, 0};

        ::GetWindowRect(hWnd, &rect);
        ::ClientToScreen(hWnd, &p);

        return p.y - rect.top;
    }
    LEAVE_MAIN_THREAD_WITH_view;

    return PERFORM_AND_RETURN();
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _beginPaint
 * Signature: (J)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_win_WinView__1begin
  (JNIEnv *env, jobject jview, jlong ptr, jboolean isValid)
{
    return JNI_TRUE;
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _endPaint
 * Signature: (JZ)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinView__1end
  (JNIEnv *env, jobject jview, jlong ptr, jboolean isValid, jboolean flush)
{
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _uploadPixels
 * Signature: (JLcom/sun/glass/ui/Pixels;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinView__1uploadPixels
    (JNIEnv *env, jobject jThis, jlong ptr, jobject jPixels)
{
    ENTER_MAIN_THREAD()
    {
        HWND hWnd = view->GetHostHwnd();
        if (!::IsWindow(hWnd)) {
            //NOTE: uploadPixels() may be invoked from a thread other than the
            //      toolkit thread. Therefore, when SendMessage() is finally
            //      processed, the hWnd may be stale already, and hence
            //      the pWindow == NULL shouldn't be surprising.
            return;
        }

        GlassWindow *pWindow = GlassWindow::FromHandle(hWnd);
        Pixels pixels(GetEnv(), jPixels);

        if (!pWindow || !pWindow->IsTransparent()) {
            // Either a non-glass window (FullScreenWindow), or not transparent
            BITMAPINFOHEADER bmi;

            ZeroMemory(&bmi, sizeof(bmi));
            bmi.biSize = sizeof(bmi);
            bmi.biWidth = pixels.GetWidth();
            bmi.biHeight = -pixels.GetHeight();
            bmi.biPlanes = 1;
            bmi.biBitCount = 32;
            bmi.biCompression = BI_RGB;

            HDC hdcDst = ::GetDC(hWnd);
            ::SetDIBitsToDevice(
                    hdcDst,
                    0, 0, pixels.GetWidth(), pixels.GetHeight(),
                    0, 0,
                    0, pixels.GetHeight(),
                    pixels.GetBits(),
                    (BITMAPINFO*)&bmi, DIB_RGB_COLORS);
            ::ReleaseDC(hWnd, hdcDst);
        } else { // IsTransparent() == TRUE
            // http://msdn.microsoft.com/en-us/library/ms997507.aspx
            RECT rect;
            ::GetWindowRect(hWnd, &rect);
            SIZE size = { rect.right - rect.left, rect.bottom - rect.top };

            if (size.cx != pixels.GetWidth() || size.cy != pixels.GetHeight()) {
                //XXX: should report a error? OTOH, we could proceed, but
                //this will cause the window to resize to the size of the bitmap
                return;
            }

            POINT ptSrc = { 0, 0 };
            POINT ptDst = { rect.left, rect.top };

            BLENDFUNCTION bf;
            bf.SourceConstantAlpha = pWindow->GetAlpha();
            bf.AlphaFormat = AC_SRC_ALPHA;
            bf.BlendOp = AC_SRC_OVER;
            bf.BlendFlags = 0;

            DIBitmap bitmap(pixels);

            HDC hdcDst = ::GetDC(NULL);
            HDC hdcSrc = ::CreateCompatibleDC(NULL);
            HBITMAP oldBitmap = (HBITMAP)::SelectObject(hdcSrc, bitmap);

            ::UpdateLayeredWindow(hWnd, hdcDst, &ptDst, &size, hdcSrc, &ptSrc,
                    RGB(0, 0, 0), &bf, ULW_ALPHA);

            ::SelectObject(hdcSrc, oldBitmap);
            ::DeleteDC(hdcSrc);
            ::ReleaseDC(NULL, hdcDst);
        }
    }
    DECL_jobject(jPixels);
    LEAVE_MAIN_THREAD_WITH_view;

    ARG(jPixels) = jPixels;
    PERFORM();
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _repaint
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinView__1repaint
  (JNIEnv *env, jobject jview, jlong ptr)
{
    ENTER_MAIN_THREAD()
    {
        if (view->GetHostHwnd()) {
            ::InvalidateRect(view->GetHostHwnd(), NULL, FALSE);
        }
    }
    LEAVE_MAIN_THREAD_WITH_view;

    PERFORM();
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _enterFullscreen
 * Signature: (JZZZ)V
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_win_WinView__1enterFullscreen
(JNIEnv *env, jobject jview, jlong ptr, jboolean animate, jboolean keepRatio, jboolean hideCursor)
{
    ENTER_MAIN_THREAD_AND_RETURN(jboolean)
    {
        return bool_to_jbool(view->EnterFullScreen(animate, keepRatio));
    }
    BOOL animate;
    BOOL keepRatio;
    LEAVE_MAIN_THREAD_WITH_view;

    ARG(animate) = (BOOL)animate;
    ARG(keepRatio) = (BOOL)keepRatio;
    return PERFORM_AND_RETURN();
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _exitFullscreen
 * Signature: (JZ)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinView__1exitFullscreen
(JNIEnv *env, jobject jview, jlong ptr, jboolean animate)
{
    ENTER_MAIN_THREAD()
    {
        view->ExitFullScreen(animate);
    }
    BOOL animate;
    LEAVE_MAIN_THREAD_WITH_view;

    ARG(animate) = (BOOL)animate;
    PERFORM();
}

/*
 * Class:     com_sun_glass_ui_win_WinView
 * Method:    _enableInputMethodEvents
 * Signature: (JZ)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinView__1enableInputMethodEvents
  (JNIEnv *env, jobject jview, jlong ptr, jboolean enable)
{
    GlassView* view = (GlassView*)jlong_to_ptr(ptr);
    view->EnableInputMethodEvents(jbool_to_bool(enable));
}
}   // extern "C"
