/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 */

#include <stdlib.h>
#include "mat_common.h"

#include "com_sun_glass_ui_Application.h"
#include "com_sun_glass_ui_View.h"
#include "com_sun_glass_ui_View_Capability.h"
#include "com_sun_glass_events_KeyEvent.h"
#include "com_sun_glass_events_MouseEvent.h"

/*
 * Class:     com_sun_glass_ui_View
 * Method:    _create
 * Signature: (Ljava/util/Map;)J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_x11_X11View__1create
  (JNIEnv *env, jobject jview, jobject jCapabilities) {
    DEBUG_PRINTF("X11View_create\n");
    jboolean is3D = JNI_FALSE;

    // TODO - what capability values do we really care about ?
    // we don't care about 3D, but leaving in the check as a template.
    jobject j3dKey = (*env)->NewObject(env, mat_jIntegerClass, (*env)->GetMethodID(env, mat_jIntegerClass, "<init>", "(I)V"), com_sun_glass_ui_View_Capability_k3dKeyValue);
    jobject j3dKeyValue = (*env)->CallObjectMethod(env, jCapabilities, mat_jMapGetMethod, j3dKey);
    if (j3dKeyValue != NULL)
    {
        is3D = (*env)->CallBooleanMethod(env, j3dKeyValue, mat_jBooleanValueMethod);
    }

    GlassView* view = (GlassView*)malloc(sizeof(GlassView));
    view->jview = (*env)->NewGlobalRef(env,jview); //cache handle to ourselves - gc issue ?

    GLASS_CHECK_EXCEPTION(env);

    return ptr_to_jlong(view);
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _getNativeView
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_x11_X11View__1getNativeView
  (JNIEnv *env, jobject jview, jlong ptr) {
    DEBUG_PRINTF("X11View_getNativeView\n");
    return 0; // nothing coresponds to the native view for X11.
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _getX
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_x11_X11View__1getX
  (JNIEnv *env, jobject jview, jlong ptr) {
    DEBUG_PRINTF("X11View_getX\n");
    return 0; // No inset from native window
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _getY
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_x11_X11View__1getY
  (JNIEnv *env, jobject jview, jlong ptr) {
    DEBUG_PRINTF("X11View_getX\n");
    return 0; // No inset from native window
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _setSize
 * Signature: (JII)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11View__1setSize
  (JNIEnv *env, jobject jview, jlong ptr, jint width, jint height) {
    DEBUG_PRINTF("X11View_setSize\n");
    printf("TODO Warning: check that setSize does not need some work\n");
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _setParent
 * Signature: (JJ)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11View__1setParent
  (JNIEnv *env, jobject jview, jlong ptr, jlong parentPtr) {
    DEBUG_PRINTF("X11View_setParent\n");
    // does nothing
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _close
 * Signature: (J)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11View__1close
  (JNIEnv *env, jclass jview, jlong ptr) {
    DEBUG_PRINTF("X11View_close\n");

    GlassView * view = (GlassView*)jlong_to_ptr(ptr);
    if (view != NULL) {
        free(view);
    }
    return JNI_TRUE;
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _begin
 * Signature: (JZ)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11View__1begin
  (JNIEnv *env, jobject jview, jlong ptr, jboolean isValid) {
//    DEBUG_PRINTF("X11View_begin\n");

    matLock();

    return JNI_TRUE;
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _end
 * Signature: (JZZ)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11View__1end
  (JNIEnv *env, jobject jview, jlong ptr, jboolean isValid, jboolean flush) {
//    DEBUG_PRINTF("X11View_end\n");
    matUnlock();
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _uploadPixelsToWindow
 * Signature: (JLcom/sun/glass/ui/Pixels;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11View__1uploadPixelsToWindow
  (JNIEnv *env, jobject jview, jlong viewPtr, jlong windowPtr, jobject pixels) {
    DEBUG_PRINTF("X11View_uploadPixels\n");


    if (pixels == NULL) {
        printf("Warning: null pixels passed to Window,uploadPixel");
        return;
    }

    if (windowPtr) {
        (*env)->CallVoidMethod(env, pixels, mat_jPixelsAttachData, windowPtr);
        GLASS_CHECK_EXCEPTION(env);
    }
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _enterFullscreen
 * Signature: (JZZZ)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11View__1enterFullscreen
  (JNIEnv *env, jobject jview, jlong ptr, jboolean animate, jboolean keepRatio, jboolean hideCursor) {
    DEBUG_PRINTF("X11View_enterFullscreen - unimp\n");
    return 0;
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _exitFullscreen
 * Signature: (JZ)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11View__1exitFullscreen
  (JNIEnv *env, jobject jview, jlong ptr, jboolean animate) {
    DEBUG_PRINTF("X11View_exitFullscreen - unimp\n");
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _repaint
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11View__1repaint
  (JNIEnv *env, jobject jview, jlong ptr) {
    DEBUG_PRINTF("X11View_repaint\n");
    mat_JNU_ThrowByName(env,mat_RuntimeException,"Unimplemented");
}

static int xButtonToMat(int xbutton) {
    if (xbutton == 0) {
        return com_sun_glass_events_MouseEvent_BUTTON_NONE;
    } else if (xbutton == Button1) {
        return com_sun_glass_events_MouseEvent_BUTTON_LEFT;
    } else if (xbutton == Button3) {
        return com_sun_glass_events_MouseEvent_BUTTON_RIGHT;
    } else {
        return com_sun_glass_events_MouseEvent_BUTTON_OTHER;
    }
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _getMultiClickTime
 * Signature: ()J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_x11_X11View__1getMultiClickTime_1impl
    (JNIEnv *env, jclass cls)
{
    //TODO
    return (jlong)300;
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _getMultiClickMaxX
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_x11_X11View__1getMultiClickMaxX_1impl
    (JNIEnv *env, jclass cls)
{
    return (jint)2;
}

/*
 * Class:     com_sun_glass_ui_x11_X11View
 * Method:    _getMultiClickMaxY
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_x11_X11View__1getMultiClickMaxY_1impl
    (JNIEnv *env, jclass cls)
{
    //TODO
    return (jint)2;
}

void mat_MouseEnterEvent(XEvent *evt,jboolean enter) {
    GlassWindow *gw = mat_matchXWindow(evt->xexpose.window);
    if (gw != NULL && gw->jview != NULL) {

        int mapped_button = xButtonToMat(gw->button);

        (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jViewNotifyMouse,
                (enter ?
                    com_sun_glass_events_MouseEvent_ENTER :
                    com_sun_glass_events_MouseEvent_EXIT),
                mapped_button,
                evt->xcrossing.x,evt->xcrossing.y,
                evt->xcrossing.x_root, evt->xcrossing.y_root,
                0, // todo: modifiers
                JNI_FALSE, // todo: isPopupTrigger
                JNI_FALSE // todo: isSynthesized
                );

        GLASS_CHECK_EXCEPTION(gw->env);
    }
}

void mat_MouseButtonEvent(XEvent *evt, jboolean pressed) {
    GlassWindow *gw = mat_matchXWindow(evt->xexpose.window);
    if (gw != NULL && gw->jview != NULL) {
        if (pressed) {
            // if we don't have an active button, remember it.
            // used by motion and enter events.
            if (!gw->button) {
                gw->button = evt->xbutton.button;
            }
        } else {
            // the button released is our active button, release it
            // and check for another pressed button.This partly becase
            // motion events use a "remembered" button (gw->button) and the
            // case of left down, right down, left up, seems to need a motion
            // button of right.
            if (gw->button == evt->xbutton.button) {
                gw->button = 0;
                int res = evt->xbutton.state;
                // now check mask in case another was active
                switch(evt->xbutton.button) {
                    case Button1:
                        res &= ~ Button1Mask;
                        break;
                    case Button2:
                        res &= ~ Button2Mask;
                        break;
                    case Button3:
                        res &= ~ Button3Mask;
                        break;
                }
                if ((res & Button1Mask) == Button1Mask) {
                    gw->button = Button1;
                } else if ((res & Button3Mask) == Button3Mask) {
                    gw->button = Button3;
                } else if ((res & Button2Mask) == Button2Mask) {
                    gw->button = Button2;
                } else {
                    gw->button = 0;
                }
            }
        }

        int mapped_button = xButtonToMat(evt->xbutton.button);

        (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jViewNotifyMouse,
                (pressed ?
                    com_sun_glass_events_MouseEvent_DOWN:
                    com_sun_glass_events_MouseEvent_UP),
                mapped_button,
                evt->xbutton.x,evt->xbutton.y,
                evt->xbutton.x_root, evt->xbutton.y_root,
                0, // todo: modifiers
                JNI_FALSE, // todo isPopupTrigger
                JNI_FALSE // todo: isSynthesized
                );
        GLASS_CHECK_EXCEPTION(gw->env);
    }
}

void mat_MouseMotionEvent(XEvent *evt) {
    GlassWindow *gw = mat_matchXWindow(evt->xexpose.window);
    if (gw != NULL && gw->jview != NULL) {
        int mapped_button = xButtonToMat(gw->button);

        (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jViewNotifyMouse,
                (evt->xmotion.state != 0 ?
                    com_sun_glass_events_MouseEvent_DRAG:
                    com_sun_glass_events_MouseEvent_MOVE),
                mapped_button,
                evt->xmotion.x,evt->xmotion.y,
                evt->xmotion.x_root, evt->xmotion.y_root,
                0, // todo: modifiers
                JNI_FALSE, // todo isPopupTrigger
                JNI_FALSE // todo: isSynthesized
                );
        GLASS_CHECK_EXCEPTION(gw->env);
    }
}


void mat_KeyEvent(XEvent *event, jboolean pressed) {
    GlassWindow *gw = mat_matchXWindow(event->xexpose.window);

    if (gw != NULL && gw->jview != NULL) {

        char buffer[20];
        KeySym xkeysym;
        buffer[0] = 0;
        /* FIXME This only handles Latin-1 characters - if that. */
        int len = XLookupString((XKeyEvent*)event,buffer,sizeof(buffer),
            &xkeysym, NULL);

        if (MAT_X11_DEBUG) {
            printf("keyEvent sym=%d/%x %s %d keycode %d/%x mod %x\n",
                (int)xkeysym, (int)xkeysym,
                buffer, len,
                event->xkey.keycode,
                event->xkey.keycode,
                event->xkey.state);
        }

        int keycode = mat_GetJavaKeyCode(xkeysym);
        jcharArray jChars = NULL;
        int modifiers = 0;

        if (event->xkey.state & ShiftMask) {
            modifiers |= com_sun_glass_events_KeyEvent_MODIFIER_SHIFT;
        }
        if (event->xkey.state & ControlMask) {
            modifiers |= com_sun_glass_events_KeyEvent_MODIFIER_CONTROL;
        }
        //Todo: Alt ? any of the other modifyier keys ?

        // always return a char array, even if zero length
        jChars = (*gw->env)->NewCharArray(gw->env, len);
        if (len > 0) {
             jchar jbuffer[20];
             int i = len > 20 ? 20 : len;
             while (--i >= 0)
               jbuffer[i] = (jchar) (buffer[i] & 0xff);
             (*gw->env)->SetCharArrayRegion(gw->env, jChars, 0, len,(jchar*) &jbuffer);
        }

        if (pressed) {
            (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jViewNotifyKey,
                com_sun_glass_events_KeyEvent_PRESS,
                keycode, jChars, modifiers);
        } else {
            (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jViewNotifyKey,
                com_sun_glass_events_KeyEvent_TYPED,
                keycode, jChars, modifiers);
            (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jViewNotifyKey,
                com_sun_glass_events_KeyEvent_RELEASE,
                keycode, jChars, modifiers);
        }
        GLASS_CHECK_EXCEPTION(gw->env);
    }
}

void mat_ViewEvent(XEvent *event, jint type) {
    GlassWindow *gw = mat_matchXWindow(event->xexpose.window);

    if (gw != NULL && gw->jview != NULL) {
        DEBUG_PRINTF("calling HandleViewEvent %d\n",type);
        (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jHandleViewEvent, type);
        GLASS_CHECK_EXCEPTION(gw->env);
    }
}

void mat_ViewRepaint(XEvent *event) {
    GlassWindow *gw = mat_matchXWindow(event->xexpose.window);

    if (gw != NULL && gw->jview != NULL) {
        DEBUG_PRINTF("calling ViewNotifyRepaint\n");
        (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jViewNotifyRepaint, 
                event->xexpose.x,
                event->xexpose.y,
                event->xexpose.width,
                event->xexpose.height);
        GLASS_CHECK_EXCEPTION(gw->env);
    }
}
