#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XTest.h>
#include <assert.h>
#include <stdlib.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>

#include <com_sun_glass_ui_gtk_GtkRobot.h>
#include <com_sun_glass_events_MouseEvent.h>
#include "glass_general.h"
#include "glass_gtkcompat.h"
#include "glass_key.h"


static int32_t isXTestAvailable() {
    int32_t major_opcode, first_event, first_error;
    int32_t  event_basep, error_basep, majorp, minorp;
    int32_t isXTestAvailable;

    /* check if XTest is available */
    isXTestAvailable = XQueryExtension(gdk_x11_get_default_xdisplay(), XTestExtensionName, &major_opcode, &first_event, &first_error);
    if (isXTestAvailable) {
        /* check if XTest version is OK */
        XTestQueryExtension(gdk_x11_get_default_xdisplay(), &event_basep, &error_basep, &majorp, &minorp);
        if (majorp < 2 || (majorp == 2 && minorp < 2)) {
                isXTestAvailable = False;
        } else {
            XTestGrabControl(gdk_x11_get_default_xdisplay(), True);
        }
    }

    return isXTestAvailable;
}

/*
 * Class:     com_sun_glass_ui_gtk_GtkRobot
 * Method:    _create
 * Signature: ()J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_gtk_GtkRobot__1create
  (JNIEnv *env, jobject obj)
{
    if (!isXTestAvailable()) {
        (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/UnsupportedOperationException"), "Glass Robot needs XTest extension to work");
    }
    return 1L;
}

/*
 * Class:     com_sun_glass_ui_gtk_GtkRobot
 * Method:    _destroy
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkRobot__1destroy
  (JNIEnv * env, jobject obj, jlong ptr)
{
    
}

static void keyButton(jint code, gboolean press)
{
    Display *xdisplay = gdk_x11_get_default_xdisplay();
    gint gdk_keyval = find_gdk_keyval_for_glass_keycode(code);
    GdkKeymapKey *keys;
    gint n_keys;
    if (gdk_keyval == -1) {
        return;
    }
    gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(),
            gdk_keyval, &keys, &n_keys);
    if (n_keys < 1) {
        return;
    }
    
    XTestFakeKeyEvent(xdisplay,
                      keys[0].keycode,
                      press ? True : False,
                      CurrentTime);
    g_free(keys);
    XSync(xdisplay, False);
}

/*
 * Class:     com_sun_glass_ui_gtk_GtkRobot
 * Method:    _keyPress
 * Signature: (JI)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkRobot__1keyPress
  (JNIEnv *env, jobject obj, jlong ptr, jint code)
{
    keyButton(code, TRUE);
}

/*
 * Class:     com_sun_glass_ui_gtk_GtkRobot
 * Method:    _keyRelease
 * Signature: (JI)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkRobot__1keyRelease
  (JNIEnv *env, jobject obj, jlong ptr, jint code)
{
    keyButton(code, FALSE);
}

/*
 * Class:     com_sun_glass_ui_gtk_GtkRobot
 * Method:    _mouseMove
 * Signature: (JII)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkRobot__1mouseMove
  (JNIEnv *env, jobject obj, jlong ptr, jint x, jint y)
{
    Display *xdisplay = gdk_x11_get_default_xdisplay();
    XWarpPointer(xdisplay,
            None,
            XRootWindow(xdisplay,gdk_x11_get_default_screen()),
            0, 0, 0, 0, x, y);
    XSync(xdisplay, False);
}

static void mouseButtons(jint buttons, gboolean press)
{
    Display *xdisplay = gdk_x11_get_default_xdisplay();
    if (buttons & com_sun_glass_events_MouseEvent_BUTTON_LEFT) {
        XTestFakeButtonEvent(xdisplay, 1, press, CurrentTime);
    }

    if (buttons & com_sun_glass_events_MouseEvent_BUTTON_RIGHT) {
        XTestFakeButtonEvent(xdisplay, 2, press, CurrentTime);
    }
    if (buttons & com_sun_glass_events_MouseEvent_BUTTON_OTHER) {
        XTestFakeButtonEvent(xdisplay, 3, press, CurrentTime);
    }

    XSync(xdisplay, False);
}

/*
 * Class:     com_sun_glass_ui_gtk_GtkRobot
 * Method:    _mousePress
 * Signature: (JI)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkRobot__1mousePress
  (JNIEnv *env, jobject obj, jlong ptr, jint buttons)
{
    mouseButtons(buttons, TRUE);
}

/*
 * Class:     com_sun_glass_ui_gtk_GtkRobot
 * Method:    _mouseRelease
 * Signature: (JI)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkRobot__1mouseRelease
  (JNIEnv *env, jobject obj, jlong ptr, jint buttons)
{
    mouseButtons(buttons, FALSE);
}

/*
 * Class:     com_sun_glass_ui_gtk_GtkRobot
 * Method:    _mouseWheel
 * Signature: (JI)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkRobot__1mouseWheel
  (JNIEnv *env, jobject obj, jlong ptr, jint amt)
{
    Display *xdisplay = gdk_x11_get_default_xdisplay();
    int repeat = abs(amt);
    int button = amt < 0 ? 4 : 5; 
    int i;


    for (i = 0; i < repeat; i++) {
        XTestFakeButtonEvent(xdisplay, button, True, CurrentTime);
        XTestFakeButtonEvent(xdisplay, button, False, CurrentTime);
    }
    XSync(xdisplay, False);
}

/*
 * Class:     com_sun_glass_ui_gtk_GtkRobot
 * Method:    _getMouseX
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_gtk_GtkRobot__1getMouseX
  (JNIEnv *env, jobject obj, jlong ptr) 
{
    jint x;
    glass_gdk_display_get_pointer(gdk_display_get_default(), &x, NULL);
    return x;
}

/*
 * Class:     com_sun_glass_ui_gtk_GtkRobot
 * Method:    _getMouseY
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_com_sun_glass_ui_gtk_GtkRobot__1getMouseY
  (JNIEnv *env, jobject obj, jlong ptr)
{
    jint y;
    glass_gdk_display_get_pointer(gdk_display_get_default(), NULL, &y);
    return y;
}

/*
 * Class:     com_sun_glass_ui_gtk_GtkRobot
 * Method:    _getScreenCapture
 * Signature: (JIIII[I)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkRobot__1getScreenCapture
  (JNIEnv * env, jobject obj, jlong ptr, jint x, jint y, jint width, jint height, jintArray data)
{
    GdkPixbuf *screenshot, *tmp;
    GdkWindow *root_window;
    root_window = gdk_get_default_root_window ();

    
    tmp = gdk_pixbuf_get_from_drawable (NULL, root_window, NULL,
                                           x, y, 0, 0, width, height);
    screenshot = gdk_pixbuf_add_alpha(tmp, FALSE, 0, 0, 0);
    gdk_pixbuf_unref(tmp);
    
    jint *pixels = (jint *)convert_BGRA_to_RGBA((int*)gdk_pixbuf_get_pixels(screenshot), width * 4, height);
    (*env)->SetIntArrayRegion(env, data, 0, height * width, pixels);
    g_free(pixels);
    
    gdk_pixbuf_unref(screenshot);
}
