/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */
#include "common.h"
#include "GlassApplication.h"
#include "GlassClipboard.h"
#include "GlassScreen.h"
#include "Timer.h"
#include "com_sun_glass_ui_win_WinApplication.h"
#include "com_sun_glass_ui_win_WinSystemClipboard.h"


/**********************************
 * GlassApplication
 **********************************/

static LPCTSTR szGlassToolkitWindow = TEXT("GlassToolkitWindowClass");

#define WM_DO_ACTION        (WM_USER+1)
#define WM_DO_ACTION_LATER  (WM_USER+2)

GlassApplication *GlassApplication::pInstance = NULL;
bool GlassApplication::sm_shouldLeaveNestedLoop = false;
JGlobalRef<jobject> GlassApplication::sm_nestedLoopReturnValue;

GlassApplication::GlassApplication(jobject jrefThis) : BaseWnd()
{
    m_grefThis = GetEnv()->NewGlobalRef(jrefThis);
    m_clipboard = NULL;
    m_hNextClipboardView = NULL;
    m_mainThreadId = ::GetCurrentThreadId();

    Create(NULL, 0, 0, 400, 300, TEXT(""), 0, 0, NULL);
}

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

LPCTSTR GlassApplication::GetWindowClassNameSuffix()
{
    return szGlassToolkitWindow;
}

LRESULT GlassApplication::WindowProc(UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg) {
        case WM_DO_ACTION:
        case WM_DO_ACTION_LATER:
            {
                Action * action = (Action *)wParam;
                action->Do();
                if (msg == WM_DO_ACTION_LATER) {
                    delete action;
                }
            }
            return 0;
        case WM_CREATE:
            pInstance = this;        
            STRACE(_T("GlassApplication: created."));
            break;
        case WM_DESTROY:
            //Alarm clipboard dispose if any.
            //Please, use RegisterClipboardViewer(NULL) instead of UnregisterClipboardViewer.
            RegisterClipboardViewer(NULL);
            return 0;
        case WM_NCDESTROY: 
            // pInstance is deleted in BaseWnd::StaticWindowProc
            pInstance = NULL;
            STRACE(_T("GlassApplication: destroyed."));
            return 0;
        case WM_CHANGECBCHAIN:
            if ((HWND)wParam == m_hNextClipboardView) {
                m_hNextClipboardView = (HWND)lParam;
            } else if (NULL != m_hNextClipboardView) {
                ::SendMessage(m_hNextClipboardView, WM_CHANGECBCHAIN, wParam, lParam);
            }
            break;
        case WM_DRAWCLIPBOARD:
            if (NULL != m_clipboard) {
                GetEnv()->CallVoidMethod(m_clipboard, midContentChanged); 
                CheckAndClearException(GetEnv());
            }
            if (NULL != m_hNextClipboardView) {
                ::SendMessage(m_hNextClipboardView, WM_DRAWCLIPBOARD, wParam, lParam);
            }            
            break;
        case WM_DISPLAYCHANGE:
            GlassScreen::HandleDisplayChange();
            break;
    }
    return ::DefWindowProc(GetHWND(), msg, wParam, lParam);
}

/* static */
void GlassApplication::RegisterClipboardViewer(jobject clipboard)
{
    JNIEnv *env = GetEnv();
    if (NULL != m_clipboard) {
        //Alarm dispose. We need to release all native resources 
        //of previous instance.
        //It means that user skipped ClipboardAssistance close.  
        JLObject _clipboard(env, env->NewLocalRef(m_clipboard));
        Java_com_sun_glass_ui_win_WinSystemClipboard_dispose(env, _clipboard);
    }
    if (NULL != clipboard) {
        m_clipboard = env->NewGlobalRef(clipboard);
        m_hNextClipboardView = ::SetClipboardViewer(GetHWND()) ;
        STRACE(_T("RegisterClipboardViewer"));
    }
}

/* static */
void GlassApplication::UnregisterClipboardViewer()
{
    if (NULL != m_hNextClipboardView) {
        ::ChangeClipboardChain(GetHWND(), m_hNextClipboardView);
        m_hNextClipboardView = NULL;
        STRACE(_T("UnregisterClipboardViewer"));
    } 
    if (NULL != m_clipboard) {
        GetEnv()->DeleteGlobalRef(m_clipboard);
        m_clipboard = NULL;
    }     
}

/* static */
void GlassApplication::ExecAction(Action *action)
{
    if (!pInstance) {
        return;
    }
    ::SendMessage(pInstance->GetHWND(), WM_DO_ACTION, (WPARAM)action, (LPARAM)0);
}

/* static */
void GlassApplication::ExecActionLater(Action *action)
{
    if (!pInstance) {
        delete action;
        return;
    }
    ::PostMessage(pInstance->GetHWND(), WM_DO_ACTION_LATER, (WPARAM)action, (LPARAM)0);
}

/* static */
jobject GlassApplication::EnterNestedEventLoop(JNIEnv * env)
{
    sm_shouldLeaveNestedLoop = false;

    MSG msg;
    while (GlassApplication::GetInstance()
            && !sm_shouldLeaveNestedLoop
            && ::GetMessage(&msg, NULL, 0, 0) > 0)
    {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }

    sm_shouldLeaveNestedLoop = false;

    if (!sm_nestedLoopReturnValue) {
        return NULL;
    }

    jobject ret = env->NewLocalRef(sm_nestedLoopReturnValue);
    sm_nestedLoopReturnValue.Attach(env, NULL);
    return ret;
}

/* static */
void GlassApplication::LeaveNestedEventLoop(JNIEnv * env, jobject retValue)
{
    sm_nestedLoopReturnValue.Attach(env, retValue);
    sm_shouldLeaveNestedLoop = true;
}

/*******************************************************
 * JNI section
 *******************************************************/

extern "C" {

/*
 * Class:     com_sun_glass_ui_win_WinApplication
 * Method:    initIDs
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinApplication_initIDs
  (JNIEnv *env, jclass cls)
{
    //NOTE: substitute the cls
    cls = (jclass)env->FindClass("java/lang/Runnable");

    javaIDs.Runnable.run = env->GetMethodID(cls, "run", "()V");
    ASSERT(javaIDs.Runnable.run);
}

/*
 * Class:     com_sun_glass_ui_win_WinApplication
 * Method:    _init
 * Signature: ()J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_win_WinApplication__1init
  (JNIEnv *env, jobject _this)
{
    // TODO: if/when we introduce JavaFX launcher, DPI awareness should
    // be specified in its manifest instead of this call below
    if (IS_WINVISTA) {
        ::SetProcessDPIAware();
    }

    GlassApplication *pApp = new GlassApplication(_this);
    
    HWND hWnd = GlassApplication::GetToolkitHWND();
    if (hWnd == NULL) {
        delete pApp;
    }

    return (jlong)hWnd;
}

/*
 * Class:     com_sun_glass_ui_win_WinApplication
 * Method:    _runLoop
 * Signature: ([Ljava/lang/String;Lcom/sun/glass/ui/Launchable;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinApplication__1runLoop
  (JNIEnv * env, jobject self, jobjectArray args, jobject jLaunchable)
{
    OLEHolder _ole_;
    if (jLaunchable != NULL) {
        jclass cls = env->GetObjectClass(jLaunchable);
        jmethodID finishLaunchingMethod = env->GetMethodID(cls,
                "finishLaunching", "([Ljava/lang/String;)V");
        env->CallVoidMethod(jLaunchable, finishLaunchingMethod, args);
        CheckAndClearException(env);
    }

    MSG msg;
    // The GlassApplication instance may be destroyed in a nested loop.
    // Note that we leave the WM_QUIT message on the queue but who cares?
    while (GlassApplication::GetInstance() && ::GetMessage(&msg, NULL, 0, 0) > 0) {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
}

/*
 * Class:     com_sun_glass_ui_win_WinApplication
 * Method:    _terminateLoop
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinApplication__1terminateLoop
  (JNIEnv * env, jobject self)
{
    HWND hWnd = GlassApplication::GetToolkitHWND();
    if (::IsWindow(hWnd)) {
        ::DestroyWindow(hWnd);
    }
}

/*
 * Class:     com_sun_glass_ui_win_WinApplication
 * Method:    _enterNestedEventLoop
 * Signature: ()Ljava/lang/Object;
 */
JNIEXPORT jobject JNICALL Java_com_sun_glass_ui_win_WinApplication__1enterNestedEventLoop
  (JNIEnv * env, jobject self)
{
    return GlassApplication::EnterNestedEventLoop(env);
}

/*
 * Class:     com_sun_glass_ui_win_WinApplication
 * Method:    _leaveNestedEventLoop
 * Signature: (Ljava/lang/Object;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinApplication__1leaveNestedEventLoop
  (JNIEnv * env, jobject self, jobject retValue)
{
    GlassApplication::LeaveNestedEventLoop(env, retValue);
}

/*
 * Class:     com_sun_glass_ui_Application
 * Method:    _invokeAndWait
 * Signature: (Ljava/lang/Runnable;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinApplication__1invokeAndWait
  (JNIEnv * env, jobject japplication, jobject runnable)
{
    ENTER_MAIN_THREAD()
    {
        GetEnv()->CallVoidMethod(runnable, javaIDs.Runnable.run);
        CheckAndClearException(GetEnv());
    }
    DECL_jobject(runnable);
    LEAVE_MAIN_THREAD;

    ARG(runnable) = runnable;
    PERFORM();
}

/*
 * Class:     com_sun_glass_ui_Application
 * Method:    _invokeLater
 * Signature: (Ljava/lang/Runnable;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinApplication__1invokeLater
  (JNIEnv * env, jobject japplication, jobject runnable)
{
    ENTER_MAIN_THREAD()
    {
        GetEnv()->CallVoidMethod(runnable, javaIDs.Runnable.run);
        CheckAndClearException(GetEnv());
    }
    DECL_jobject(runnable);
    LEAVE_MAIN_THREAD_LATER;

    ARG(runnable) = runnable;
    PERFORM_LATER();
}

/*
 * Class:     com_sun_glass_ui_Application
 * Method:    _postOnEventQueue
 * Signature: (Ljava/lang/Runnable;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinApplication__1postOnEventQueue
  (JNIEnv * env, jobject japplication, jobject runnable)
{
    // On Windows these actions are equivalent
    Java_com_sun_glass_ui_win_WinApplication__1invokeLater(env, japplication, runnable);
}

} // extern "C"

