
#include "glass_general.h"

#include <jni.h>
#include <gtk/gtk.h>

char const * const GDK_WINDOW_DATA_CONTEXT = "glass_window_context";

jclass string;
jclass byteBuffer;
jmethodID byteBufferArray;
jmethodID byteBufferWrap;

jclass jRunnable;
jmethodID jRunnableRun;

jclass arrayList;
jmethodID arrayListInit;
jmethodID arrayListAdd;
jmethodID arrayListGetIdx;

jmethodID jPixelsAttachData;

jclass gtk_pixels;
jmethodID jGtkPixelsInit;

jmethodID jViewNotifyResize;
jmethodID jViewNotifyMouse;
jmethodID jViewNotifyRepaint;
jmethodID jViewNotifyKey;
jmethodID jViewHandleViewEvent;
jmethodID jViewNotifyDragEnter;
jmethodID jViewNotifyDragOver;
jmethodID jViewNotifyDragDrop;
jmethodID jViewNotifyDragLeave;
jmethodID jViewNotifyScroll;
jmethodID jViewNotifyInputMethod;
jmethodID jViewNotifyInputMethodDraw;
jmethodID jViewNotifyInputMethodCaret;
jmethodID jViewNotifyMenu;

jmethodID jWindowNotifyResize;
jmethodID jWindowNotifyMove;
jmethodID jWindowNotifyDestroy;
jmethodID jWindowNotifyClose;
jmethodID jWindowNotifyFocus;
jmethodID jWindowNotifyFocusUngrab;
jmethodID jWindowNotifyMoveToAnotherScreen;
jmethodID jWindowIsEnabled;
jmethodID jWindowNotifyDelegatePtr;
jfieldID jWindowPtr;
jfieldID jCursorPtr;

jmethodID jGtkWindowNotifyStateChanged;

jmethodID jSizeInit;

jmethodID jMapGet;
jmethodID jMapKeySet;
jmethodID jMapContainsKey;

jclass jHashSet;
jmethodID jHashSetInit;

jmethodID jSetAdd;
jmethodID jSetSize;
jmethodID jSetToArray;

jmethodID jIterableIterator;
jmethodID jIteratorHasNext;
jmethodID jIteratorNext;

jclass jScreenClass;
jmethodID jgetScreenForLocation;
jmethodID jgetNativeScreen;

jclass jApplication;
jfieldID jDisplay;
jfieldID jScreen;
jfieldID jVisualID;

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
    JNIEnv *env;
    jclass class;

    if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_6)) {
         return JNI_ERR; /* JNI version not supported */
     }

    string = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/lang/String"));
    byteBuffer = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/nio/ByteBuffer"));
    byteBufferArray = (*env)->GetMethodID(env, byteBuffer, "array", "()[B");
    byteBufferWrap = (*env)->GetStaticMethodID(env, byteBuffer, "wrap", "([B)Ljava/nio/ByteBuffer;");

    class = (*env)->FindClass(env, "java/lang/Runnable");
    jRunnableRun = (*env)->GetMethodID(env, class, "run", "()V");

    arrayList = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/util/ArrayList"));
    arrayListInit = (*env)->GetMethodID(env, arrayList, "<init>", "()V");
    arrayListAdd = (*env)->GetMethodID(env, arrayList, "add", "(Ljava/lang/Object;)Z");
    arrayListGetIdx = (*env)->GetMethodID(env, arrayList, "get", "(I)Ljava/lang/Object;");

    class = (*env)->FindClass(env, "com/sun/glass/ui/Pixels");
    jPixelsAttachData = (*env)->GetMethodID(env, class, "attachData", "(J)V");

    gtk_pixels = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/sun/glass/ui/gtk/GtkPixels"));
    jGtkPixelsInit = (*env)->GetMethodID(env, gtk_pixels, "<init>", "(IILjava/nio/ByteBuffer;)V");

    class = (*env)->FindClass(env, "com/sun/glass/ui/View");
    jViewNotifyResize = (*env)->GetMethodID(env, class, "notifyResize", "(II)V");
    jViewNotifyMouse = (*env)->GetMethodID(env, class, "notifyMouse", "(IIIIIIIZZ)V");
    jViewNotifyRepaint = (*env)->GetMethodID(env, class, "notifyRepaint", "(IIII)V");
    jViewNotifyKey = (*env)->GetMethodID(env, class, "notifyKey", "(II[CI)V");
    jViewHandleViewEvent = (*env)->GetMethodID(env, class, "handleViewEvent", "(I)V");
    jViewNotifyDragEnter = (*env)->GetMethodID(env, class, "notifyDragEnter", "(IIIII)I");
    jViewNotifyDragOver = (*env)->GetMethodID(env, class, "notifyDragOver", "(IIIII)I");
    jViewNotifyDragDrop = (*env)->GetMethodID(env, class, "notifyDragDrop", "(IIIII)I");
    jViewNotifyDragLeave = (*env)->GetMethodID(env, class, "notifyDragLeave", "()V");
    jViewNotifyScroll = (*env)->GetMethodID(env, class, "notifyScroll", "(IIIIDDIIIIIDD)V");
    jViewNotifyInputMethod = (*env)->GetMethodID(env, class, "notifyInputMethod", "(Ljava/lang/String;[I[I[BIII)V");
    jViewNotifyMenu = (*env)->GetMethodID(env, class, "notifyMenu", "(IIIIZ)V");

    class = (*env)->FindClass(env, "com/sun/glass/ui/gtk/GtkView");
    jViewNotifyInputMethodDraw = (*env)->GetMethodID(env, class, "notifyInputMethodDraw", "(Ljava/lang/String;III)V");
    jViewNotifyInputMethodCaret = (*env)->GetMethodID(env, class, "notifyInputMethodCaret", "(III)V");

    class = (*env)->FindClass(env, "com/sun/glass/ui/Window");
    jWindowNotifyResize = (*env)->GetMethodID(env, class, "notifyResize", "(III)V");
    jWindowNotifyMove = (*env)->GetMethodID(env, class, "notifyMove", "(II)V");
    jWindowNotifyDestroy = (*env)->GetMethodID(env, class, "notifyDestroy", "()V");
    jWindowNotifyClose = (*env)->GetMethodID(env, class, "notifyClose", "()V");
    jWindowNotifyFocus = (*env)->GetMethodID(env, class, "notifyFocus", "(I)V");
    jWindowNotifyFocusUngrab = (*env)->GetMethodID(env, class, "notifyFocusUngrab", "()V");
    jWindowNotifyMoveToAnotherScreen = (*env)->GetMethodID(env, class, "notifyMoveToAnotherScreen", "(JJ)V");
    jWindowIsEnabled = (*env)->GetMethodID(env, class, "isEnabled", "()Z");
    jWindowNotifyDelegatePtr = (*env)->GetMethodID(env, class, "notifyDelegatePtr", "(J)V");
    jWindowPtr = (*env)->GetFieldID(env, class, "ptr", "J");

    class = (*env)->FindClass(env, "com/sun/glass/ui/gtk/GtkWindow");
    jGtkWindowNotifyStateChanged =
            (*env)->GetMethodID(env, class, "notifyStateChanged", "(I)V");

    class = (*env)->FindClass(env, "com/sun/glass/ui/Cursor");
    jCursorPtr = (*env)->GetFieldID(env, class, "ptr", "J");

    class = (*env)->FindClass(env, "com/sun/glass/ui/Size");
    jSizeInit = (*env)->GetMethodID(env, class, "<init>", "(II)V");

    class = (*env)->FindClass(env, "java/util/Map");
    jMapGet = (*env)->GetMethodID(env, class, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
    jMapKeySet = (*env)->GetMethodID(env, class, "keySet", "()Ljava/util/Set;");
    jMapContainsKey = (*env)->GetMethodID(env, class, "containsKey", "(Ljava/lang/Object;)Z");

    jHashSet = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/util/HashSet"));
    jHashSetInit = (*env)->GetMethodID(env, jHashSet, "<init>", "()V");

    class = (*env)->FindClass(env, "java/util/Set");
    jSetAdd = (*env)->GetMethodID(env, class, "add", "(Ljava/lang/Object;)Z");
    jSetSize = (*env)->GetMethodID(env, class, "size", "()I");
    jSetToArray = (*env)->GetMethodID(env, class, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;");

    class = (*env)->FindClass(env, "java/lang/Iterable");
    jIterableIterator = (*env)->GetMethodID(env, class, "iterator", "()Ljava/util/Iterator;");

    class = (*env)->FindClass(env, "java/util/Iterator");
    jIteratorHasNext = (*env)->GetMethodID(env, class, "hasNext", "()Z");
    jIteratorNext = (*env)->GetMethodID(env, class, "next", "()Ljava/lang/Object;");

    jScreenClass = (*env)->FindClass(env, "com/sun/glass/ui/Screen");
    jgetScreenForLocation = (*env)->GetStaticMethodID(env, jScreenClass, "getScreenForLocation","(II)Lcom/sun/glass/ui/Screen;");
    jgetNativeScreen = (*env)->GetMethodID(env, jScreenClass, "getNativeScreen","()J");

    jApplication = (*env)->FindClass(env, "com/sun/glass/ui/gtk/GtkApplication");
    jDisplay = (*env)->GetStaticFieldID(env, jApplication, "display", "J");
    jScreen = (*env)->GetStaticFieldID(env, jApplication, "screen", "I");
    jVisualID = (*env)->GetStaticFieldID(env, jApplication, "visualID", "J");

    g_thread_init(NULL);
    gdk_threads_init();
    gdk_threads_enter();
    gtk_init(NULL, NULL);

    return JNI_VERSION_1_6;
}

void
glass_throw_exception(JNIEnv * env,
                      const char * exceptionClass,
                      const char * exceptionMessage) {
    jclass throwableClass = (*env)->FindClass(env, exceptionClass);
    (*env)->ThrowNew(env, throwableClass, exceptionMessage);
}

int
glass_throw_oom(JNIEnv * env, const char * message) {
    glass_throw_exception(env, "java/lang/OutOfMemoryError", message);
    // must return a non-zero value, see HANDLE_MEM_ALLOC_ERROR
    return 1;
}


guint8* convert_BGRA_to_RGBA(const int* pixels, int stride, int height) {
  guint8* new_pixels = g_malloc(height * stride);
  int i = 0;

  for (i = 0; i < height * stride; i += 4) {
      new_pixels[i] = (guint8)(*pixels >> 16);
      new_pixels[i + 1] = (guint8)(*pixels >> 8);
      new_pixels[i + 2] = (guint8)(*pixels);
      new_pixels[i + 3] = (guint8)(*pixels >> 24);
      pixels++;
  }

  return new_pixels;
}


void dump_jstring_array(JNIEnv* env, jobjectArray arr) {
    if (arr == NULL) {
        LOG0("dump: Array is null\n")
        return;
    }
    jsize len = (*env)->GetArrayLength(env, arr);
    LOG1("dump: length = %d\n", len)
    int i = 0;
    jboolean isCopy;
    for(i = 0; i < len; i++) {
        jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
        const char* str = (*env)->GetStringUTFChars(env, jstr, &isCopy);
        LOG2("dump: s[%d]: %s\n", i, str)
    }
}

void log_exception(JNIEnv *env) {
    if ((*env)->ExceptionCheck(env))
        (*env)->ExceptionDescribe(env);
}
