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

#include "mat_common.h"

#include <com_sun_glass_ui_x11_X11Window.h>
#include <com_sun_glass_events_WindowEvent.h>

/*
 * window list... This is a native cache of the X11 windows we have, to provide
 * the mapping of Window -> GlassWindow so we can report events.
 *
 * Todo: make sure we remove dead windows
 */

static inline GlassView *getView(JNIEnv *env, jobject jview)
{
    if (jview != NULL)
    {
        return (GlassView*)jlong_to_ptr((*env)->GetLongField(env, jview, mat_jViewPtr));
    }
    else
    {
        return NULL;
    }
}

static int windowListCount = 0;
static GlassWindow **windowList;

int get_byte_order (void) {
        union {
                char c[sizeof(short)];
                short s;
        } order;

        order.s = 1;

        if ((1 == order.c[0])) {
                return LSBFirst;
        } else {
                return MSBFirst;
        }
}

// Manage a list of active windows so we can map events to windows/views
// TODO: should this be using the Window.visibleWindows list

static inline int windowCount() {
    return(windowListCount);
}

static void addWindowToList(GlassWindow *window) {
    if (windowListCount == 0) {
        windowList = (GlassWindow**)(malloc(sizeof(GlassWindow*)));
    } else {
        windowList = (GlassWindow**)(realloc(windowList,sizeof(GlassWindow*)*windowListCount+1));
    }
    windowList[windowListCount] = window;
    windowListCount++;
}

static void removeWindowFromList(GlassWindow *window) {
    if (windowListCount == 1) {
        windowListCount = 0;
        free(windowList);
    } else {
        int i;
        int nextfree = 0;
        for(i=0;i<windowListCount;i++) {
            if (window != windowList[i]) {
                windowList[nextfree++] = windowList[i];
            }
        }
        windowListCount--;
    }
}

GlassWindow* mat_matchXWindow(Window xwindow) {

    int i;
    for(i=0;i<windowListCount;i++) {
        if (xwindow == windowList[i]->window)
            return windowList[i];
    }

    return NULL;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _createWindow
 * Signature: (JJI)J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_x11_X11Window__1createWindow
  (JNIEnv *env, jobject jwindow, jlong jownerPtr, jlong jscreenPtr, jint jstyleMask) {

    Screen *screen = (Screen*)jlong_to_ptr(jscreenPtr);

    Window window = matCreateWindow();

    //Todo: better window creation error checking

    GlassWindow* gw = calloc(sizeof(GlassWindow),1);
    gw->env = env;
    gw->window = window;
    gw->jwindow = (*env)->NewGlobalRef(env,jwindow); //cache handle to ourselves - gc issue ?
    gw->jview = NULL;
    gw->view = NULL;
    gw->button = 0;
    gw->destroyed = 0;
    gw->configureCount = 0;
    gw->x = gw->y = gw->width = gw->height = -1;
    gw->image = NULL;

    addWindowToList(gw);

    return ptr_to_jlong(window);
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _createChildWindow
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_x11_X11Window__1createChildWindow
  (JNIEnv *env, jobject jwindow, jlong parent) {
    mat_JNU_ThrowByName(env, mat_RuntimeException, "Unimplemented");
    return 0L;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _close
 * Signature: (J)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1close
  (JNIEnv *env, jobject jwindow, jlong windowPtr) {

    if (windowPtr) {
        DEBUG_PRINTF("X11Window_close\n");
        Window window = (Window)jlong_to_ptr(windowPtr);

        GlassWindow* gw = mat_matchXWindow(window);
        if (gw != NULL) {
            // take it off the screen
            if (!gw->destroyed) {
                gw->destroyed = 1;
                matCloseWindow(window); 
            }
        }
    }

    return JNI_TRUE;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setView
 * Signature: (JLcom/sun/glass/ui/View;)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1setView
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jobject jview) {
    DEBUG_PRINTF("X11Window_setView\n");

    Window window = (Window)jlong_to_ptr(windowPtr);
    GlassWindow* gw = mat_matchXWindow(window);

    if (gw != NULL) {
        // release the old reference first...
        if (gw->jview != NULL)
            (*env)->DeleteGlobalRef(env,gw->jview);

        if (jview != NULL) {
            //TODO: more to do in setView ?
            GlassView * view = getView(env, jview);
            gw->jview = (*env)->NewGlobalRef(env,jview);
            gw->view = view;
        } else {
            gw->jview = NULL;
            gw->view = NULL;
        }
    }

    return JNI_TRUE;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setMenubar
 * Signature: (JJ)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1setMenubar
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jlong menubarPtr) {
    DEBUG_PRINTF("X11Window_setMenubar\n");

    printf("Warning: X11Window._setMenubar() not supported yet\n");
    return 0;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _minimize
 * Signature: (JZ)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1minimize
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jboolean minimize) {
    DEBUG_PRINTF("X11Window_minimize\n");

    mat_JNU_ThrowByName(env,mat_RuntimeException,"Unimplemented");
    return 0;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _maximize
 * Signature: (JZZ)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1maximize
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jboolean maximize, jboolean wasMaximized) {
    DEBUG_PRINTF("X11Window_maximize\n");

    mat_JNU_ThrowByName(env,mat_RuntimeException,"Unimplemented");
    return 0;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setBounds
 * Signature: (JIIZZIIIIFF)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1setBounds
  (JNIEnv *env, jobject jwindow, jlong windowPtr, 
    jint x, jint y, jboolean xset, jboolean yset, 
    jint width, jint height, jint cw, jint ch,
    jfloat xGravity, jfloat yGravity) {

    DEBUG_PRINTF("X11Window_setBounds(%d,%d,%d,%d,%d,%d,%d,%d)\n",
        x, y, xset, yset, 
        width, height, cw, ch);
    if (windowPtr) {
        Window window = (Window)jlong_to_ptr(windowPtr);
        matSetBounds(window,x,y,xset,yset,width,height,cw,ch);
    }
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setContentSize
 * Signature: (JII)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1setContentSize
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jint width, jint height) {
    DEBUG_PRINTF("X11Window_setContentSize\n");
    if (windowPtr) {
        Window window = (Window)jlong_to_ptr(windowPtr);

        matResize(window, width, height);
    }
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setVisible
 * Signature: (JZ)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1setVisible
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jboolean visible) {
    DEBUG_PRINTF("X11Window_setVisible %d\n",visible);
    if (windowPtr) {
        Window window = (Window)jlong_to_ptr(windowPtr);

        matSetVisible(window, visible); 
    }
    return JNI_TRUE;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _requestFocus
 * Signature: (J)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1requestFocus
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jint event) {
    DEBUG_PRINTF("X11Window_setActivate\n");

    mat_JNU_ThrowByName(env,mat_RuntimeException,"Unimplemented");
    return 0;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setTitle
 * Signature: (JLjava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1setTitle
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jstring title) {

    if (windowPtr) {
        Window window = (Window)jlong_to_ptr(windowPtr);

        const char *titleText = (*env)->GetStringUTFChars(env, title, NULL);
        if (titleText == NULL) {
            return JNI_FALSE;
        }

        DEBUG_PRINTF("X11Window_setTitle(%s)\n",titleText);

        matSetTitle(window,titleText);

        (*env)->ReleaseStringUTFChars(env, title, titleText);
    }
    return JNI_TRUE;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setLevel
 * Signature: (JI)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1setLevel
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jint level) {
    DEBUG_PRINTF("X11Window_setLevel- Unimplemented\n");
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setResizable
 * Signature: (JZ)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1setResizable
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jboolean resizeable) {
    DEBUG_PRINTF("X11Window_setResizable- Unimplemented\n");

    return JNI_TRUE;
}


/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setFocusable
 * Signature: (JZ)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1setFocusable
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jboolean canBecomeActive) {
    DEBUG_PRINTF("X11Window_setCanBecomeActive - Unimplemented\n");
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setAlpha
 * Signature: (JF)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1setAlpha
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jfloat alpha) {
    DEBUG_PRINTF("X11Window_setAlpha(%f)\n",alpha);
    //TODO: can we set a partial alpha ?
    if (alpha != 1.0f)
        printf("Warning: Window alpha not supported with X11\n");
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setBackground
 * Signature: (JFFF)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1setBackground
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jfloat r, jfloat g, jfloat b) {
    DEBUG_PRINTF("X11Window_setbackground\n");
    //Todo: properly set the backgound pixel ? Not needed if the view takes care of it ?
    return JNI_TRUE;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setMinimumSize
 * Signature: (JII)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1setMinimumSize
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jint width, jint height) {
    DEBUG_PRINTF("X11Window_setMinimumSize\n");
    //Todo: set the minimum size hint on the window

    return JNI_FALSE;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setMaximumSize
 * Signature: (JII)Z
 */
JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_x11_X11Window__1setMaximumSize
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jint width, jint height) {
    DEBUG_PRINTF("X11Window_setMaximunSize - unimp\n");
    //Todo: set the maximum size hint on the window
    return JNI_FALSE;
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setIcon
 * Signature: (JIILjava/nio/ByteBuffer;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1setIcon
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jobject pixels) {
    DEBUG_PRINTF("X11Window_toFront - unimp\n");

    //Todo: can we set an icon?
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _toFront
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1toFront
  (JNIEnv *env, jobject jwindow, jlong windowPtr) {
    DEBUG_PRINTF("X11Window_toFront\n");

    mat_JNU_ThrowByName(env,mat_RuntimeException,"Unimplemented");
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _toBack
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1toBack
  (JNIEnv *env, jobject jwindow, jlong windowPtr) {
    DEBUG_PRINTF("X11Window_toBack - unimp\n");
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _enterModal
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1enterModal
  (JNIEnv *env, jobject jwindow, jlong windowPtr) {
    DEBUG_PRINTF("X11Window_enterModal\n");

    mat_JNU_ThrowByName(env,mat_RuntimeException,"Unimplemented");
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _enterModalWithWindow
 * Signature: (JJ)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1enterModalWithWindow
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jlong window) {
    DEBUG_PRINTF("X11Window_enterModalWithWindow\n");

    mat_JNU_ThrowByName(env,mat_RuntimeException,"Unimplemented");
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _exitModal
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1exitModal
  (JNIEnv *env, jobject jwindow, jlong windowPtr) {
    DEBUG_PRINTF("X11Window_exitModal\n");

    mat_JNU_ThrowByName(env,mat_RuntimeException,"Unimplemented");
}

/*
 * Class:     com_sun_glass_ui_x11_X11Window
 * Method:    _setEnabled
 * Signature: (JZ)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Window__1setEnabled
  (JNIEnv *env, jobject jwindow, jlong windowPtr, jboolean enabled) {
    DEBUG_PRINTF("X11Window_setEnabled\n");

    mat_JNU_ThrowByName(env,mat_RuntimeException,"Unimplemented");
}

/******************************************************************************/

void mat_FocusWindow(XEvent *evt, jboolean activate) {

    GlassWindow *gw = mat_matchXWindow(evt->xexpose.window);

    if (gw != NULL) {
        (*gw->env)->CallVoidMethod(gw->env, gw->jwindow, mat_jWindowNotifyFocus, activate ? 
            com_sun_glass_events_WindowEvent_FOCUS_GAINED:
            com_sun_glass_events_WindowEvent_FOCUS_LOST);
    }
}

//Move a window
void mat_MoveWindow(GlassWindow *gw,
    int relX, int relY,
    int screenX, int screenY) {
    DEBUG_PRINTF("mat_MoveWindow rel %d,%d  abs %d,%d\n",
        relX,relY, screenX, screenY);

    if (gw != NULL) {
        // we could cache these in gw if needed.
        int curX =  (*gw->env)->GetIntField(gw->env, gw->jwindow, mat_jWindowX);
        int curY =  (*gw->env)->GetIntField(gw->env, gw->jwindow, mat_jWindowY);

        if ((curX != screenX) || (curY != screenY)) {
            (*gw->env)->CallVoidMethod(gw->env, gw->jwindow, mat_jWindowNotifyMove, screenX, screenY);
            if (gw->jview != NULL) {
                DEBUG_PRINTF("calling ViewNotifyMove %d,%d\n",relX,relY);
                //note: this will change if we have a menubar
                (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jViewNotifyMove, relX, relY);
            }
        }
    }
}

//Move a window
void mat_ResizeWindow(GlassWindow *gw, int width, int height) {
    DEBUG_PRINTF("mat_ResizeWindow %d,%d\n", width, height);

    if (gw != NULL) {
        // we could cache these in gw if needed.
        int curWidth =  (*gw->env)->GetIntField(gw->env, gw->jwindow, mat_jWindowWidth);
        int curHeight =  (*gw->env)->GetIntField(gw->env, gw->jwindow, mat_jWindowHeight);

        if ((curWidth != width) || (curHeight != height)) {
            DEBUG_PRINTF("calling WindowNotifyResize %d,%d\n",width,height);
            (*gw->env)->CallVoidMethod(gw->env, gw->jwindow, mat_jWindowNotifyResize, 
                    com_sun_glass_events_WindowEvent_RESIZE,
                    width, height);

            if (gw->jview != NULL) {
                // we have already done the work here for a resize check - call
                // directly up.
                DEBUG_PRINTF("calling ViewNotifyResize %d,%d\n",width,height);
                (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jViewNotifyResize,
                        width, height);
            }
        }
    }
}

//TODO: can we catch minimize/mazimize ?
void mat_ConfigureWindow(GlassWindow *gw,
    int relX, int relY, 
    int width, int height, 
    int screenX, int screenY) {

    DEBUG_PRINTF("mat_ConfigureWindow %d,%d %dx%x @ %d,%d\n",
        relX,relY, width, height, screenX, screenY);

    if (gw != NULL) {
        // we could cache these in gw if needed.
        int curX =  (*gw->env)->GetIntField(gw->env, gw->jwindow, mat_jWindowX);
        int curY =  (*gw->env)->GetIntField(gw->env, gw->jwindow, mat_jWindowY);
        int curWidth =  (*gw->env)->GetIntField(gw->env, gw->jwindow, mat_jWindowWidth);
        int curHeight =  (*gw->env)->GetIntField(gw->env, gw->jwindow, mat_jWindowHeight);

        if ((curX != screenX) || (curY != screenY)) {
            (*gw->env)->CallVoidMethod(gw->env, gw->jwindow, mat_jWindowNotifyMove, screenX, screenY);
            if (gw->jview != NULL) {
                DEBUG_PRINTF("calling ViewNotifyMove %d,%d\n",relX,relY);
                //note: this will change if we have a menubar
                (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jViewNotifyMove, relX, relY);
            }
        }
        if ((curWidth != width) || (curHeight != height)) {
            DEBUG_PRINTF("calling WindowNotifyResize %d,%d\n",width,height);
            (*gw->env)->CallVoidMethod(gw->env, gw->jwindow, mat_jWindowNotifyResize, 
                    com_sun_glass_events_WindowEvent_RESIZE,
                    width, height);

            if (gw->jview != NULL) {
                // we have already done the work here for a resize check - call
                // directly up.
                DEBUG_PRINTF("calling ViewNotifyResize %d,%d\n",width,height);
                (*gw->env)->CallVoidMethod(gw->env, gw->jview, mat_jViewNotifyResize,
                        width, height);
            }
        }
    }
}

void mat_DestroyWindow(XEvent *evt) {
    GlassWindow *gw = mat_matchXWindow(evt->xexpose.window);
    if (gw != NULL) {
        //and tell Glass it is disposed of.
        (*gw->env)->CallVoidMethod(gw->env, gw->jwindow, mat_jWindowNotifyDestroy);

        // and get rid of the GW construct
        if (gw->jwindow)
            (*gw->env)->DeleteGlobalRef(gw->env,gw->jwindow);
        if (gw->jview)
            (*gw->env)->DeleteGlobalRef(gw->env,gw->jview);
        removeWindowFromList(gw);
        free(gw);
    }
}

void mat_CloseWindow(XEvent *evt) {
    GlassWindow *gw = mat_matchXWindow(evt->xexpose.window);
    if (gw != NULL) {
        // note - this is a request from the window mgr and can be ignored by the app.
        (*gw->env)->CallVoidMethod(gw->env, gw->jwindow, mat_jWindowNotifyClose);
    }
}
