/*
 * 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 "jni_util.h"

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

#import "GeomUtilities.h"
#import "ThreadUtilities.h"

#import "sun_lwawt_macosx_CImage.h"


static void CImage_CopyArrayIntoNSImageRep
(jint *srcPixels, jint *dstPixels, int width, int height)
{
    int x, y;
    // TODO: test this on big endian systems (not sure if its correct)...
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            jint pix = srcPixels[x];
            jint a = (pix >> 24) & 0xff;
            jint r = (pix >> 16) & 0xff;
            jint g = (pix >>  8) & 0xff;
            jint b = (pix      ) & 0xff;
            dstPixels[x] = (b << 24) | (g << 16) | (r << 8) | a;
        }
        srcPixels += width; // TODO: use explicit scanStride
        dstPixels += width;
    }
}

static void CImage_CopyNSImageIntoArray
(NSImage *srcImage, jint *dstPixels, NSRect fromRect, NSRect toRect)
{
    int width = toRect.size.width;
    int height = toRect.size.height;
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGContextRef cgRef = CGBitmapContextCreate(dstPixels, width, height,
                                8, width * 4, colorspace,
                                kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
    CGColorSpaceRelease(colorspace);
    NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:cgRef flipped:NO];
    CGContextRelease(cgRef);
    NSGraphicsContext *oldContext = [[NSGraphicsContext currentContext] retain];
    [NSGraphicsContext setCurrentContext:context];
    [srcImage drawInRect:toRect
                fromRect:fromRect
               operation:NSCompositeSourceOver
                fraction:1.0];
    [NSGraphicsContext setCurrentContext:oldContext];
    [oldContext release];
}

static NSBitmapImageRep* CImage_CreateImageRep(JNIEnv *env, jintArray buffer, jint width, jint height)
{
    NSBitmapImageRep* imageRep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
                                                                          pixelsWide:width
                                                                          pixelsHigh:height
                                                                       bitsPerSample:8
                                                                     samplesPerPixel:4
                                                                            hasAlpha:YES
                                                                            isPlanar:NO
                                                                      colorSpaceName:NSDeviceRGBColorSpace
                                                                        bitmapFormat:NSAlphaFirstBitmapFormat
                                                                         bytesPerRow:width*4 // TODO: use explicit scanStride
                                                                        bitsPerPixel:32] autorelease];

    jint *imgData = (jint *)[imageRep bitmapData];
    if (imgData == NULL) return 0L;

    jint *src = (*env)->GetPrimitiveArrayCritical(env, buffer, NULL);
    if (src == NULL) return 0L;

    CImage_CopyArrayIntoNSImageRep(src, imgData, width, height);

    (*env)->ReleasePrimitiveArrayCritical(env, buffer, src, JNI_ABORT);

    return imageRep;
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeCreateNSImageFromArray
 * Signature: ([III)J
 */
JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CImage_nativeCreateNSImageFromArray
(JNIEnv *env, jclass klass, jintArray buffer, jint width, jint height)
{
    jlong result = 0L;

JNF_COCOA_ENTER(env);
    
    NSBitmapImageRep* imageRep = CImage_CreateImageRep(env, buffer, width, height);
    if (imageRep) {
        NSImage *nsImage = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
        [nsImage addRepresentation:imageRep];
        result = ptr_to_jlong(nsImage);
    }

JNF_COCOA_EXIT(env);

    return result;
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeCreateNSImageFromArrays
 * Signature: ([[I[I[I)J
 */
JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CImage_nativeCreateNSImageFromArrays
(JNIEnv *env, jclass klass, jobjectArray buffers, jintArray widths, jintArray heights)
{
    jlong result = 0L;

JNF_COCOA_ENTER(env);

    jsize num = (*env)->GetArrayLength(env, buffers);
    NSMutableArray * reps = [NSMutableArray arrayWithCapacity: num];

    jint * ws = (*env)->GetIntArrayElements(env, widths, NULL);
    if (ws != NULL) {
        jint * hs = (*env)->GetIntArrayElements(env, heights, NULL);
        if (hs != NULL) {
            jsize i;
            for (i = 0; i < num; i++) {
                jintArray buffer = (*env)->GetObjectArrayElement(env, buffers, i);

                NSBitmapImageRep* imageRep = CImage_CreateImageRep(env, buffer, ws[i], hs[i]);
                if (imageRep) {
                    [reps addObject: imageRep];
                }
            }

            (*env)->ReleaseIntArrayElements(env, heights, hs, JNI_ABORT);
        }
        (*env)->ReleaseIntArrayElements(env, widths, ws, JNI_ABORT);
    }
    if ([reps count]) {
        NSImage *nsImage = [[NSImage alloc] initWithSize:NSMakeSize(0, 0)];
        [nsImage addRepresentations: reps];
        result = ptr_to_jlong(nsImage);
    }

JNF_COCOA_EXIT(env);

    return result;
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeCreateNSImageFromIconSelector
 * Signature: (I)J
 */
JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CImage_nativeCreateNSImageFromIconSelector
(JNIEnv *env, jclass klass, jint selector)
{
    NSImage *image = nil;

JNF_COCOA_ENTER(env);

    IconRef iconRef;
    if (noErr == GetIconRef(kOnSystemDisk, kSystemIconsCreator, selector, &iconRef)) {
        image = [[NSImage alloc] initWithIconRef:iconRef];
        ReleaseIconRef(iconRef);
    }

JNF_COCOA_EXIT(env);

    return ptr_to_jlong(image);
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeCreateNSImageFromFileContents
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CImage_nativeCreateNSImageFromFileContents
(JNIEnv *env, jclass klass, jstring file)
{
    NSImage *image = nil;

JNF_COCOA_ENTER(env);

    NSString *path = JNFNormalizedNSStringForPath(env, file);
    image = [[NSImage alloc] initByReferencingFile:path];

JNF_COCOA_EXIT(env);

    return ptr_to_jlong(image);
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeCreateNSImageOfFileFromLaunchServices
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CImage_nativeCreateNSImageOfFileFromLaunchServices
(JNIEnv *env, jclass klass, jstring file)
{
    __block NSImage *image = nil;

JNF_COCOA_ENTER(env);

    NSString *path = JNFNormalizedNSStringForPath(env, file);
    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
        image = [[[NSWorkspace sharedWorkspace] iconForFile:path] retain];
        [image setScalesWhenResized:TRUE];
    }];

JNF_COCOA_EXIT(env);

    return ptr_to_jlong(image);
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeCreateNSImageFromImageName
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CImage_nativeCreateNSImageFromImageName
(JNIEnv *env, jclass klass, jstring name)
{
    NSImage *image = nil;

JNF_COCOA_ENTER(env);

    image = [[NSImage imageNamed:JNFJavaToNSString(env, name)] retain];

JNF_COCOA_EXIT(env);

    return ptr_to_jlong(image);
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeCopyNSImageIntoArray
 * Signature: (J[IIIII)V
 */
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CImage_nativeCopyNSImageIntoArray
(JNIEnv *env, jclass klass, jlong nsImgPtr, jintArray buffer, jint sw, jint sh,
                 jint dw, jint dh)
{
JNF_COCOA_ENTER(env);

    NSImage *img = (NSImage *)jlong_to_ptr(nsImgPtr);
    jint *dst = (*env)->GetPrimitiveArrayCritical(env, buffer, NULL);
    if (dst) {
        NSRect fromRect = NSMakeRect(0, 0, sw, sh);
        NSRect toRect = NSMakeRect(0, 0, dw, dh);
        CImage_CopyNSImageIntoArray(img, dst, fromRect, toRect);
        (*env)->ReleasePrimitiveArrayCritical(env, buffer, dst, JNI_ABORT);
    }

JNF_COCOA_EXIT(env);
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeGetNSImageSize
 * Signature: (J)Ljava/awt/geom/Dimension2D;
 */
JNIEXPORT jobject JNICALL Java_sun_lwawt_macosx_CImage_nativeGetNSImageSize
(JNIEnv *env, jclass klass, jlong nsImgPtr)
{
    jobject size = NULL;

JNF_COCOA_ENTER(env);

    size = NSToJavaSize(env, [(NSImage *)jlong_to_ptr(nsImgPtr) size]);

JNF_COCOA_EXIT(env);

    return size;
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeSetNSImageSize
 * Signature: (JDD)V
 */
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CImage_nativeSetNSImageSize
(JNIEnv *env, jclass clazz, jlong image, jdouble w, jdouble h)
{
    if (!image) return;
    NSImage *i = (NSImage *)jlong_to_ptr(image);

JNF_COCOA_ENTER(env);

    [i setScalesWhenResized:TRUE];
    [i setSize:NSMakeSize(w, h)];

JNF_COCOA_EXIT(env);
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeResizeNSImageRepresentations
 * Signature: (JDD)V
 */
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CImage_nativeResizeNSImageRepresentations
(JNIEnv *env, jclass clazz, jlong image, jdouble w, jdouble h)
{
    if (!image) return;
    NSImage *i = (NSImage *)jlong_to_ptr(image);
    
JNF_COCOA_ENTER(env);
    
    NSImageRep *imageRep = nil;
    NSArray *imageRepresentations = [i representations];
    NSEnumerator *imageEnumerator = [imageRepresentations objectEnumerator];
    while ((imageRep = [imageEnumerator nextObject]) != nil) {
        [imageRep setSize:NSMakeSize(w, h)];
    }
    
JNF_COCOA_EXIT(env);
}

NSComparisonResult getOrder(BOOL order){
    return (NSComparisonResult) (order ? NSOrderedAscending : NSOrderedDescending);
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeGetNSImageRepresentationsCount
 * Signature: (JDD)[Ljava/awt/geom/Dimension2D;
 */
JNIEXPORT jobjectArray JNICALL
                  Java_sun_lwawt_macosx_CImage_nativeGetNSImageRepresentationSizes
(JNIEnv *env, jclass clazz, jlong image, jdouble w, jdouble h)
{
    if (!image) return NULL;
    jobjectArray jreturnArray = NULL;
    NSImage *img = (NSImage *)jlong_to_ptr(image);

JNF_COCOA_ENTER(env);
        
    NSArray *imageRepresentations = [img representations];
    if([imageRepresentations count] == 0){
        return NULL;
    }
    
    NSArray *sortedImageRepresentations = [imageRepresentations
                    sortedArrayUsingComparator: ^(id obj1, id obj2) {
        
        NSImageRep *imageRep1 = (NSImageRep *) obj1;
        NSImageRep *imageRep2 = (NSImageRep *) obj2;
        NSSize size1 = [imageRep1 size];
        NSSize size2 = [imageRep2 size];
        
        if (NSEqualSizes(size1, size2)) {
            return getOrder([imageRep1 pixelsWide] <= [imageRep2 pixelsWide] &&
                            [imageRep1 pixelsHigh] <= [imageRep2 pixelsHigh]);
        }

        return getOrder(size1.width <= size2.width && size1.height <= size2.height);
    }];

    NSMutableArray *sortedPixelSizes = [[[NSMutableArray alloc] init] autorelease];
    NSSize lastSize = [[sortedImageRepresentations lastObject] size];
    
    NSUInteger i = [sortedImageRepresentations indexOfObjectPassingTest:
               ^BOOL(id obj, NSUInteger idx, BOOL *stop) {
        NSSize imageRepSize = [obj size];
        return (w <= imageRepSize.width && h <= imageRepSize.height)
                   || NSEqualSizes(imageRepSize, lastSize);
    }];

    NSUInteger count = [sortedImageRepresentations count];
    i = (i == NSNotFound) ? count - 1 : i;
    NSSize bestFitSize = [[sortedImageRepresentations objectAtIndex: i] size];

    for(; i < count; i++){
        NSImageRep *imageRep = [sortedImageRepresentations objectAtIndex: i];

        if (!NSEqualSizes([imageRep size], bestFitSize)) {
            break;
        }

        NSSize pixelSize = NSMakeSize(
                                [imageRep pixelsWide], [imageRep pixelsHigh]);
        [sortedPixelSizes addObject: [NSValue valueWithSize: pixelSize]];
    }

    count = [sortedPixelSizes count];
    static JNF_CLASS_CACHE(jc_Dimension, "java/awt/Dimension");
    jreturnArray = JNFNewObjectArray(env, &jc_Dimension, count);
    CHECK_NULL_RETURN(jreturnArray, NULL);

    for(i = 0; i < count; i++){
        NSSize pixelSize = [[sortedPixelSizes objectAtIndex: i] sizeValue];

        (*env)->SetObjectArrayElement(env, jreturnArray, i,
                                      NSToJavaSize(env, pixelSize));
        JNU_CHECK_EXCEPTION_RETURN(env, NULL);
    }

JNF_COCOA_EXIT(env);

    return jreturnArray;
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeGetPlatformImageBytes
 * Signature: ([III)[B
 */
JNIEXPORT jbyteArray JNICALL Java_sun_lwawt_macosx_CImage_nativeGetPlatformImageBytes
(JNIEnv *env, jclass klass, jintArray buffer, jint width, jint height)
{
    jbyteArray result = 0L;

    JNF_COCOA_ENTER(env);

    NSBitmapImageRep* imageRep = CImage_CreateImageRep(env, buffer, width, height);
    if (imageRep) {
        NSData *tiffImage = [imageRep TIFFRepresentation];
        jsize tiffSize = (jsize)[tiffImage length];
        result = (*env)->NewByteArray(env, tiffSize);
        CHECK_NULL_RETURN(result, nil);
        jbyte *tiffData = (jbyte *)(*env)->GetPrimitiveArrayCritical(env, result, 0);
        CHECK_NULL_RETURN(tiffData, nil);
        [tiffImage getBytes:tiffData];
        (*env)->ReleasePrimitiveArrayCritical(env, result, tiffData, 0);
    }

    JNF_COCOA_EXIT(env);

    return result;
}

/*
 * Class:     sun_lwawt_macosx_CImage
 * Method:    nativeCreateNSImageFromBytes
 * Signature: ([B)J
 */
JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CImage_nativeCreateNSImageFromBytes
(JNIEnv *env, jclass klass, jbyteArray sourceData)
{
    jlong result = 0L;
    CHECK_NULL_RETURN(sourceData, 0L);

    JNF_COCOA_ENTER(env);

    jsize sourceSize = (*env)->GetArrayLength(env, sourceData);
    if (sourceSize == 0) return 0L;

    jbyte *sourceBytes = (*env)->GetPrimitiveArrayCritical(env, sourceData, NULL);
    CHECK_NULL_RETURN(sourceBytes, 0L);
    NSData *rawData = [NSData dataWithBytes:sourceBytes length:sourceSize];
    NSImage *newImage = [[NSImage alloc] initWithData:rawData];

    (*env)->ReleasePrimitiveArrayCritical(env, sourceData, sourceBytes, JNI_ABORT);
    CHECK_NULL_RETURN(newImage, 0L);

    result = ptr_to_jlong(newImage);
    JNF_COCOA_EXIT(env);

    return result;
}
