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

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
#include <dlfcn.h>
#include <pthread.h>

#include "mat_common.h"

#include "com_sun_glass_ui_Application.h"
#include "com_sun_glass_ui_x11_X11Application.h"
#include "com_sun_glass_events_ViewEvent.h"

#include <X11/Xlibint.h> //needed to get the Display.fd

#define SHOW_X_EVENTS 0 // enable printfs about incoming x events
#define DEBUG_RUNNABLE 0

Display *mat_x11Display = NULL;
int closeDisplayOnTerminate = 0;
VisualID mat_visualID = 0;
int mat_screen = 0;
XVisualInfo *mat_x11VisualInfo = NULL;
static Atom wmDeleteMessage = 0;

static pthread_mutex_t postMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t postCond = PTHREAD_COND_INITIALIZER;

JavaVM *jVM = NULL;

jclass mat_jIntegerClass = NULL;

jmethodID mat_jRunnableRun = 0;
jmethodID mat_jVectorAddElement = 0;

jclass mat_jScreenClass = NULL;

jmethodID mat_jHandleViewEvent = 0;
jmethodID mat_jViewNotifyMove = 0;
jmethodID mat_jViewNotifyResize = 0;
jmethodID mat_jViewNotifyRepaint = 0;
jmethodID mat_jViewNotifyKey = 0;
jmethodID mat_jViewNotifyMouse = 0;

jmethodID mat_jMapGetMethod = 0;
jmethodID mat_jBooleanValueMethod = 0;
jmethodID mat_jListAddElement = 0;

jfieldID mat_jViewPtr = 0;

jfieldID mat_jWindowX = 0;
jfieldID mat_jWindowY = 0;
jfieldID mat_jWindowWidth = 0;
jfieldID mat_jWindowHeight = 0;
jfieldID mat_jWindowView = 0;

jmethodID mat_jWindowNotifyClose = 0;
jmethodID mat_jWindowNotifyDestroy = 0;
jmethodID mat_jWindowNotifyFocus = 0;
jmethodID mat_jWindowNotifyFocusDisabled = 0;
jmethodID mat_jWindowNotifyMove = 0;
jmethodID mat_jWindowNotifyMoveToAnotherScreen = 0;
jmethodID mat_jWindowNotifyResize = 0;

jclass mat_jPixelsClass = NULL;

jfieldID mat_jPixelsWidth = 0;
jfieldID mat_jPixelsHeight = 0;
jfieldID mat_jPixelsBytes = 0;
jfieldID mat_jPixelsInts = 0;

jmethodID mat_jPixelsAttachData = 0;

jclass mat_jCursorClass = NULL;

static int activeRunLoop = 0;
static int waitingOnRunnable = 0;

// a unix pipe which we will use as a runnable queue for posted events.
static int postEventPipe[2];

typedef struct _PostEvent {
    jobject runnable;
    int type;
} PostEvent;

#define CHECK_AND_RET(ENV) if ((*ENV)->ExceptionCheck(ENV) == JNI_TRUE) \
    { fprintf(stderr, "erk Java exception detected in at %s:%s:%d\n", __FUNCTION__, __FILE__, __LINE__); \
    (*ENV)->ExceptionDescribe(ENV); (*ENV)->ExceptionClear(ENV);  \
    mat_JNU_ThrowByName(env, mat_RuntimeException, "JNI InitIDs failed"); \
    return 0; };

static int haveIDs = 0;
/*
 * Class:     com_sun_glass_ui_x11_X11Application
 * Method:    _initIDs
 * Signature: ()V
 */
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 */
     }

    jVM = jvm;
    
    mat_jIntegerClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/lang/Integer"));
    jclass mat_jMapClass = (*env)->FindClass(env, "java/util/Map");
    jclass mat_jBooleanClass = (*env)->FindClass(env, "java/lang/Boolean");

    mat_jVectorAddElement = (*env)->GetMethodID(env, (*env)->FindClass(env, "java/util/Vector"), "addElement", "(Ljava/lang/Object;)V");
    mat_jMapGetMethod = (*env)->GetMethodID(env, mat_jMapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
    mat_jBooleanValueMethod = (*env)->GetMethodID(env, mat_jBooleanClass, "booleanValue", "()Z");
    mat_jRunnableRun = (*env)->GetMethodID(env,
            (*env)->FindClass(env, "java/lang/Runnable"), "run", "()V");
    mat_jListAddElement = (*env)->GetMethodID(env, (*env)->FindClass(env, "java/util/List"), "add", "(Ljava/lang/Object;)Z");
    CHECK_AND_RET(env);

    jclass objectClass = (*env)->FindClass(env, "java/lang/Object");
    CHECK_AND_RET(env);

    // Application specific

    // screen specific
    mat_jScreenClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/sun/glass/ui/Screen"));
    CHECK_AND_RET(env);

    // view specific
    jclass mat_jX11ViewClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/sun/glass/ui/x11/X11View"));
    CHECK_AND_RET(env);

    mat_jHandleViewEvent = (*env)->GetMethodID(env, mat_jX11ViewClass, "handleViewEvent", "(I)V");
    mat_jViewNotifyMove = (*env)->GetMethodID(env, mat_jX11ViewClass, "_notifyMove", "(II)V");
    mat_jViewNotifyResize = (*env)->GetMethodID(env, mat_jX11ViewClass, "notifyResize", "(II)V");
    mat_jViewNotifyRepaint = (*env)->GetMethodID(env, mat_jX11ViewClass, "notifyRepaint", "(IIII)V");
    mat_jViewNotifyKey = (*env)->GetMethodID(env, mat_jX11ViewClass, "notifyKey", "(II[CI)V");
    mat_jViewNotifyMouse = (*env)->GetMethodID(env, mat_jX11ViewClass, "notifyMouse", "(IIIIIIIZZ)V");
    CHECK_AND_RET(env);

    // CVM does not support getting private fields from parent classes, but does allow setting them.
    jclass mat_jViewClass = (*env)->FindClass(env, "com/sun/glass/ui/View");
    CHECK_AND_RET(env);

    mat_jViewPtr = (*env)->GetFieldID(env, mat_jViewClass, "ptr", "J");
    CHECK_AND_RET(env);

    //window specific
    // CVM does not support getting private fields from parent classes, but does allow setting them.
    // which is ok, because we are only using parent class fields.
    jclass mat_jWindowClass = (*env)->FindClass(env, "com/sun/glass/ui/Window");
    CHECK_AND_RET(env);

    mat_jWindowX = (*env)->GetFieldID(env, mat_jWindowClass, "x", "I");
    CHECK_AND_RET(env);
    mat_jWindowY = (*env)->GetFieldID(env, mat_jWindowClass, "y", "I");
    CHECK_AND_RET(env);
    mat_jWindowWidth = (*env)->GetFieldID(env, mat_jWindowClass, "width", "I");
    CHECK_AND_RET(env);
    mat_jWindowHeight = (*env)->GetFieldID(env, mat_jWindowClass, "height", "I");
    CHECK_AND_RET(env);

    mat_jWindowView = (*env)->GetFieldID(env, mat_jWindowClass, "view", "Lcom/sun/glass/ui/View;");
    CHECK_AND_RET(env);

    mat_jWindowNotifyMove = (*env)->GetMethodID(env, mat_jWindowClass, "notifyMove", "(II)V");
    mat_jWindowNotifyResize = (*env)->GetMethodID(env, mat_jWindowClass, "notifyResize", "(III)V");
    mat_jWindowNotifyMoveToAnotherScreen = (*env)->GetMethodID(env, mat_jWindowClass, "notifyMoveToAnotherScreen", "(JJ)V");
    mat_jWindowNotifyClose = (*env)->GetMethodID(env, mat_jWindowClass, "notifyClose", "()V");
    mat_jWindowNotifyFocus = (*env)->GetMethodID(env, mat_jWindowClass, "notifyFocus", "(I)V");
    mat_jWindowNotifyDestroy = (*env)->GetMethodID(env, mat_jWindowClass, "notifyDestroy", "()V");
    mat_jWindowNotifyFocusDisabled = (*env)->GetMethodID(env, mat_jWindowClass, "notifyFocusDisabled", "()V");
    CHECK_AND_RET(env);

    //pixels specific
    // CVM does not support getting private fields from parent classes, but does allow setting them.
    //mat_jPixelsClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/sun/glass/ui/x11/X11Pixels"));
    mat_jPixelsClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/sun/glass/ui/Pixels"));
    CHECK_AND_RET(env);

    mat_jPixelsWidth = (*env)->GetFieldID(env, mat_jPixelsClass, "width", "I");
    mat_jPixelsHeight = (*env)->GetFieldID(env, mat_jPixelsClass, "height", "I");
    mat_jPixelsBytes = (*env)->GetFieldID(env, mat_jPixelsClass, "bytes", "Ljava/nio/ByteBuffer;");
    mat_jPixelsInts = (*env)->GetFieldID(env, mat_jPixelsClass, "ints", "Ljava/nio/IntBuffer;");
    CHECK_AND_RET(env);

    mat_jPixelsAttachData = (*env)->GetMethodID(env, mat_jPixelsClass, "attachData", "(J)V");
    CHECK_AND_RET(env);

    //cursor specific
    mat_jCursorClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/sun/glass/ui/x11/X11Cursor"));
    CHECK_AND_RET(env);

    // Create our post event pipe - which is used to queue runnable events onto the event thread.
    if (pipe(postEventPipe) != 0) {
        mat_JNU_ThrowByName(env, mat_RuntimeException, "Pipe allocation failed");
    }
    // mark the read side of the event pipe non-blocking
    int flags;
    if (-1 == (flags = fcntl(postEventPipe[0], F_GETFL, 0)))
            flags = 0;
    fcntl(postEventPipe[0], F_SETFL, flags | O_NONBLOCK);

    return JNI_VERSION_1_4;
}

//The X11 event mask lets the server know which events we care about, and
//should just have the events we really care about.
static int mat_x11_eventMask =
    KeyPressMask |
    KeyReleaseMask |

    ButtonPressMask |
    ButtonReleaseMask |
    PointerMotionMask |

    //PointerMotionHintMask |
    //Button1MotionMask |
    //Button2MotionMask |
    //Button3MotionMask |
    //Button4MotionMask |
    //Button5MotionMask |
    //ButtonMotionMask |
    EnterWindowMask |
    LeaveWindowMask |

    //KeymapStateMask |
    ExposureMask |
    //VisibilityChangeMask |
    StructureNotifyMask |
    // ResizeRedirectMask |
    // SubstructureNotifyMask |
    // SubstructureRedirectMask |
    FocusChangeMask |
    //PropertyChangeMask |
    //ColormapChangeMask |
    //OwnerGrabButtonMask |
    0; // 0 is an aid to allow easy commenting out of the masks

void matOpenDisplay() {
    if (mat_x11Display == NULL) {
        XInitThreads();

        mat_x11Display = XOpenDisplay(NULL);
        if (mat_x11Display == NULL) {
            printf("Error: failed to connect to X11 server %s\n",getenv("DISPLAY"));
            //ToDo: throw an exception rather than just exit ?
            exit(-1);
        }
        closeDisplayOnTerminate = 1;
    }

    if (mat_screen <= 0) {
        mat_screen = DefaultScreen(mat_x11Display);
        DEBUG_PRINTF("using default X11 Screen %d\n",mat_screen);
    } else {
        DEBUG_PRINTF("using provided X11 Screen %d\n",mat_visualID);
    }

    if (mat_visualID == 0) { 
        mat_visualID = XVisualIDFromVisual(DefaultVisual(mat_x11Display,mat_screen));
        DEBUG_PRINTF("using default X11 Visual %d (0x%x\n",mat_visualID,mat_visualID);
    } else {
        DEBUG_PRINTF("using provided X11 Visual %d (0x%x\n",mat_visualID,mat_visualID);
    }

    if (mat_x11VisualInfo == NULL) {
        XVisualInfo vinfo;
        int nitems_return;

        vinfo.visualid = mat_visualID;

        mat_x11VisualInfo = XGetVisualInfo(mat_x11Display, VisualIDMask, &vinfo, &nitems_return);
        if (nitems_return <= 0) {
            printf("Error: failed to retrive to visual from ID %d\n",(int)mat_visualID);
            exit(-2);
        }
    }

    if (MAT_X11_DEBUG) {
       if (getenv("X11_USE_XSYNC")) {
            XSynchronize(mat_x11Display,1);
            DEBUG_PRINTF("enableing X11 Sync\n");
       }
    }
}

void matCloseDisplay(jboolean isEventThread) {
    if (mat_x11Display) {
        if (isEventThread) {
            DEBUG_PRINTF("matCloseDisplay shutting down event loop on event thread\n");
            activeRunLoop = 0;
        } else {
            DEBUG_PRINTF("matCloseDisplay shutting down event loop with posted request\n");
            mat_runOnEventThread(NULL, PostEventCloseRunLoop, NULL);
        }
    }
}

// create a 1x1 window to be resized later.
Window matCreateWindow() {
    XSetWindowAttributes attr;
    attr.event_mask = mat_x11_eventMask;
    attr.border_pixel = 0;
    attr.colormap = XCreateColormap(mat_x11Display,
                                        RootWindow(mat_x11Display,
                                                   mat_x11VisualInfo->screen),
                                        mat_x11VisualInfo->visual, AllocNone);

    XLockDisplay(mat_x11Display);
    Window window = XCreateWindow(mat_x11Display,
                RootWindow(mat_x11Display, mat_x11VisualInfo->screen) ,
            0, 0,             // x,y
            1, 1,             // width, height - true size set later...
            0,                // border_width,
            mat_x11VisualInfo->depth,
            InputOutput,      // class,
            mat_x11VisualInfo->visual,
            (CWEventMask | CWColormap | CWBorderPixel),
            &attr);           // attributes

    // register interest in the delete window message
    if (wmDeleteMessage == 0)
        wmDeleteMessage = XInternAtom(mat_x11Display, "WM_DELETE_WINDOW", False);
    XSetWMProtocols(mat_x11Display, window, &wmDeleteMessage, 1);

    XFlush(mat_x11Display);
    XUnlockDisplay(mat_x11Display);

    DEBUG_PRINTF("X11Window created 0x%x\n",(unsigned int)window);
    return window;
}

void matSetBounds(Window window,
       int x, int y,
       jboolean xset, jboolean yset,
       int width, int height,
       int cw, int ch) {
   if (mat_x11Display && window) {

      GlassWindow *gw = mat_matchXWindow(window);

      int newWidth = -1;
      int newHeight = -1;

      // note: X11 does not like a size of zero, hence the &&
      if ((width > 0) && (height > 0)) {
          newWidth = width;
          newHeight = height;
      } else if ((cw > 0) && (ch > 0)) { 
          // account for any menubar here later...
          newWidth = cw;
          newHeight = ch;
      }
      if (newWidth == gw->width && newHeight == gw->height) {
          newWidth = newHeight = -1;
      }

      XLockDisplay(mat_x11Display);
      if ((xset || yset) && (newWidth > 0)) {
            DEBUG_PRINTF("bounding  window to %d,%d %dx%d \n",x,y,newWidth,newHeight);
            gw->x = x;
            gw->y = y;
            gw->width = newWidth;
            gw->height = newHeight;
            if (gw->visible) {
                XMoveResizeWindow(mat_x11Display, window, x, y, newWidth, newHeight);
            } else {
                mat_MoveWindow(gw, x, y, x, y); //todo: rel stuff
                mat_ResizeWindow(gw, width, height);
            }
      } else if (xset || yset) {
            DEBUG_PRINTF("moving window to %d,%d \n",x,y);
            if (x != gw->x && y != gw->y) {
                gw->x = x;
                gw->y = y;
                if (gw->visible) {
                    XMoveWindow(mat_x11Display, window, x, y);
                } else {
                    int scrX = x , scrY = y;
                    matTranslateScreenCoords(window, x, y, &scrX, &scrY);
                    mat_MoveWindow(gw, x, y, scrX, scrY); // rel stuff
                }
            }
      } else if ((newWidth > 0) || (newHeight > 0)) {
            DEBUG_PRINTF("sizing window to %d,%d \n",newWidth, newHeight);
            gw->width = newWidth;
            gw->height = newHeight;
            if (gw->visible) {
                XResizeWindow(mat_x11Display, window, newWidth, newHeight);
            } else {
                // if the window is not visible, notify it now because 
                // we will not get a resize event
                mat_ResizeWindow(gw, newWidth, newHeight);
            }
      }
      XFlush(mat_x11Display);
      XUnlockDisplay(mat_x11Display);
   }
}

void matSetVisible(Window window, int visible) {
    if (mat_x11Display && window) {
        GlassWindow *gw = mat_matchXWindow(window);
        if (visible != gw->visible) {
            gw->visible = visible;
            XLockDisplay(mat_x11Display);
            if (visible) {
        DEBUG_PRINTF("XMoveResizeWindow(%d,%d,%d,%d);\n",gw->x, gw->y, gw->width, gw->height);
                XMoveResizeWindow(mat_x11Display, window,
                    gw->x, gw->y, gw->width, gw->height);
        DEBUG_PRINTF("XMapWindow\n");
                XMapWindow(mat_x11Display, window);
            } else {
                XUnmapWindow(mat_x11Display, window);
            }
            XFlush(mat_x11Display);
            XUnlockDisplay(mat_x11Display);
        }
    }
}

void matSetTitle(Window window, const char * title) {
   if (mat_x11Display && window) {
        XTextProperty textProp;
        textProp.value = (unsigned char*)title;
        textProp.encoding = XA_STRING;
        textProp.format = 8;
        textProp.nitems = strlen(title);

        XLockDisplay(mat_x11Display);
        XSetWMName(mat_x11Display, window, &textProp);
        XFlush(mat_x11Display);
        XUnlockDisplay(mat_x11Display);
    }
}

void matCloseWindow(Window window) {
    if (mat_x11Display && window) {
        XLockDisplay(mat_x11Display);
        XDestroyWindow(mat_x11Display,window);
        XFlush(mat_x11Display);
        XUnlockDisplay(mat_x11Display);
    }
}

void matLock() {
    XLockDisplay(mat_x11Display);
}

void matUnlock() {
    XUnlockDisplay(mat_x11Display);
}

void matTranslateScreenCoords(Window window, 
        int relX, int relY, 
        int *screenX, int *screenY) {
    Window child_win; //unused but needed as an arg
    Window parent_win;
    Window root_win;
    Window* child_windows;
    unsigned int num_child_windows;
    XLockDisplay(mat_x11Display);
    if (XQueryTree(mat_x11Display, window,
        &root_win, &parent_win, &child_windows, &num_child_windows)) {
        if(child_windows)
            XFree(child_windows);

        /* next, we make the coordinates translation, from the coordinates system */
        /* of the parent window, to the coordinates system of the root window,    */
        /* which happens to be the same as that of the screen, since the root     */
        /* window always spans the entire screen size.                            */
        /* the 'x' and 'y' values are those previously returned by the            */
        /* XGetWindowAttributes function.                                         */
        XTranslateCoordinates(mat_x11Display, 
            parent_win, root_win,
            relX, relY,
            screenX, screenY, &child_win);
    } else {
        DEBUG_PRINTF("matTranslateScreenCoords - no screen coords yet.\n");
        *screenX = relX;
        *screenY = relY;
    }
    XUnlockDisplay(mat_x11Display);
}

void matConfigureWindow(XEvent *evt) {
    GlassWindow *gw = mat_matchXWindow(evt->xexpose.window);
    if (gw == NULL) {
        return; // window has been disposed of 
    }
    int relX = evt->xconfigure.x;
    int relY = evt->xconfigure.y;
    int width = evt->xconfigure.width;
    int height = evt->xconfigure.height;
    DEBUG_PRINTF("matConfigureWindow - event says %d,%d and %dx%d\n",relX,relY,width,height);

    if (width == 1 || width == 65535) {
        // X11 is sending a configure event back when we create the initial window. But
        // we really, really don't want to report that 1x1 window. I was seeing a 1x1 window
        // coming back as 65535x65535 on ubuntu.
        DEBUG_PRINTF("matConfigureWindow - ignoring window configure by width\n");
        return;
    }

    int screenX, screenY;
    matTranslateScreenCoords(evt->xexpose.window, relX, relY, &screenX, &screenY);
    
    mat_MoveWindow(gw,
        relX, relY,
        screenX - relX, screenY - relY);
    mat_ResizeWindow(gw, width, height);
}

Screen* matScreenDefault() {
    Screen* screen = 0;
    if (mat_x11Display) {
        XLockDisplay(mat_x11Display);
        screen =  XDefaultScreenOfDisplay(mat_x11Display);
        XUnlockDisplay(mat_x11Display);
    }
    return screen;
}

static void handleXEvent(JNIEnv *env, XEvent* event) {

    switch  (event->type) {
        //Keyboard events  	 KeyPress, KeyRelease
        case KeyPress:
            if (SHOW_X_EVENTS) printf( "EVT:KeyPress\n");
            mat_KeyEvent(event,JNI_TRUE);
            break;
        case KeyRelease:
            if (SHOW_X_EVENTS) printf( "EVT:KeyRelease\n");
            mat_KeyEvent(event,JNI_FALSE);
            break;

        //Pointer events  	 ButtonPress, ButtonRelease, MotionNotify
        case ButtonPress:
            if (SHOW_X_EVENTS) printf( "EVT:ButtonPress\n");
            mat_MouseButtonEvent(event,JNI_TRUE);
            break;
        case ButtonRelease:
            if (SHOW_X_EVENTS) printf( "EVT:ButtonRelease\n");
            mat_MouseButtonEvent(event,JNI_FALSE);
            break;
        case MotionNotify:
            if (SHOW_X_EVENTS) printf( "EVT:Motion\n");
            mat_MouseMotionEvent(event);
            break;

        //Window crossing events  	 EnterNotify, LeaveNotify
        case EnterNotify:
            if (SHOW_X_EVENTS) printf( "EVT:EnterNotify\n");
            mat_MouseEnterEvent(event,JNI_TRUE);
            break;
        case LeaveNotify:
            if (SHOW_X_EVENTS) printf( "EVT:LeaveNotify\n");
            mat_MouseEnterEvent(event,JNI_FALSE);
            break;

        //Input focus events  	 FocusIn, FocusOut
        case FocusIn:
            if (SHOW_X_EVENTS) printf( "EVT:FocusIn\n");
            mat_FocusWindow(event, JNI_TRUE);
            break;
        case FocusOut:
            if (SHOW_X_EVENTS) printf( "EVT:FocusOut\n");
            mat_FocusWindow(event, JNI_FALSE);
            break;

        //Keymap state notification event  	 KeymapNotify
        case KeymapNotify:
            if (SHOW_X_EVENTS) printf( "EVT:KeymapNotify\n");
            break;

        //Exposure events  	 Expose, GraphicsExpose, NoExpose
        case Expose:
            if (SHOW_X_EVENTS) printf( "EVT:Expose\n");
            mat_ViewRepaint(event);
            break;
        case GraphicsExpose:  // unused
            if (SHOW_X_EVENTS) printf( "EVT:GraphicsExpose\n");
            mat_ViewRepaint(event);
            break;
        case NoExpose:  // unused
            if (SHOW_X_EVENTS) printf( "EVT:NoExpose\n");
            break;

        // Window state notification events CirculateNotify, ConfigureNotify,
        // CreateNotify, DestroyNotify, GravityNotify, MapNotify, MappingNotify,
        // ReparentNotify, UnmapNotify, VisibilityNotify
        case CirculateNotify:
            if (SHOW_X_EVENTS) printf( "EVT:CirculateNotify\n");
            break;
        case ConfigureNotify:
            if (SHOW_X_EVENTS) printf( "EVT:ConfigureNotify\n");
            matConfigureWindow(event);
            //mat_ViewEvent(event, com_sun_glass_events_ViewEvent_ADD);
            break;
        case CreateNotify:
            if (SHOW_X_EVENTS) printf( "EVT:CreateNotify\n");
            break;
        case DestroyNotify:
            if (SHOW_X_EVENTS) printf( "EVT:DestroyNotify\n");
            mat_DestroyWindow(event);
            break;
        case MapNotify:
            if (SHOW_X_EVENTS) printf( "EVT:MapNotify\n");
            break;
        case MappingNotify:
            if (SHOW_X_EVENTS) printf( "EVT:MapppingNotify\n");
            break;
        //case ReparentNotify:
        //    printf( "EVT:ReparentNotify\n");
        //    break;
        case UnmapNotify:
            if (SHOW_X_EVENTS) printf( "EVT:UnmapNotify\n");
            break;
        case VisibilityNotify:
            if (SHOW_X_EVENTS) printf( "EVT:VisibilityNotify\n");
            break;

        // Cut and paste stuff
        //Client communication events ClientMessage, PropertyNotify,
        //SelectionClear, SelectionNotify, SelectionRequest
        case ClientMessage:
            if ((Atom)event->xclient.data.l[0] == wmDeleteMessage) {
                // our window is being closed !
                if (SHOW_X_EVENTS) printf( "EVT:ClientMessage - CLOSE WINDOW\n");
                mat_CloseWindow(event);
            } else {
                if (SHOW_X_EVENTS) printf( "EVT:ClientMessage - unknown\n");
            }
            break;
        case PropertyNotify:
            if (SHOW_X_EVENTS) printf( "EVT:PropertyNotify\n");
            break;
        case SelectionClear:
            if (SHOW_X_EVENTS) printf( "EVT:SelectionClear\n");
            break;
        case SelectionNotify:
            if (SHOW_X_EVENTS) printf( "EVT:SelectionNotify\n");
            break;
        case SelectionRequest:
            if (SHOW_X_EVENTS) printf( "EVT:SelectionRequest\n");
            break;
        case ReparentNotify:
            // can ignore
            break;

        default:
            if (SHOW_X_EVENTS) printf( "EVT:Odd event %d\n",event->type);
    }
}

static void runRunnable(JNIEnv *env,jobject runnable) {
 #if DEBUG_RUNNABLE
    DEBUG_PRINTF("X11Application_runRunnable\n");
 #endif
    if (runnable != NULL) {
        (*env)->CallVoidMethod(env,
                runnable,
                mat_jRunnableRun);
        GLASS_CHECK_EXCEPTION(env);
    }
 #if DEBUG_RUNNABLE
    DEBUG_PRINTF("X11Application_runRunnable complete\n");
 #endif
}

void mat_runOnEventThread(JNIEnv *env, int waitType, jobject jRunnable) {
    PostEvent postevent;

    if (!mat_x11Display) {
        DEBUG_PRINTF("mat_runOnEventThread discarding posted event\n");
        return;
    }

    //note, we need to make a copy of the ref, and free it later
    if (jRunnable)
        postevent.runnable = (*env)->NewGlobalRef(env,jRunnable);
    postevent.type = waitType;

    // write the object to the pipe, which acts as our queue of runnables
    if (write(postEventPipe[1],&postevent,sizeof(PostEvent)) != sizeof(PostEvent) && env)
        mat_JNU_ThrowByName(env, mat_RuntimeException, "PostEvent write failed");
}

static void matRunLoop(JNIEnv *env) {
    // runloop - does not return;
    XEvent event;
    struct pollfd fds[2];

    activeRunLoop = 1;

    fds[0].fd = ((struct _XDisplay*)mat_x11Display)->fd;
    fds[0].events = POLLIN;

    fds[1].fd = postEventPipe[0];
    fds[1].events = POLLIN;

    while(XPending(mat_x11Display)) {
        XLockDisplay(mat_x11Display);
        XNextEvent(mat_x11Display, &event);
        XUnlockDisplay(mat_x11Display);
        handleXEvent(env,&event);
    }

    while (activeRunLoop)  {

        int ret = poll(fds,2,-1);

        if (fds[1].revents & POLLIN) {
           PostEvent postevent;
           // get the next runnable from the pipe
           int count = 3; // limit number of runnables handled before we move on to X events.
           // postEventPipe[0] is marked non-blocking, so this should fall thorough if nothing is waiting.
           while (activeRunLoop && count > 0 && (read(postEventPipe[0],&postevent,sizeof(PostEvent))) == sizeof(PostEvent)) {
                if (postevent.type == PostEventCloseRunLoop) {
                    DEBUG_PRINTF("X11 postevent close run loop handled\n");
                    activeRunLoop = 0;
                    break;
                }
                //convert back the a runnable and run;
                runRunnable(env,postevent.runnable);

                //release our reference
                (*env)->DeleteGlobalRef(env,postevent.runnable);
                postevent.runnable = 0; // get the reference off the stack too.

                if (postevent.type == PostEventInvokeAndWait) {
                    // notify waiting thread it can continue
                    pthread_mutex_lock(&postMutex);
                    waitingOnRunnable = 0;
                    pthread_mutex_unlock(&postMutex);
                    pthread_cond_signal(&postCond);
                }
                count --;
            }
            fds[1].revents = 0;
        } 

        if (activeRunLoop && fds[0].revents & POLLIN) {
            while(XEventsQueued(mat_x11Display, QueuedAfterReading)) {
                XLockDisplay(mat_x11Display);
                XNextEvent(mat_x11Display, &event);
                XUnlockDisplay(mat_x11Display);
                handleXEvent(env,&event);
            }
        }
    }
    if (closeDisplayOnTerminate) {
        DEBUG_PRINTF("X11Application runLoop exited, closing display\n");
        XLockDisplay(mat_x11Display);
        XCloseDisplay(mat_x11Display);
        XUnlockDisplay(mat_x11Display);
        mat_x11Display = NULL;
    }
    mat_x11Display = NULL;
    DEBUG_PRINTF("X11Application runLoop terminated\n");
}

/*
 * Class:     com_sun_glass_ui_x11_X11Application
 * Method:    _runLoop
 * Signature: ([Ljava/lang/String;Lcom/sun/glass/ui/Launchable;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Application__1runLoop
  (JNIEnv *env, jobject japplication, jobjectArray jLaunchableArgs, jobject jLaunchable,
        jlong display, jint screen, jlong visualID)
{
    DEBUG_PRINTF("X11Application_runloop\n");
    mat_x11Display = jlong_to_ptr(display);
    mat_visualID = visualID;
    mat_screen = screen;
    DEBUG_PRINTF("X11Application using display=0x%0x visualID=0x%0x, screen=0x%0x\n",
        mat_x11Display, mat_visualID, mat_screen);

    // open the display
    matOpenDisplay();

    DEBUG_PRINTF("X11Application upcall to finishLaunching\n");
    // upcall to have the application finish launching itself on the event thread.
    if (jLaunchable != NULL) {
        // called once so don't bother to cache values
        jmethodID finishLaunchingMethod = (*env)->GetMethodID(env,
            (*env)->FindClass(env,
                "com/sun/glass/ui/Launchable"),
                "finishLaunching",
                "([Ljava/lang/String;)V");
        (*env)->CallVoidMethod(env,
                jLaunchable,
                finishLaunchingMethod,
                jLaunchableArgs);
        GLASS_CHECK_EXCEPTION(env);
    }
    DEBUG_PRINTF("X11Application finishLaunching complete\n");

    matRunLoop(env); 
}


/*
 * Class:     Java_com_sun_glass_ui_x11_X11Application__1terminate
 * Method:    _terminate
 * Signature: ()V
 */
                       
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Application__1terminate
  (JNIEnv *env, jobject japplication, jboolean isEventThread)
{
    DEBUG_PRINTF("X11Application__terminate\n");
    matCloseDisplay(isEventThread);
}

/*
 * Class:     com_sun_glass_ui_Application
 * Method:    _postOnEventQueue
 * Signature: (Ljava/lang/Runnable;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Application__1postOnEventQueue
  (JNIEnv *env, jobject japplication, jobject jRunnable) {
    /* Use XPutBackEvent of ClientMessage event to post Runnable on the X11 event queue */
 #if DEBUG_RUNNABLE
    DEBUG_PRINTF("X11Application__postOnEventQueue\n");
 #endif
    mat_runOnEventThread(env, PostEventInvokeLater, jRunnable);
}

/*
 * Class:     com_sun_glass_ui_Application
 * Method:    _invokeAndWait
 * Signature: (Ljava/lang/Runnable;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Application__1invokeAndWait
  (JNIEnv *env, jobject jApplication, jobject jRunnable)
{
 #if DEBUG_RUNNABLE
    DEBUG_PRINTF("X11Application__invokeAndWait\n");
 #endif
    pthread_mutex_lock(&postMutex);
    waitingOnRunnable = 1;

    mat_runOnEventThread(env, PostEventInvokeAndWait, jRunnable);

    pthread_cond_wait(&postCond, &postMutex);
    pthread_mutex_unlock(&postMutex);

}

/*
 * Class:     com_sun_glass_ui_Application
 * Method:    _invokeLater
 * Signature: (Ljava/lang/Runnable;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Application__1invokeLater
  (JNIEnv *env, jobject jApplication, jobject jRunnable)
{
 #if DEBUG_RUNNABLE
    DEBUG_PRINTF("X11Application__invokeLater\n");
 #endif
    mat_runOnEventThread(env, PostEventInvokeLater, jRunnable);
}

char * mat_RuntimeException = "java/lang/RuntimeException";

void
mat_JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg)
{
     jclass cls = (*env)->FindClass(env, name);
     /* if cls is NULL, an exception has already been thrown */
     if (cls != NULL) {
         (*env)->ThrowNew(env, cls, msg);
     } else {
         fprintf(stderr, "EXCEPTION: not found %s(%s)\n", name, msg);
     }
     /* free the local ref */
     (*env)->DeleteLocalRef(env, cls);
}

