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

#include <stdio.h>
#include "mat_common.h"

#include <string.h>
#include <unistd.h>

#include "com_sun_glass_ui_Pixels.h"
#include "com_sun_glass_ui_x11_X11Pixels.h"

static Bool try_xshm = True; // check to see if xshm is useful
static Bool use_xshm = False; // use XSHM for pushing pixels.

#ifdef USE_XSHM
/* error handler to catch MITSHM problems */ 
int mitshm_handler(Display *d, XErrorEvent *ev)
{
   use_xshm = False;
   return 0;
}
#endif

/*
 * Class:     com_sun_glass_ui_x11_X11Pixels
 * Method:    _close
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Pixels__1close
  (JNIEnv *env, jclass jpixel, jlong ptr) {

    DEBUG_PRINTF("X11Pixels_close\n");

//    XImage* image = (XImage*)jlong_to_ptr(ptr);
//    if (image != NULL)
//        XFree(image);
}


/*
 * Class:     com_sun_glass_ui_x11_X11Pixels
 * Method:    _fillDirectByteBuffer
 * Signature: (Ljava/nio/ByteBuffer;)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Pixels__1fillDirectByteBuffer
  (JNIEnv *env, jclass jpixel, jobject jbytebuffer) {
    DEBUG_PRINTF("X11Pixels_fillDirectByteBuffer\n");

    mat_JNU_ThrowByName(env,mat_RuntimeException,"Unimplemented");
}
static int pixmapDepth = 0;
static int copyPixmapDepth = 0;

/*
 * Class:     com_sun_glass_ui_x11_X11Pixels
 * Method:    _attachInt
 * Signature: (JIILjava/nio/IntBuffer;[II)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Pixels__1attachInt
  (JNIEnv *env, jclass jpixel, jlong windowPtr, jint width, jint height, jobject intBuffer, jintArray array, jint offset) {
    DEBUG_PRINTF("X11Pixels_attachInt\n");

    Window window = (Window)jlong_to_ptr(windowPtr);

    jint *arrayPtr;
    jint *iarr = 0;;
    short *sarr = 0;
    char *dataPtr = 0;
    jint i;
    char *srcPtr, *dstPtr;

    GlassWindow *gw = mat_matchXWindow(window);

    XLockDisplay(mat_x11Display);

    Visual *visual = DefaultVisualOfScreen(DefaultScreenOfDisplay(mat_x11Display));
    int screenID = XDefaultScreen(mat_x11Display);

    if (pixmapDepth == 0) {
        pixmapDepth = DefaultDepth(mat_x11Display, screenID);
        // when we copy the image for the USE_XSHM case, we need to be aware that  
        // the source image is 32 bit even thought the screen may only be 24
        copyPixmapDepth = (pixmapDepth == 16) ? 16 : 32;        
        DEBUG_PRINTF("X11Pixels_attachInt, DefaultDepth is %d\n",pixmapDepth);
    }
    arrayPtr = (int *)(*env)->GetPrimitiveArrayCritical(env, array, NULL);
    if (arrayPtr == NULL) {
        printf("failed to get the array, punting\n");
        return;
    }
    if (pixmapDepth == 16) {
        // convert image to 565
        DEBUG_PRINTF("X11Pixels_attachInt, converting to 565\n");
        sarr = (short *)malloc(width * height * sizeof(short));

        if (!sarr) {
            DEBUG_PRINTF("X11Pixels_attachInt, failed to allocate short array\n");
            (*env)->ReleasePrimitiveArrayCritical(env, array, arrayPtr, 0);
            return;
        }

        for (i = 0; i < width * height; i++) {
            sarr[i] =
                ((arrayPtr[i] >> 8) & 0xF800 ) |
                ((arrayPtr[i] >> 5) & 0x7E0 ) |
                ((arrayPtr[i] >> 3) & 0x1F );
        }
        dataPtr = (char *)sarr;
    } 
    else 
    { // 24 bit 
        iarr = (int *)malloc(width * height * sizeof(int));
        for (i=0; i < width * height; i++) {
            iarr[i] = arrayPtr[i];
        }
        dataPtr = (char *) iarr;
    }
    (*env)->ReleasePrimitiveArrayCritical(env, array, arrayPtr, 0);

#ifdef USE_XSHM
    if (try_xshm)  {
        try_xshm = False;
        int major, minor;
        Bool pixmaps;
        if (XShmQueryVersion(mat_x11Display, &major, &minor, &pixmaps)) {
            if (pixmaps) {
                use_xshm = True;
                DEBUG_PRINTF("use_xshm=True\n");
            }
        }
    }
    if  (use_xshm && gw->image != NULL && 
        (gw->shWidth != width || gw->shHeight != height)) 
    {
        // Free up the old image
        DEBUG_PRINTF("RESIZE of shimage needed\n");
        XShmDetach(mat_x11Display, &gw->shminfo);
        XDestroyImage(gw->image);
        gw->image = NULL;
        shmdt(gw->shminfo.shmaddr);
        shmctl(gw->shminfo.shmid, IPC_RMID, 0);
    }
    if (use_xshm && gw->image == NULL) {
        // Create a new matching shared image.
        DEBUG_PRINTF("Creating shimage\n");
        gw->image =  XShmCreateImage(mat_x11Display, 
                                     visual,
                                     pixmapDepth,
                                     XShmPixmapFormat(mat_x11Display),
                                     NULL, 
                                     &gw->shminfo, 
                                     width, height);
        gw->shminfo.shmid = shmget(IPC_PRIVATE,
                                   gw->image->bytes_per_line*gw->image->height,
                                   IPC_CREAT|0777);
        gw->shminfo.shmaddr = gw->image->data = shmat(gw->shminfo.shmid, 0, 0);

        gw->shWidth = width;
        gw->shHeight = height;

        /* Tell the server to attach */
        XShmAttach(mat_x11Display, &gw->shminfo); 
        XSync(mat_x11Display, 0);
        XSetErrorHandler(mitshm_handler);
        
        shmctl(gw->shminfo.shmid, IPC_RMID, 0);
        /* Mark this shm segment for deletion at once. The segment will
         * automatically become released when both the server and this
         * client have detached from it.
         * (Process termination automagically detach shm segments) */
        
        if (!use_xshm) {
             shmdt(gw->shminfo.shmaddr);
        }
    }
    if (use_xshm) {
        if (gw->image->bytes_per_line == width * copyPixmapDepth / 8) {
            memcpy(gw->image->data, dataPtr, width * height * copyPixmapDepth / 8);
        }
        else { // need to pad each line of the image 
            srcPtr = (char *)dataPtr;
            dstPtr = (char *)(gw->image->data); 
            for (i = 0; i < gw->image->height; i++) {
                memcpy(dstPtr, srcPtr, width * copyPixmapDepth / 8);
                dstPtr += width * copyPixmapDepth / 8;
                memset(dstPtr, 0, 
                       gw->image->bytes_per_line - (width * copyPixmapDepth / 8));
                dstPtr += 
                    (gw->image->bytes_per_line - width * copyPixmapDepth / 8);
                srcPtr += width * copyPixmapDepth / 8;
            }
        }
        DEBUG_PRINTF("XShmPutImage\n");
        XShmPutImage(mat_x11Display,
            window,
            DefaultGC(mat_x11Display, screenID),
            gw->image,
            0, 0, // src x, y
            0, 0, // dest x, y
            width, height,
            False);
        XSync(mat_x11Display, False);
    }
#endif

    if (!use_xshm) {
        if (gw->image != NULL && (gw->width != width || gw->height != height)) {
            // Free up the old image
            DEBUG_PRINTF("RESIZE of image needed\n");
            XDestroyImage (gw->image);
            gw->image = NULL;
        }
        XImage *img = XCreateImage (mat_x11Display,
                visual,
                // depth of our image, 24 means ignore the Alpha
                (pixmapDepth == 16) ? 16 : 24, 
                ZPixmap,
                0,              // offset
                dataPtr,  // data
                width, height,
                (pixmapDepth == 16) ? 16 : 32, //pad
                0);

        // push bits
        XPutImage(
            mat_x11Display,
            window,
            DefaultGC(mat_x11Display, screenID),
            img,
            0,0,    //src x,y
            0,0,    //dest x,y
            width,height);
    }
    XUnlockDisplay(mat_x11Display);

    if (sarr) free(sarr);
    if (iarr) free(iarr);
    GLASS_CHECK_EXCEPTION(env);
}

/*
 * Class:     com_sun_glass_ui_x11_X11Pixels
 * Method:    _attachByte
 * Signature: (JIILjava/nio/ByteBuffer;[BI)V
 */
JNIEXPORT void JNICALL Java_com_sun_glass_ui_x11_X11Pixels__1attachByte
  (JNIEnv *env, jclass jpixel, jlong windowPtr, jint w, jint h, jobject jbytes, jintArray byteArray, jint offset) {
    DEBUG_PRINTF("X11Pixels_attachByte\n");

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


