/*
 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#import <JavaNativeFoundation/JavaNativeFoundation.h>
#import <JavaRuntimeSupport/JavaRuntimeSupport.h>

#import "apple_laf_JRSUIControl.h"
#import "apple_laf_JRSUIConstants_DoubleValue.h"
#import "apple_laf_JRSUIConstants_Hit.h"

#import "JRSUIConstantSync.h"


static JRSUIRendererRef gRenderer;

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    initNativeJRSUI
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_apple_laf_JRSUIControl_initNativeJRSUI
(JNIEnv *env, jclass clazz)
{
    BOOL coherent = _InitializeJRSProperties();
    if (!coherent) return apple_laf_JRSUIControl_INCOHERENT;

    gRenderer = JRSUIRendererCreate();
    if (gRenderer == NULL) return apple_laf_JRSUIControl_NULL_PTR;

    return apple_laf_JRSUIControl_SUCCESS;
}

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    getPtrOfBuffer
 * Signature: (Ljava/nio/ByteBuffer;)J
 */
JNIEXPORT jlong JNICALL Java_apple_laf_JRSUIControl_getPtrOfBuffer
(JNIEnv *env, jclass clazz, jobject byteBuffer)
{
    char *byteBufferPtr = (*env)->GetDirectBufferAddress(env, byteBuffer);
    if (byteBufferPtr == NULL) return 0L;
    return ptr_to_jlong(byteBufferPtr); // GC
}

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    getCFDictionary
 * Signature: (Z)J
 */
JNIEXPORT jlong JNICALL Java_apple_laf_JRSUIControl_getCFDictionary
(JNIEnv *env, jclass clazz, jboolean isFlipped)
{
    return ptr_to_jlong(JRSUIControlCreate(isFlipped));
}

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    disposeCFDictionary
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_apple_laf_JRSUIControl_disposeCFDictionary
(JNIEnv *env, jclass clazz, jlong controlPtr)
{
    void *ptr = jlong_to_ptr(controlPtr);
    if (!ptr) return;
    JRSUIControlRelease((JRSUIControlRef)ptr);
}


static inline void *getValueFor
(jbyte code, UInt8 *changeBuffer, size_t *dataSizePtr)
{
    switch (code)
    {
        case apple_laf_JRSUIConstants_DoubleValue_TYPE_CODE:
            *dataSizePtr = sizeof(jdouble);
            jdouble doubleValue = (*(jdouble *)changeBuffer);
            return (void *)CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &doubleValue);
    }

    return NULL;
}

static inline jint syncChangesToControl
(JRSUIControlRef control, UInt8 *changeBuffer)
{
    UInt8 *endOfBuffer = changeBuffer + apple_laf_JRSUIControl_NIO_BUFFER_SIZE;

    while (changeBuffer < endOfBuffer)
    {
        // dereference the pointer to the constant that was stored as a jlong in the byte buffer
        CFStringRef key = (CFStringRef)jlong_to_ptr(*((jlong *)changeBuffer));
        if (key == NULL) return apple_laf_JRSUIControl_SUCCESS;
        changeBuffer += sizeof(jlong);

        jbyte code = *((jbyte *)changeBuffer);
        changeBuffer += sizeof(jbyte);

        size_t dataSize;
        void *value = (void *)getValueFor(code, changeBuffer, &dataSize);
        if (value == NULL) {
            NSLog(@"null pointer for %@ for value %d", key, (int)code);

            return apple_laf_JRSUIControl_NULL_PTR;
        }

        changeBuffer += dataSize;
        JRSUIControlSetValueByKey(control, key, value);
        CFRelease(value);
    }

    return apple_laf_JRSUIControl_SUCCESS;
}

static inline jint doSyncChanges
(JNIEnv *env, jlong controlPtr, jlong byteBufferPtr)
{
    JRSUIControlRef control = (JRSUIControlRef)jlong_to_ptr(controlPtr);
    UInt8 *changeBuffer = (UInt8 *)jlong_to_ptr(byteBufferPtr);

    return syncChangesToControl(control, changeBuffer);
}

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    syncChanges
 * Signature: (JJ)I
 */
JNIEXPORT jint JNICALL Java_apple_laf_JRSUIControl_syncChanges
(JNIEnv *env, jclass clazz, jlong controlPtr, jlong byteBufferPtr)
{
    return doSyncChanges(env, controlPtr, byteBufferPtr);
}

static inline jint doPaintCGContext(CGContextRef cgRef, jlong controlPtr, jlong oldProperties, jlong newProperties, jdouble x, jdouble y, jdouble w, jdouble h)
{
    JRSUIControlRef control = (JRSUIControlRef)jlong_to_ptr(controlPtr);
    _SyncEncodedProperties(control, oldProperties, newProperties);
    CGRect bounds = CGRectMake(x, y, w, h);
    JRSUIControlDraw(gRenderer, control, cgRef, bounds);
    return 0;
}

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    paintToCGContext
 * Signature: (JJJJDDDD)I
 */
JNIEXPORT jint JNICALL Java_apple_laf_JRSUIControl_paintToCGContext
(JNIEnv *env, jclass clazz, jlong cgContextPtr, jlong controlPtr, jlong oldProperties, jlong newProperties, jdouble x, jdouble y, jdouble w, jdouble h)
{
    return doPaintCGContext((CGContextRef)jlong_to_ptr(cgContextPtr), controlPtr, oldProperties, newProperties, x, y, w, h);
}

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    paintChangesToCGContext
 * Signature: (JJJJDDDDJ)I
 */
JNIEXPORT jint JNICALL Java_apple_laf_JRSUIControl_paintChangesToCGContext
(JNIEnv *env, jclass clazz, jlong cgContextPtr, jlong controlPtr, jlong oldProperties, jlong newProperties, jdouble x, jdouble y, jdouble w, jdouble h, jlong changes)
{
    int syncStatus = doSyncChanges(env, controlPtr, changes);
    if (syncStatus != apple_laf_JRSUIControl_SUCCESS) return syncStatus;

    return doPaintCGContext((CGContextRef)jlong_to_ptr(cgContextPtr), controlPtr, oldProperties, newProperties, x, y, w, h);
}

static inline jint doPaintImage
(JNIEnv *env, jlong controlPtr, jlong oldProperties, jlong newProperties, jintArray data, jint imgW, jint imgH, jdouble x, jdouble y, jdouble w, jdouble h)
{
    jboolean isCopy = JNI_FALSE;
    void *rawPixelData = (*env)->GetPrimitiveArrayCritical(env, data, &isCopy);
    if (!rawPixelData) return apple_laf_JRSUIControl_NULL_PTR;

    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGContextRef cgRef = CGBitmapContextCreate(rawPixelData, imgW, imgH, 8, imgW * 4, colorspace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
    CGColorSpaceRelease(colorspace);
    CGContextScaleCTM(cgRef, imgW/(w + x + x) , imgH/(h + y + y));

    jint status = doPaintCGContext(cgRef, controlPtr, oldProperties, newProperties, x, y, w, h);
    CGContextRelease(cgRef);

    (*env)->ReleasePrimitiveArrayCritical(env, data, rawPixelData, 0);

    return status == noErr ? apple_laf_JRSUIControl_SUCCESS : status;
}

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    paintImage
 * Signature: ([IIIJJJDDDD)I
 */
JNIEXPORT jint JNICALL Java_apple_laf_JRSUIControl_paintImage
(JNIEnv *env, jclass clazz, jintArray data, jint imgW, jint imgH, jlong controlPtr, jlong oldProperties, jlong newProperties, jdouble x, jdouble y, jdouble w, jdouble h)
{
    return doPaintImage(env, controlPtr, oldProperties, newProperties, data, imgW, imgH, x, y, w, h);
}

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    paintChangesImage
 * Signature: ([IIIJJJDDDDJ)I
 */
JNIEXPORT jint JNICALL Java_apple_laf_JRSUIControl_paintChangesImage
(JNIEnv *env, jclass clazz, jintArray data, jint imgW, jint imgH, jlong controlPtr, jlong oldProperties, jlong newProperties, jdouble x, jdouble y, jdouble w, jdouble h, jlong changes)
{
    int syncStatus = doSyncChanges(env, controlPtr, changes);
    if (syncStatus != apple_laf_JRSUIControl_SUCCESS) return syncStatus;

    return doPaintImage(env, controlPtr, oldProperties, newProperties, data, imgW, imgH, x, y, w, h);
}

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    getNativeHitPart
 * Signature: (JJJDDDDDD)I
 */
JNIEXPORT jint JNICALL Java_apple_laf_JRSUIControl_getNativeHitPart
(JNIEnv *env, jclass clazz, jlong controlPtr, jlong oldProperties, jlong newProperties, jdouble x, jdouble y, jdouble w, jdouble h, jdouble pointX, jdouble pointY)
{
    JRSUIControlRef control = (JRSUIControlRef)jlong_to_ptr(controlPtr);
    _SyncEncodedProperties(control, oldProperties, newProperties);

    CGRect bounds = CGRectMake(x, y, w, h);
    CGPoint point = CGPointMake(pointX, pointY);

    return JRSUIControlGetHitPart(gRenderer, control, bounds, point);
}

/*
 * Class:     apple_laf_JRSUIUtils_ScrollBar
 * Method:    shouldUseScrollToClick
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_apple_laf_JRSUIUtils_00024ScrollBar_shouldUseScrollToClick
(JNIEnv *env, jclass clazz)
{
    return JRSUIControlShouldScrollToClick();
}

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    getNativePartBounds
 * Signature: ([DJJJDDDDI)V
 */
JNIEXPORT void JNICALL Java_apple_laf_JRSUIControl_getNativePartBounds
(JNIEnv *env, jclass clazz, jdoubleArray rectArray, jlong controlPtr, jlong oldProperties, jlong newProperties, jdouble x, jdouble y, jdouble w, jdouble h, jint part)
{
    JRSUIControlRef control = (JRSUIControlRef)jlong_to_ptr(controlPtr);
    _SyncEncodedProperties(control, oldProperties, newProperties);

    CGRect frame = CGRectMake(x, y, w, h);
    CGRect partBounds = JRSUIControlGetScrollBarPartBounds(control, frame, part);

    jdouble *rect = (*env)->GetPrimitiveArrayCritical(env, rectArray, NULL);
    rect[0] = partBounds.origin.x;
    rect[1] = partBounds.origin.y;
    rect[2] = partBounds.size.width;
    rect[3] = partBounds.size.height;
    (*env)->ReleasePrimitiveArrayCritical(env, rectArray, rect, 0);
}

/*
 * Class:     apple_laf_JRSUIControl
 * Method:    getNativeScrollBarOffsetChange
 * Signature: (JJJDDDDIII)D
 */
JNIEXPORT jdouble JNICALL Java_apple_laf_JRSUIControl_getNativeScrollBarOffsetChange
(JNIEnv *env, jclass clazz, jlong controlPtr, jlong oldProperties, jlong newProperties, jdouble x, jdouble y, jdouble w, jdouble h, jint offset, jint visibleAmount, jint extent)
{
    JRSUIControlRef control = (JRSUIControlRef)jlong_to_ptr(controlPtr);
    _SyncEncodedProperties(control, oldProperties, newProperties);

    CGRect frame = CGRectMake(x, y, w, h);
    return (jdouble)JRSUIControlGetScrollBarOffsetFor(control, frame, offset, visibleAmount, extent);
}
