#include "glass_window.hpp"
#include "glass_general.h"
#include "glass_gtkcompat.h"
#include "glass_key.h"

#include <com_sun_glass_events_WindowEvent.h>
#include <com_sun_glass_events_ViewEvent.h>
#include <com_sun_glass_events_MouseEvent.h>
#include <com_sun_glass_events_KeyEvent.h>

extern "C" {
#include <X11/extensions/shape.h>
#include <cairo.h>
#include <cairo-xlib.h>
#include <gdk/gdkx.h>
#include <gdk/gdk.h>

#include <string.h>
}

#include <iostream>

GdkWindow* WindowContextBase::get_gdk_window(){
    return gdk_window;
}

jobject WindowContextBase::get_jview() {
    return jview;
}

jobject WindowContextBase::get_jwindow() {
    return jwindow;
}

bool WindowContextBase::isEnabled() {
    return (JNI_TRUE == mainEnv->CallBooleanMethod(jwindow, jWindowIsEnabled));
}

void WindowContextBase::process_focus(GdkEventFocus* event) {
    mainEnv->CallVoidMethod(jwindow, jWindowNotifyFocus,
            event->in ? com_sun_glass_events_WindowEvent_FOCUS_GAINED : com_sun_glass_events_WindowEvent_FOCUS_LOST);
}

void WindowContextBase::process_destroy() {
    mainEnv->CallVoidMethod(jwindow, jWindowNotifyDestroy);
}

void WindowContextBase::process_delete() {
    mainEnv->CallVoidMethod(jwindow, jWindowNotifyClose);
}

void WindowContextBase::process_expose(GdkEventExpose* event) {
    if (cairo_surface != NULL) {
        cairo_t *context = gdk_cairo_create(gdk_window);
        cairo_set_source_surface(context, cairo_surface, 0, 0);
        cairo_set_operator(context, CAIRO_OPERATOR_SOURCE);
        cairo_paint(context);

        cairo_destroy(context);
    }
}

static inline jint gtk_button_number_to_mouse_button(guint button) {
    switch (button) {
        case 1:
            return com_sun_glass_events_MouseEvent_BUTTON_LEFT;
        case 2:
            return com_sun_glass_events_MouseEvent_BUTTON_OTHER;
        case 3:
            return com_sun_glass_events_MouseEvent_BUTTON_RIGHT;
        default:
            // Other buttons are not supported by quantum and are not reported by other platforms
            return com_sun_glass_events_MouseEvent_BUTTON_NONE;
    }
}

void WindowContextBase::process_mouse_button(GdkEventButton* event) {
    bool press = event->type == GDK_BUTTON_PRESS;
    guint state = event->state;
    guint mask = 0;

    // We need to add/remove current mouse button from the modifier flags
    // as X lib state represents the state just prior to the event and
    // glass needs the state just after the event
    switch (event->button) {
        case 1:
            mask = GDK_BUTTON1_MASK;
            break;
        case 2:
            mask = GDK_BUTTON2_MASK;
            break;
        case 3:
            mask = GDK_BUTTON3_MASK;
            break;
    }

    if (press) {
        state |= mask;
    } else {
        state &= ~mask;
    }

    if (press) {
        GdkDevice* device = event->device;

        if (glass_gdk_device_is_grabbed(device)
                && (glass_gdk_device_get_window_at_position(device, NULL, NULL)
                == NULL)) {
            glass_gdk_mouse_devices_ungrab();

            mainEnv->CallVoidMethod(jwindow, jWindowNotifyFocusUngrab);

            return;
        }
    }

    if (jview) {
        mainEnv->CallVoidMethod(jview, jViewNotifyMouse,
                press ? com_sun_glass_events_MouseEvent_DOWN : com_sun_glass_events_MouseEvent_UP,
                gtk_button_number_to_mouse_button(event->button),
                (jint) event->x, (jint) event->y,
                (jint) event->x_root, (jint) event->y_root,
                gdk_modifier_mask_to_glass(state),
                event->button == 3 ? JNI_TRUE : JNI_FALSE,
                JNI_FALSE);

        if (event->button == 3 && press) {
            mainEnv->CallVoidMethod(jview, jViewNotifyMenu,
                    (jint)event->x, (jint)event->y,
                    (jint)event->x_root, (jint)event->y_root,
                    JNI_FALSE);
        }
    }

}

void WindowContextBase::process_mouse_motion(GdkEventMotion* event) {
    jint glass_modifier = gdk_modifier_mask_to_glass(event->state);
    jint isDrag = glass_modifier & (
            com_sun_glass_events_KeyEvent_MODIFIER_BUTTON_PRIMARY |
            com_sun_glass_events_KeyEvent_MODIFIER_BUTTON_SECONDARY);
    jint button = com_sun_glass_events_MouseEvent_BUTTON_NONE;

    if (glass_modifier & com_sun_glass_events_KeyEvent_MODIFIER_BUTTON_PRIMARY) {
        button = com_sun_glass_events_MouseEvent_BUTTON_LEFT;
    } else if (glass_modifier & com_sun_glass_events_KeyEvent_MODIFIER_BUTTON_SECONDARY) {
        button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT;
    }

    if (jview) {
        mainEnv->CallVoidMethod(jview, jViewNotifyMouse,
                isDrag ? com_sun_glass_events_MouseEvent_DRAG : com_sun_glass_events_MouseEvent_MOVE,
                button,
                (jint) event->x, (jint) event->y,
                (jint) event->x_root, (jint) event->y_root,
                glass_modifier,
                JNI_FALSE,
                JNI_FALSE);
    }
}

void WindowContextBase::process_mouse_scroll(GdkEventScroll* event) {
    jdouble dx = 0;
    jdouble dy = 0;

    // converting direction to change in pixels
    switch (event->direction) {
        case GDK_SCROLL_UP:
            dy = 1;
            break;
        case GDK_SCROLL_DOWN:
            dy = -1;
            break;
        case GDK_SCROLL_LEFT:
            dx = 1;
            break;
        case GDK_SCROLL_RIGHT:
            dx = -1;
            break;
    }

    if (jview) {
        mainEnv->CallVoidMethod(jview, jViewNotifyScroll,
                (jint) event->x, (jint) event->y,
                (jint) event->x_root, (jint) event->y_root,
                dx, dy,
                gdk_modifier_mask_to_glass(event->state),
                (jint) 0, (jint) 0,
                (jint) 0, (jint) 0,
                (jdouble) 40.0, (jdouble) 40.0);
    }

}

void WindowContextBase::process_mouse_cross(GdkEventCrossing* event) {
    bool enter = event->type == GDK_ENTER_NOTIFY;
    if (jview) {
        mainEnv->CallVoidMethod(jview, jViewNotifyMouse,
                enter ? com_sun_glass_events_MouseEvent_ENTER : com_sun_glass_events_MouseEvent_EXIT,
                com_sun_glass_events_MouseEvent_BUTTON_NONE,
                (jint) event->x, (jint) event->y,
                (jint) event->x_root, (jint) event->y_root,
                gdk_modifier_mask_to_glass(event->state),
                JNI_FALSE,
                JNI_FALSE);
    }
}

void WindowContextBase::process_key(GdkEventKey* event) {
    bool press = event->type == GDK_KEY_PRESS;
    guint keyValue;
    gint state = 0;
    state |= event->state & GDK_MOD2_MASK; //NumLock test
    gdk_keymap_translate_keyboard_state(gdk_keymap_get_default(),
            event->hardware_keycode, static_cast<GdkModifierType>(state), event->group,
            &keyValue, NULL, NULL, NULL);
    jint glassKey = gdk_keyval_to_glass(keyValue);
    jint glassModifier = gdk_modifier_mask_to_glass(event->state);
    if (press) {
        glassModifier |= glass_key_to_modifier(glassKey);
    } else {
        glassModifier &= ~glass_key_to_modifier(glassKey);
    }
    jcharArray jChars;
    jchar key = gdk_keyval_to_unicode(event->keyval);
    if (key >= 'a' && key <= 'z' && (event->state & GDK_CONTROL_MASK)) {
        key = key - 'a' + 1; // map 'a' to ctrl-a, and so on.
    } else {
        key = glass_gtk_fixup_typed_key(key, event->keyval);
    }

    if (key > 0) {
        jChars = mainEnv->NewCharArray(1);
        mainEnv->SetCharArrayRegion(jChars, 0, 1, &key);

    } else {
        jChars = mainEnv->NewCharArray(0);
    }
    if (jview) {
        if (press) {
            mainEnv->CallVoidMethod(jview, jViewNotifyKey,
                    com_sun_glass_events_KeyEvent_PRESS,
                    glassKey,
                    jChars,
                    glassModifier);
            CHECK_JNI_EXCEPTION(mainEnv)
                    // TYPED events should only be sent for printable characters.
            if (key > 0) {
                mainEnv->CallVoidMethod(jview, jViewNotifyKey,
                        com_sun_glass_events_KeyEvent_TYPED,
                        com_sun_glass_events_KeyEvent_VK_UNDEFINED,
                        jChars,
                        glassModifier);
                CHECK_JNI_EXCEPTION(mainEnv)
            }
        } else {
            mainEnv->CallVoidMethod(jview, jViewNotifyKey,
                    com_sun_glass_events_KeyEvent_RELEASE,
                    glassKey,
                    jChars,
                    glassModifier);
            CHECK_JNI_EXCEPTION(mainEnv)
        }
    }
}

void WindowContextBase::paint(void* data, jint width, jint height)
{
    cairo_t* context;
    context = gdk_cairo_create(GDK_DRAWABLE(gdk_window));

    if (cairo_surface != NULL) {
        cairo_surface_destroy(cairo_surface);
    }
    cairo_surface = cairo_image_surface_create_for_data(
            (unsigned char*)data,
            CAIRO_FORMAT_ARGB32,
            width, height, width * 4);

    applyShapeMask(width, height);

    cairo_set_source_surface(context, cairo_surface, 0, 0);
    cairo_set_operator (context, CAIRO_OPERATOR_SOURCE);
    cairo_paint(context);

    cairo_destroy(context);
}

void WindowContextBase::add_child(WindowContext* child) {
    children.insert(child);
}

void WindowContextBase::remove_child(WindowContext* child) {
    children.erase(child);
}

void WindowContextBase::show_or_hide_children(bool show) {
    std::set<WindowContext*>::iterator it;
    for (it = children.begin(); it != children.end(); ++it) {
        (*it)->set_visible(show);
        (*it)->show_or_hide_children(show);
    }
}

void WindowContextBase::set_visible(bool visible) {
    if (visible) {
        gtk_widget_show(gtk_widget);
    } else {
        gtk_widget_hide(gtk_widget);
    }
}

bool WindowContextBase::is_visible() {
    return gtk_widget_get_visible(gtk_widget);
}

void WindowContextBase::set_view(jobject view) {

    if (jview) {
        mainEnv->DeleteGlobalRef(jview);
    }

    if (view) {
        gint width, height;
        jview = mainEnv->NewGlobalRef(view);
        gtk_window_get_size(GTK_WINDOW(gtk_widget), &width, &height);
        mainEnv->CallVoidMethod(view, jViewNotifyResize, width, height);
    } else {
        jview = NULL;
    }

}

bool WindowContextBase::grab_focus() {
    return glass_gdk_mouse_devices_grab(gdk_window);
}

void WindowContextBase::set_cursor(GdkCursor* cursor) {
    gdk_window_set_cursor(gdk_window, cursor);
}

WindowContextBase::~WindowContextBase() {
    if (jview) {
        mainEnv->DeleteGlobalRef(jview);
    }
    if (xim.ic) {
        XDestroyIC(xim.ic);
    }
    if (xim.im) {
        XCloseIM(xim.im);
    }
    if (cairo_surface) {
        cairo_surface_destroy(cairo_surface);
    }
    gtk_widget_destroy(gtk_widget);
    mainEnv->DeleteGlobalRef(jwindow);
}

////////////////////////////// WindoContextTop /////////////////////////////////


WindowContextTop::WindowContextTop(jobject _jwindow, WindowContext* _owner, long _screen,
        WindowFrameType _frame_type, WindowType type)
: WindowContextBase(), screen(_screen), frame_type(_frame_type),
        owner(_owner), geometry(), stale_config_notifications(), resizable(), xshape(), frame_extents_initialized()
{
    jwindow = mainEnv->NewGlobalRef(_jwindow);
    if (owner) {
        owner->add_child(this);
    }
    gtk_widget =  gtk_window_new(type == POPUP? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL);

    if (type == UTILITY) {
        gtk_window_set_type_hint(GTK_WINDOW(gtk_widget), GDK_WINDOW_TYPE_HINT_UTILITY);
    }

    glong xdisplay = (glong)mainEnv->GetStaticLongField(jApplication, jDisplay);
    gint  xscreenID = (gint)mainEnv->GetStaticIntField(jApplication, jScreen);
    glong xvisualID = (glong)mainEnv->GetStaticLongField(jApplication, jVisualID);

    if (xvisualID != 0) {
        GdkVisual *visual = gdk_x11_screen_lookup_visual(gdk_screen_get_default(), xvisualID);
        glass_gtk_window_configure_from_visual(gtk_widget, visual);
    }

    gtk_widget_set_size_request(gtk_widget, 0, 0);
    gtk_widget_set_events(gtk_widget, GDK_ALL_EVENTS_MASK);
    gtk_widget_set_app_paintable(gtk_widget, TRUE);
    if (frame_type != TITLED) {
        gtk_window_set_decorated(GTK_WINDOW(gtk_widget), FALSE);
    }

    glass_gtk_configure_transparency_and_realize(gtk_widget, frame_type == TRANSPARENT);
    gtk_window_set_title(GTK_WINDOW(gtk_widget), "");

    gdk_window = gtk_widget_get_window(gtk_widget);

    g_object_set_data_full(G_OBJECT(gdk_window), GDK_WINDOW_DATA_CONTEXT, this, NULL);

    gdk_window_register_dnd(gdk_window);

    if (frame_type == TITLED) {
        request_frame_extents();
    }
}

static GdkAtom
get_net_frame_extents_atom() {
    static const char * extents_str = "_NET_FRAME_EXTENTS";
    return gdk_atom_intern(extents_str, TRUE);
}

void
WindowContextTop::request_frame_extents() {
    Display *display = GDK_WINDOW_XDISPLAY(gdk_window);
    Atom rfeAtom = XInternAtom(display, "_NET_REQUEST_FRAME_EXTENTS", True);
    if (rfeAtom != None) {
        XClientMessageEvent clientMessage;
        memset(&clientMessage, 0, sizeof(clientMessage));

        clientMessage.type = ClientMessage;
        clientMessage.window = GDK_WINDOW_XID(gdk_window);
        clientMessage.message_type = rfeAtom;
        clientMessage.format = 32;

        XSendEvent(display, XDefaultRootWindow(display), False,
                   SubstructureRedirectMask | SubstructureNotifyMask,
                   (XEvent *) &clientMessage);
        XFlush(display);
    }
}

void
WindowContextTop::initialize_frame_extents() {
    int top, left, bottom, right;
    if (get_frame_extents_property(&top, &left, &bottom, &right)) {
        geometry.extents.top = top;
        geometry.extents.left = left;
        geometry.extents.bottom = bottom;
        geometry.extents.right = right;
    }
}

bool
WindowContextTop::get_frame_extents_property(int *top, int *left,
        int *bottom, int *right) {
    unsigned long *extents;

    if (gdk_property_get(gdk_window,
            get_net_frame_extents_atom(),
            gdk_atom_intern("CARDINAL", FALSE),
            0,
            sizeof (unsigned long) * 4,
            FALSE,
            NULL,
            NULL,
            NULL,
            (guchar**) & extents)) {
        *left = extents [0];
        *right = extents [1];
        *top = extents [2];
        *bottom = extents [3];

        g_free(extents);
        return true;
    }

    return false;
}


static int geometry_get_window_width(const WindowGeometry *windowGeometry) {
     return (windowGeometry->final_width.type != BOUNDSTYPE_WINDOW)
                   ? windowGeometry->final_width.value
                         + windowGeometry->extents.left
                         + windowGeometry->extents.right
                   : windowGeometry->final_width.value;
}

static int geometry_get_window_height(const WindowGeometry *windowGeometry) {
    return (windowGeometry->final_height.type != BOUNDSTYPE_WINDOW)
                   ? windowGeometry->final_height.value
                         + windowGeometry->extents.top
                         + windowGeometry->extents.bottom
                   : windowGeometry->final_height.value;
}

static int geometry_get_content_width(WindowGeometry *windowGeometry) {
    return (windowGeometry->final_width.type != BOUNDSTYPE_CONTENT)
                   ? windowGeometry->final_width.value
                         - windowGeometry->extents.left
                         - windowGeometry->extents.right
                   : windowGeometry->final_width.value;
}
static int geometry_get_content_height(WindowGeometry *windowGeometry) {
    return (windowGeometry->final_height.type != BOUNDSTYPE_CONTENT)
                   ? windowGeometry->final_height.value
                         - windowGeometry->extents.top
                         - windowGeometry->extents.bottom
                   : windowGeometry->final_height.value;
}

static int geometry_get_window_x(const WindowGeometry *windowGeometry) {
    float value = windowGeometry->refx;
    if (windowGeometry->gravity_x != 0) {
        value -= geometry_get_window_width(windowGeometry)
                     * windowGeometry->gravity_x;
    }
    return (int) value;
}

static int geometry_get_window_y(const WindowGeometry *windowGeometry) {
    float value = windowGeometry->refy;
    if (windowGeometry->gravity_y != 0) {
        value -= geometry_get_window_height(windowGeometry)
                     * windowGeometry->gravity_y;
    }
    return (int) value;
}

static void geometry_set_window_x(WindowGeometry *windowGeometry, int value) {
    float newValue = value;
    if (windowGeometry->gravity_x != 0) {
        newValue += geometry_get_window_width(windowGeometry)
                * windowGeometry->gravity_x;
    }
    windowGeometry->refx = newValue;
}

static void geometry_set_window_y(WindowGeometry *windowGeometry, int value) {
    float newValue = value;
    if (windowGeometry->gravity_y != 0) {
        newValue += geometry_get_window_height(windowGeometry)
                * windowGeometry->gravity_y;
    }
    windowGeometry->refy = newValue;
}

void WindowContextTop::process_property_notify(GdkEventProperty* event) {
    if (event->atom == get_net_frame_extents_atom() &&
            event->window == gdk_window) {
        int top, left, bottom, right;
        if (get_frame_extents_property(&top, &left, &bottom, &right)) {
            int oldX = geometry_get_window_x(&geometry);
            int oldY = geometry_get_window_y(&geometry);
            int oldWidth = geometry_get_content_width(&geometry);
            int oldHeight = geometry_get_content_height(&geometry);

            geometry.extents.top = top;
            geometry.extents.left = left;
            geometry.extents.bottom = bottom;
            geometry.extents.right = right;

            XWindowChanges windowChanges;
            unsigned int windowChangesMask = 0;

            int newX = geometry_get_window_x(&geometry);
            int newY = geometry_get_window_y(&geometry);
            int newWidth = geometry_get_content_width(&geometry);
            int newHeight = geometry_get_content_height(&geometry);

            if (oldX != newX) {
                windowChanges.x = newX;
                windowChangesMask |= CWX;
            }

            if (oldY != newY) {
                windowChanges.y = newY;
                windowChangesMask |= CWY;
            }

            if (oldWidth != newWidth) {
                windowChanges.width = newWidth;
                windowChangesMask |= CWWidth;
            }

            if (oldHeight != newHeight) {
                windowChanges.height = newHeight;
                windowChangesMask |= CWHeight;
            }

            window_configure(&windowChanges, windowChangesMask);

            if (jview) {
                mainEnv->CallVoidMethod(jview, jViewHandleViewEvent, com_sun_glass_events_ViewEvent_MOVE);
            }
        }
    }
}

static glong getScreenPtrForLocation(gint x, gint y) { //TODO: refactor to GlassApplication.cpp ?
    //Note: we are relying on the fact that javafx_screen_id == gdk_monitor_id
    return gdk_screen_get_monitor_at_point(gdk_screen_get_default(), x, y);
}

void WindowContextTop::process_configure(GdkEventConfigure* event) {

    geometry.current_width = event->width + geometry.extents.left
                                           + geometry.extents.right;
    geometry.current_height = event->height + geometry.extents.top
                                             + geometry.extents.bottom;

    int x, y;
    gtk_window_get_position(GTK_WINDOW(gtk_widget), &x, &y);

    if (stale_config_notifications == 0) {
        if ((geometry_get_content_width(&geometry) != event->width)
                || (geometry_get_content_height(&geometry) != event->height)) {
            geometry.final_width.value = event->width;
            geometry.final_width.type = BOUNDSTYPE_CONTENT;
            geometry.final_height.value = event->height;
            geometry.final_height.type = BOUNDSTYPE_CONTENT;
        }
        geometry_set_window_x(&geometry, x);
        geometry_set_window_y(&geometry, y);
    } else {
        --stale_config_notifications;
    }
    if (jview) {
        mainEnv->CallVoidMethod(jview, jViewNotifyResize,
                event->width,
                event->height);
        CHECK_JNI_EXCEPTION(mainEnv)
    }
    mainEnv->CallVoidMethod(jwindow, jWindowNotifyResize,
            com_sun_glass_events_WindowEvent_RESIZE,
            geometry.current_width,
            geometry.current_height);
    CHECK_JNI_EXCEPTION(mainEnv)

    mainEnv->CallVoidMethod(jwindow, jWindowNotifyMove, x, y);
    CHECK_JNI_EXCEPTION(mainEnv)

    glong to_screen = getScreenPtrForLocation(x, y);
    if (to_screen != -1) {
        if (to_screen != screen) {
            //notify screen changed
            mainEnv->CallVoidMethod(jwindow, jWindowNotifyMoveToAnotherScreen, screen, (jlong) to_screen);
            CHECK_JNI_EXCEPTION(mainEnv)
            screen = to_screen;
        }
    }
}

void WindowContextTop::set_window_resizable(bool res, bool grip) {
    if(!res) {
        int w = geometry_get_content_width(&geometry);
        int h = geometry_get_content_height(&geometry);
        if (w == -1 && h == -1) {
            gtk_window_get_size(GTK_WINDOW(gtk_widget), &w, &h);
        }
        GdkGeometry geom = {w, h, w, h, 0, 0, 0, 0, 0.0, 0.0, GDK_GRAVITY_NORTH_WEST};
        gtk_window_set_geometry_hints(GTK_WINDOW(gtk_widget), NULL, &geom,
                static_cast<GdkWindowHints>(GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE));
        GLASS_GTK_WINDOW_SET_HAS_RESIZE_GRIP(gdk_window, FALSE);
        resizable.prev = resizable.value;
        resizable.value = false;
    } else {
        GdkGeometry geom = {1, 1, 100000, 100000, 0, 0, 0, 0, 0.0, 0.0, GDK_GRAVITY_NORTH_WEST};
        gtk_window_set_geometry_hints(GTK_WINDOW(gtk_widget), NULL, &geom,
                static_cast<GdkWindowHints>(GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE));
        if (grip) {
            GLASS_GTK_WINDOW_SET_HAS_RESIZE_GRIP(gdk_window, TRUE);
        }
        resizable.prev = resizable.value;
        resizable.value = true;
    }
}

void WindowContextTop::set_resizable(bool res) {
    gint w, h;
    gtk_window_get_size(GTK_WINDOW(gtk_widget), &w, &h);
    if (w <= 1 || h <= 1) {
        //Since window is not ready yet set only request for change of resizable.
        resizable.request  = res ? REQUEST_RESIZABLE : REQUEST_NOT_RESIZABLE;
    } else {
        set_window_resizable(res, true);
    }
}


void WindowContextTop::set_bounds(int x, int y, bool xSet, bool ySet, int w, int h, int cw, int ch) {
    if (!frame_extents_initialized && frame_type == TITLED) {
        initialize_frame_extents();
        frame_extents_initialized = true;
    }

    XWindowChanges windowChanges;
    unsigned int windowChangesMask = 0;
    if (w > 0) {
        geometry.final_width.value = w;
        geometry.final_width.type = BOUNDSTYPE_WINDOW;
        geometry.current_width = geometry_get_window_width(&geometry);
        windowChanges.width = geometry_get_content_width(&geometry);
        windowChangesMask |= CWWidth;
    } else if (cw > 0) {
        geometry.final_width.value = cw;
        geometry.final_width.type = BOUNDSTYPE_CONTENT;
        geometry.current_width = geometry_get_window_width(&geometry);
        windowChanges.width = geometry_get_content_width(&geometry);
        windowChangesMask |= CWWidth;
    }

    if (h > 0) {
        geometry.final_height.value = h;
        geometry.final_height.type = BOUNDSTYPE_WINDOW;
        geometry.current_height = geometry_get_window_height(&geometry);
        windowChanges.height = geometry_get_content_height(&geometry);
        windowChangesMask |= CWHeight;
    } else if (ch > 0) {
        geometry.final_height.value = ch;
        geometry.final_height.type = BOUNDSTYPE_CONTENT;
        geometry.current_height = geometry_get_window_height(&geometry);
        windowChanges.height = geometry_get_content_height(&geometry);
        windowChangesMask |= CWHeight;
    }

    if (xSet) {
        geometry.refx = x + geometry.current_width * geometry.gravity_x;
        windowChanges.x = geometry_get_window_x(&geometry);
        windowChangesMask |= CWX;

    } else if ((geometry.gravity_x != 0) && (windowChangesMask & CWWidth)) {
        windowChanges.x = geometry_get_window_x(&geometry);
        windowChangesMask |= CWX;
    }

    if (ySet) {
        geometry.refy = y + geometry.current_height * geometry.gravity_y;
        windowChanges.y = geometry_get_window_y(&geometry);
        windowChangesMask |= CWY;

    } else if ((geometry.gravity_y != 0) && (windowChangesMask & CWHeight)) {
        windowChanges.y = geometry_get_window_y(&geometry);
        windowChangesMask |= CWY;
    }

    window_configure(&windowChanges, windowChangesMask);

    if (resizable.request != REQUEST_NONE) {
        set_window_resizable(resizable.request == REQUEST_RESIZABLE, true);
        resizable.request = REQUEST_NONE;
    }
}

void WindowContextTop::window_configure(XWindowChanges *windowChanges,
        unsigned int windowChangesMask) {
    if (windowChangesMask == 0) {
        return;
    }

    if (!gtk_widget_get_visible(gtk_widget)) {
        // not visible yet, synchronize with gtk only
        if (windowChangesMask & (CWX | CWY)) {
            gint newX, newY;
            gtk_window_get_position(GTK_WINDOW(gtk_widget), &newX, &newY);

            if (windowChangesMask & CWX) {
                newX = windowChanges->x;
            }
            if (windowChangesMask & CWY) {
                newY = windowChanges->y;
            }

            gtk_window_move(GTK_WINDOW(gtk_widget), newX, newY);
        }

        if (windowChangesMask & (CWWidth | CWHeight)) {
            gint newWidth, newHeight;
            gtk_window_get_size(GTK_WINDOW(gtk_widget),
                    &newWidth, &newHeight);

            if (windowChangesMask & CWWidth) {
                newWidth = windowChanges->width;
            }
            if (windowChangesMask & CWHeight) {
                newHeight = windowChanges->height;
            }

            gtk_window_resize(GTK_WINDOW(gtk_widget), newWidth, newHeight);
        }
        stale_config_notifications = 1;
        return;
    }

    ++stale_config_notifications;

    if (!resizable.value && (windowChangesMask & (CWWidth | CWHeight))) {
        XSizeHints *sizeHints = XAllocSizeHints();
        if (sizeHints != NULL) {
            int fixedWidth = (windowChangesMask & CWWidth)
                    ? windowChanges->width
                    : geometry_get_content_width(&geometry);
            int fixedHeight = (windowChangesMask & CWHeight)
                    ? windowChanges->height
                    : geometry_get_content_height(&geometry);

            sizeHints->flags = PMinSize | PMaxSize;

            sizeHints->min_width = 1;
            sizeHints->min_height = 1;
            sizeHints->max_width = INT_MAX;
            sizeHints->max_height = INT_MAX;
            XSetWMNormalHints(GDK_WINDOW_XDISPLAY(gdk_window),
                    GDK_WINDOW_XID(gdk_window),
                    sizeHints);

            XConfigureWindow(GDK_WINDOW_XDISPLAY(gdk_window),
                    GDK_WINDOW_XID(gdk_window),
                    windowChangesMask,
                    windowChanges);

            sizeHints->min_width = fixedWidth;
            sizeHints->min_height = fixedHeight;
            sizeHints->max_width = fixedWidth;
            sizeHints->max_height = fixedHeight;
            XSetWMNormalHints(GDK_WINDOW_XDISPLAY(gdk_window),
                    GDK_WINDOW_XID(gdk_window),
                    sizeHints);

            XFree(sizeHints);
            return;
        }
    }

    XConfigureWindow(GDK_WINDOW_XDISPLAY(gdk_window),
            GDK_WINDOW_XID(gdk_window),
            windowChangesMask,
            windowChanges);
}

void WindowContextTop::process_state(GdkEventWindowState *event) {
    if (event->changed_mask & (GDK_WINDOW_STATE_ICONIFIED
            | GDK_WINDOW_STATE_MAXIMIZED)) {
        jint stateChangeEvent;

        if (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
            stateChangeEvent = com_sun_glass_events_WindowEvent_MINIMIZE;
        } else if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
            stateChangeEvent = com_sun_glass_events_WindowEvent_MAXIMIZE;
        } else {
            stateChangeEvent = com_sun_glass_events_WindowEvent_RESTORE;

            int w, h;
            glass_gdk_window_get_size(gdk_window, &w, &h);
            if (jview) {
                mainEnv->CallVoidMethod(jview,
                        jViewNotifyRepaint,
                        0, 0, w, h);
                CHECK_JNI_EXCEPTION(mainEnv);
            }
        }

        mainEnv->CallVoidMethod(jwindow,
                jGtkWindowNotifyStateChanged,
                stateChangeEvent);
        CHECK_JNI_EXCEPTION(mainEnv);
    } else if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
        if (jview) {
            mainEnv->CallVoidMethod(jview, jViewHandleViewEvent,
                    (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) ?
                    com_sun_glass_events_ViewEvent_FULLSCREEN_ENTER :
                    com_sun_glass_events_ViewEvent_FULLSCREEN_EXIT);
        }
    }
}


void WindowContextTop::applyShapeMask(uint width, uint height)
{
    if (frame_type != TRANSPARENT) {
        return;
    }
    Display *display = GDK_DISPLAY_XDISPLAY(glass_gdk_window_get_display(gdk_window));
    Screen *screen = GDK_SCREEN_XSCREEN(glass_gdk_window_get_screen(gdk_window));

    if (xshape.surface == NULL || width != xshape.width || height != xshape.height) {
        if (xshape.surface != NULL) {
            XFreePixmap(display,
                    xshape.pixmap);
            cairo_surface_destroy(xshape.surface);
        }
        xshape.pixmap = XCreatePixmap(
                display,
                GDK_WINDOW_XID(gdk_window),
                width, height, 1
                );
        xshape.surface = cairo_xlib_surface_create_for_bitmap(
                display,
                xshape.pixmap,
                screen,
                width,
                height
                );
        xshape.width = width;
        xshape.height = height;
    }

    cairo_t *xshape_context = cairo_create(xshape.surface);

    cairo_set_operator(xshape_context, CAIRO_OPERATOR_SOURCE);
    cairo_set_source_surface(xshape_context, cairo_surface, 0, 0);

    cairo_paint(xshape_context);

    int type;
    if (gdk_display_supports_composite(glass_gdk_window_get_display(gdk_window))
            && gdk_screen_is_composited(glass_gdk_window_get_screen(gdk_window))) {
        type = ShapeInput;
    } else {
        type = ShapeBounding;
    }

    XShapeCombineMask(display,
            GDK_WINDOW_XID(gdk_window),
            type,
            0, 0, xshape.pixmap, ShapeSet
            );

    cairo_destroy(xshape_context);
}

void WindowContextTop::set_minimized(bool minimize) {
    if (minimize) {
        gtk_window_iconify(GTK_WINDOW(gtk_widget));
    } else {
        gtk_window_deiconify(GTK_WINDOW(gtk_widget));
    }
}
void WindowContextTop::set_maximized(bool maximize) {
    if (maximize) {
        gtk_window_maximize(GTK_WINDOW(gtk_widget));
    } else {
        gtk_window_unmaximize(GTK_WINDOW(gtk_widget));
    }
}

void WindowContextTop::enter_fullscreen() {
    gtk_window_fullscreen(GTK_WINDOW(gtk_widget));
}

void WindowContextTop::exit_fullscreen() {
    gtk_window_unfullscreen(GTK_WINDOW(gtk_widget));
}

void WindowContextTop::request_focus() {
    gtk_window_present(GTK_WINDOW(gtk_widget));
}

void WindowContextTop::set_focusable(bool focusable) {
    gtk_window_set_accept_focus(GTK_WINDOW(gtk_widget), focusable ? TRUE : FALSE);
}

void WindowContextTop::set_title(const char* title) {
    gtk_window_set_title(GTK_WINDOW(gtk_widget),title);
}

void WindowContextTop::set_alpha(double alpha) {
    gtk_window_set_opacity(GTK_WINDOW(gtk_widget), (gdouble)alpha);
}

void WindowContextTop::set_enabled(bool enabled) {
    if (enabled) {
        //set back proper resizable value.
        set_window_resizable(resizable.prev, true);
    } else {
        //disabled window can't be resizable.
        set_window_resizable(false, false);
    }
}
void WindowContextTop::set_minimum_size(int w, int h) {
    GdkGeometry geom = {w, h, 0, 0, 0, 0, 0, 0, 0.0, 0.0, GDK_GRAVITY_NORTH_WEST};
    gtk_window_set_geometry_hints(GTK_WINDOW(gtk_widget), NULL, &geom, GDK_HINT_MIN_SIZE);
}

void WindowContextTop::set_maximum_size(int w, int h) {
    GdkGeometry geom = {0, 0, w, h, 0, 0, 0, 0, 0.0, 0.0, GDK_GRAVITY_NORTH_WEST};
    gtk_window_set_geometry_hints(GTK_WINDOW(gtk_widget), NULL, &geom, GDK_HINT_MAX_SIZE);
}

void WindowContextTop::set_icon(GdkPixbuf* pixbuf) {
    gtk_window_set_icon(GTK_WINDOW(gtk_widget), pixbuf);
}

void WindowContextTop::restack(bool restack) {
    gdk_window_restack(gdk_window, NULL, restack ? TRUE : FALSE);
}

void WindowContextTop::set_modal(bool modal, WindowContext* parent) {
    if (modal) {
        gtk_window_set_type_hint(GTK_WINDOW(gtk_widget), GDK_WINDOW_TYPE_HINT_DIALOG);
        if (parent) {
            gtk_window_set_transient_for(GTK_WINDOW(gtk_widget), parent->get_gtk_window());
        }
    }
    gtk_window_set_modal(GTK_WINDOW(gtk_widget), modal ? TRUE : FALSE);
}

GtkWindow *WindowContextTop::get_gtk_window() {
    return GTK_WINDOW(gtk_widget);
}

WindowFrameExtents WindowContextTop::get_frame_extents() {
    return geometry.extents;
}

void WindowContextTop::set_gravity(float x, float y) {
    int oldX = geometry_get_window_x(&geometry);
    int oldY = geometry_get_window_y(&geometry);
    geometry.gravity_x = x;
    geometry.gravity_y = y;
    geometry_set_window_x(&geometry, oldX);
    geometry_set_window_y(&geometry, oldY);
}

WindowContextTop::~WindowContextTop() {
    if (owner) {
        owner->remove_child(this);
    }
    if (xshape.surface) {
        cairo_surface_destroy(xshape.surface);
        XFreePixmap(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), xshape.pixmap);
    }
}

////////////////////////////// WindowContextPlug ////////////////////////////////

static gboolean plug_configure(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
    if (event->type == GDK_CONFIGURE) {
        ((WindowContextPlug*)user_data)->process_gtk_configure(&event->configure);
    }
    return FALSE;
}

WindowContextPlug::WindowContextPlug(jobject _jwindow, void* _owner): WindowContextBase(), parent() {
    jwindow = mainEnv->NewGlobalRef(_jwindow);

    gtk_widget = gtk_plug_new((GdkNativeWindow)PTR_TO_JLONG(_owner));

    g_signal_connect(G_OBJECT(gtk_widget), "configure-event", G_CALLBACK(plug_configure), this);

    gtk_widget_set_size_request(gtk_widget, 0, 0);
    gtk_widget_set_events(gtk_widget, GDK_ALL_EVENTS_MASK);
    gtk_widget_set_can_focus(GTK_WIDGET(gtk_widget), TRUE);
    gtk_widget_set_app_paintable(gtk_widget, TRUE);

    gtk_widget_realize(gtk_widget);
    gdk_window = gtk_widget_get_window(gtk_widget);

    g_object_set_data_full(G_OBJECT(gdk_window), GDK_WINDOW_DATA_CONTEXT, this, NULL);
    gdk_window_register_dnd(gdk_window);
}

GtkWindow *WindowContextPlug::get_gtk_window() {
    return GTK_WINDOW(gtk_widget);
}

void WindowContextPlug::process_configure(GdkEventConfigure* event) {
    //Note: process_gtk_configure is used, so there's no need to handle GDK events
}

void WindowContextPlug::process_gtk_configure(GdkEventConfigure* event) {
    if (jview) {
        mainEnv->CallVoidMethod(jview, jViewNotifyResize,
                event->width,
                event->height);
    }

    mainEnv->CallVoidMethod(jwindow, jWindowNotifyResize,
            com_sun_glass_events_WindowEvent_RESIZE,
            event->width,
            event->height);

    if (this->child) {
        child->process_configure(event);
    }
    mainEnv->ExceptionClear();

}

void WindowContextPlug::process_state(GdkEventWindowState *event) {
    if (event->changed_mask & (GDK_WINDOW_STATE_ICONIFIED
            | GDK_WINDOW_STATE_MAXIMIZED)) {
        jint stateChangeEvent;

        if (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
            stateChangeEvent = com_sun_glass_events_WindowEvent_MINIMIZE;
        } else if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
            stateChangeEvent = com_sun_glass_events_WindowEvent_MAXIMIZE;
        } else {
            stateChangeEvent = com_sun_glass_events_WindowEvent_RESTORE;

            int w, h;
            glass_gdk_window_get_size(gdk_window, &w, &h);
            if (jview) {
                mainEnv->CallVoidMethod(jview,
                        jViewNotifyRepaint,
                        0, 0, w, h);
            }
            CHECK_JNI_EXCEPTION(mainEnv);
        }

        mainEnv->CallVoidMethod(jwindow,
                jGtkWindowNotifyStateChanged,
                stateChangeEvent);
        CHECK_JNI_EXCEPTION(mainEnv);
    } else if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
        if (jview) {
            mainEnv->CallVoidMethod(jview, jViewHandleViewEvent,
                    (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) ?
                    com_sun_glass_events_ViewEvent_FULLSCREEN_ENTER :
                    com_sun_glass_events_ViewEvent_FULLSCREEN_EXIT);
        }
    }
}

void WindowContextPlug::set_view(jobject view) {
    // probably never called for applet window
    if (jview) {
        mainEnv->DeleteGlobalRef(jview);
    }

    if (view) {
        gint width, height;
        jview = mainEnv->NewGlobalRef(view);
        gtk_window_get_size(GTK_WINDOW(gtk_widget), &width, &height);
        mainEnv->CallVoidMethod(view, jViewNotifyResize, width, height);
    } else {
        jview = NULL;
    }
}

void WindowContextPlug::window_configure(XWindowChanges *windowChanges,
        unsigned int windowChangesMask) {
    if (windowChangesMask == 0) {
        return;
    }

    if (!gtk_widget_get_visible(gtk_widget)) {
        // not visible yet, synchronize with gtk only
        if (windowChangesMask & (CWX | CWY)) {
            gint newX, newY;
            gtk_window_get_position(GTK_WINDOW(gtk_widget), &newX, &newY);

            if (windowChangesMask & CWX) {
                newX = windowChanges->x;
            }
            if (windowChangesMask & CWY) {
                newY = windowChanges->y;
            }
            gtk_window_move(GTK_WINDOW(gtk_widget), newX, newY);
        }

        if (windowChangesMask & (CWWidth | CWHeight)) {
            gint newWidth, newHeight;
            gtk_window_get_size(GTK_WINDOW(gtk_widget),
                &newWidth, &newHeight);

            if (windowChangesMask & CWWidth) {
                newWidth = windowChanges->width;
            }
            if (windowChangesMask & CWHeight) {
                newHeight = windowChanges->height;
            }
            gtk_window_resize(GTK_WINDOW(gtk_widget), newWidth, newHeight);
            
        }
        return;
    }

    XConfigureWindow(GDK_WINDOW_XDISPLAY(gdk_window),
            GDK_WINDOW_XID(gdk_window),
            windowChangesMask,
            windowChanges);
}

void WindowContextPlug::set_bounds(int x, int y, bool xSet, bool ySet, int w, int h, int cw, int ch) {
    XWindowChanges windowChanges;
    unsigned int windowChangesMask = 0;

    if (xSet) {
        windowChanges.x = x;
        windowChangesMask |= CWX;
    }

    if (ySet) {
        windowChanges.y = y;
        windowChangesMask |= CWY;
    }

    if (w > 0) {
        windowChanges.width = w;
        windowChangesMask |= CWWidth;
    } else if (cw > 0) {
        windowChanges.width = cw;
        windowChangesMask |= CWWidth;
    }

    if (h > 0) {
        windowChanges.height = h;
        windowChangesMask |= CWHeight;
    } else if (ch > 0) {
        windowChanges.height = ch;
        windowChangesMask |= CWHeight;
    }

    window_configure(&windowChanges, windowChangesMask);
}
////////////////////////////// WindowContextChid ////////////////////////////////

void WindowContextChild::process_mouse_button(GdkEventButton* event) {
    WindowContextBase::process_mouse_button(event);
   // gtk_window_set_focus (GTK_WINDOW (gtk_widget_get_ancestor(gtk_widget, GTK_TYPE_WINDOW)), NULL);
    gtk_widget_grab_focus(gtk_widget);
}

static gboolean child_focus_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
    WindowContext *ctx = (WindowContext *)user_data;
    ctx->process_focus(&event->focus_change);
    return TRUE;
}

WindowContextChild::WindowContextChild(jobject _jwindow,
                                       void* _owner,
                                       GtkWidget *parent_widget,
                                       WindowContext *parent_ctx)
        : WindowContextBase(), parent() {
    jwindow = mainEnv->NewGlobalRef(_jwindow);
    gtk_widget = gtk_drawing_area_new();
    parent = parent_ctx;
    //HACK: directly manipulating with parent's child
    dynamic_cast<WindowContextBase*>(parent)->child = this;

    gtk_widget_set_events(gtk_widget, GDK_ALL_EVENTS_MASK);
    gtk_widget_set_can_focus(GTK_WIDGET(gtk_widget), TRUE);
    gtk_widget_set_app_paintable(gtk_widget, TRUE);
    gtk_container_add (GTK_CONTAINER(parent_widget), gtk_widget);
    gtk_widget_realize(gtk_widget);
    gdk_window = gtk_widget_get_window(gtk_widget);
    g_object_set_data_full(G_OBJECT(gdk_window), GDK_WINDOW_DATA_CONTEXT, this, NULL);
    gdk_window_register_dnd(gdk_window);    
    g_signal_connect(gtk_widget, "focus-in-event", G_CALLBACK(child_focus_callback), this);
    g_signal_connect(gtk_widget, "focus-out-event", G_CALLBACK(child_focus_callback), this);
}

GtkWindow *WindowContextChild::get_gtk_window() {
    return GTK_WINDOW(gtk_widget_get_ancestor(gtk_widget, GTK_TYPE_WINDOW));
}

void WindowContextChild::process_configure(GdkEventConfigure* event) {
    if (jview) {
        mainEnv->CallVoidMethod(jview, jViewNotifyResize,
                event->width,
                event->height);
    }

    gtk_widget_set_size_request(gtk_widget, event->width, event->height);

    mainEnv->CallVoidMethod(jwindow, jWindowNotifyResize,
            com_sun_glass_events_WindowEvent_RESIZE,
            event->width,
            event->height);

}

void WindowContextChild::process_state(GdkEventWindowState *event) {
    if (event->changed_mask & (GDK_WINDOW_STATE_ICONIFIED
            | GDK_WINDOW_STATE_MAXIMIZED)) {
        jint stateChangeEvent;

        if (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
            stateChangeEvent = com_sun_glass_events_WindowEvent_MINIMIZE;
        } else if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
            stateChangeEvent = com_sun_glass_events_WindowEvent_MAXIMIZE;
        } else {
            stateChangeEvent = com_sun_glass_events_WindowEvent_RESTORE;

            int w, h;
            glass_gdk_window_get_size(gdk_window, &w, &h);
            if (jview) {
                mainEnv->CallVoidMethod(jview,
                        jViewNotifyRepaint,
                        0, 0, w, h);
            }
            CHECK_JNI_EXCEPTION(mainEnv);
        }

        mainEnv->CallVoidMethod(jwindow,
                jGtkWindowNotifyStateChanged,
                stateChangeEvent);
        CHECK_JNI_EXCEPTION(mainEnv);
    } else if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
        if (jview) {
            mainEnv->CallVoidMethod(jview, jViewHandleViewEvent,
                    (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) ?
                    com_sun_glass_events_ViewEvent_FULLSCREEN_ENTER :
                    com_sun_glass_events_ViewEvent_FULLSCREEN_EXIT);
        }
    }
}

void WindowContextChild::set_view(jobject view) {
    if (jview) {
        mainEnv->DeleteGlobalRef(jview);
    }

    if (view) {
        gint width, height;
        jview = mainEnv->NewGlobalRef(view);
        width = gtk_widget->allocation.width;
        height = gtk_widget->allocation.height;
        mainEnv->CallVoidMethod(view, jViewNotifyResize, width, height);
        JNI_EXCEPTION_TO_CPP(mainEnv);
    } else {
        jview = NULL;
    }
}

void WindowContextChild::set_bounds(int x, int y, bool xSet, bool ySet, int w, int h, int cw, int ch) {

    if (x > 0 || y > 0 || xSet || ySet) {
        gint newX, newY;
        gdk_window_get_origin(gdk_window, &newX, &newY);
        mainEnv->CallVoidMethod(jwindow,
                jWindowNotifyMove,
                newX, newY);
    }

    // As we have no frames, there's no difference between the calls
    if ((cw | ch) > 0) {
        w = cw; h = ch;
    }

    if (w > 0 || h > 0) {
        gint newWidth, newHeight;
        newWidth = gtk_widget->allocation.width;
        newHeight = gtk_widget->allocation.height;

        if (w > 0) {
            newWidth = w;
        }
        if (h > 0) {
            newHeight = h;
        }
        gtk_widget_set_size_request(gtk_widget, newWidth, newHeight);
        // FIXME: hack to set correct size to view
        if (jview) {
            mainEnv->CallVoidMethod(jview,
                    jViewNotifyResize,
                    newWidth, newHeight);
        }
    }
}

int WindowContextChild::getEmbeddedX() {
    int x;
    gdk_window_get_origin(gdk_window, &x, NULL);
    return x;
}

int WindowContextChild::getEmbeddedY() {
    int y;
    gdk_window_get_origin(gdk_window, NULL, &y);
    return y;

}
