/*
 * Copyright (c) 2012, 2015, 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 "ImageLoader.h"

#include "jni_utils.h"
#include "com_sun_javafx_iio_ios_IosImageLoader.h"

#include "debug.h"

// For incremental loading see:
//http://developer.apple.com/library/ios/#documentation/GraphicsImaging/Conceptual/ImageIOGuide/imageio_intro/ikpg_intro.html


@implementation ImageLoader


@synthesize buffer;
@synthesize width;
@synthesize height;
@synthesize nComponents;
@synthesize colorSpace;
@synthesize nImages;
@synthesize delayTime;
@synthesize loopCount;
@synthesize cgImageSource;
@synthesize cgImage;

-(id) init {
    self = [super init];
    if (self) {
        [self setBuffer : [NSMutableData data]];
    }
    return self;
}

-(void) dealloc {
    IIOLog(@"ImageLoader::dealloc");
    CFRelease(cgImageSource);
    [buffer release];
    [super dealloc];
}

-(void) addToBuffer : (const void *) bytes
             length : (int) length {
    [buffer appendBytes : bytes
                 length : length];
}

-(BOOL) hasAlpha : (CGImageAlphaInfo) aInfo {
    return aInfo != kCGImageAlphaNone;
}

-(BOOL) isAlphaPremultiplied : (CGImageAlphaInfo) aInfo {
    return aInfo == kCGImageAlphaPremultipliedLast ||
    aInfo == kCGImageAlphaPremultipliedFirst;
}

-(int) resolveJavaColorSpace : (CGColorSpaceModel) nativeModel
                   alphaInfo : (CGImageAlphaInfo) aInfo {

    int jColorSpace = -1;

    if (nativeModel == kCGColorSpaceModelMonochrome) {
        if ([self hasAlpha : aInfo]) {
            if ([self isAlphaPremultiplied : aInfo]) {
                jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_GRAY_ALPHA_PRE;
            } else {
                jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_GRAY_ALPHA;
            }
        } else {
            jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_GRAY;
        }
    } else if (nativeModel == kCGColorSpaceModelIndexed) {
        if ([self hasAlpha : aInfo]) {
            if ([self isAlphaPremultiplied : aInfo]) {
                jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_PALETTE_ALPHA_PRE;
            } else {
                jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_PALETTE_ALPHA;
            }
        } else {
            jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_PALETTE;
        }
        // NOTE: what about com_sun_javafx_iio_ios_IosImageLoader_PALETTE_TRANS ???
    } else if (nativeModel == kCGColorSpaceModelRGB) {
        if ([self hasAlpha : aInfo]) {
            if ([self isAlphaPremultiplied : aInfo]) {
                jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_RGBA_PRE;
            } else {
                jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_RGBA;
            }
        } else {
            jColorSpace = com_sun_javafx_iio_ios_IosImageLoader_RGB;
        }
    }

    /*

     NOTE: what about the rest of color space models? The Java enum ImageType has no equivalents!

     kCGColorSpaceModelUnknown,
     kCGColorSpaceModelCMYK,
     kCGColorSpaceModelLab,
     kCGColorSpaceModelDeviceN,
     kCGColorSpaceModelPattern

     */

    return jColorSpace;
}

-(void) reportImageProperties {
    IIOLog(@"Image size:                 [ %d x %d ]", [self width], [self height]);
    IIOLog(@"Image color space:          %d", [self colorSpace]);
    IIOLog(@"Image number of components: %d", [self nComponents]);
    IIOLog(@"Image number of images:     %d", [self nImages]);
    IIOLog(@"Image loop count:           %d", [self loopCount]);
    IIOLog(@"Image duration:             %d", [self delayTime]);
}

-(void) retrieveDelayTime {

    NSDictionary *dict = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(cgImageSource, 0, NULL);
    NSDictionary *gifDict = (NSDictionary *) [dict objectForKey : (id) kCGImagePropertyGIFDictionary];

    int delay = 100; // 100ms default if no time interval is retrieved
    int nLoopCount = 1;
    if (gifDict) {
        NSNumber *delayValue = [gifDict objectForKey : (id) kCGImagePropertyGIFDelayTime];
        delay = (int) ([delayValue doubleValue] * 1000);
        NSNumber *loopCountValue = [gifDict objectForKey : (id) kCGImagePropertyGIFLoopCount];
        nLoopCount = [loopCountValue intValue];
    }

    [self setDelayTime : delay];
    [self setLoopCount : nLoopCount];
}

-(CGImageRef) createImageAtIndex : (int) index {

    CGImageRef cgImageRef = CGImageSourceCreateImageAtIndex(cgImageSource, index, NULL);
    return cgImageRef;
}

-(BOOL) loadFromSource : (CGImageSourceRef) sourceRef
                JNIEnv : (JNIEnv *) env {

    [self setCgImageSource : sourceRef];

    size_t imageCount = CGImageSourceGetCount(sourceRef);
    [self setNImages : (int) imageCount];

    if (imageCount > 1) {
        [self retrieveDelayTime];
    }

    BOOL success = FALSE;

    CGImageRef firstImage = [self createImageAtIndex : 0];

    if (firstImage) {
        [self setCgImage : firstImage];
        [self setWidth : (int) CGImageGetWidth(firstImage)];
        [self setHeight : (int) CGImageGetHeight(firstImage)];

        CGColorSpaceRef cgColorSpace = CGImageGetColorSpace(firstImage);
        CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(firstImage);

        int nComp = (int) CGColorSpaceGetNumberOfComponents(cgColorSpace);
        if ([self hasAlpha : alphaInfo]) {
            nComp++;
        }

        [self setNComponents : nComp];

        CGColorSpaceModel spaceModel = CGColorSpaceGetModel(cgColorSpace);
        [self setColorSpace :
         [self resolveJavaColorSpace : spaceModel
                           alphaInfo : alphaInfo]];

        [self reportImageProperties];

        success = TRUE;
    } else {
        // NOTE: see what went wrong (RT-27439)
        //CGImageSourceStatus status = CGImageSourceGetStatus(sourceRef);
        /*enum CGImageSourceStatus {
         kCGImageStatusUnexpectedEOF = -5,
         kCGImageStatusInvalidData = -4,
         kCGImageStatusUnknownType = -3,
         kCGImageStatusReadingHeader = -2,
         kCGImageStatusIncomplete = -1,
         kCGImageStatusComplete = 0
         };*/

        throwException(env, JAVA_IO_IOEXCEPTION, "Unable to decode image");
    }

    return success;
}

-(BOOL) loadFromURL : (NSString *) urlString
             JNIEnv : (JNIEnv *) env {

    NSURL *url = [NSURL URLWithString : urlString];

    BOOL success = FALSE;

    if ([url isFileURL]) {
        NSError *error;
        BOOL isReachable = [url checkResourceIsReachableAndReturnError : &error];

        if (!isReachable) {
            NSString *nsErrorMessage = [NSString stringWithFormat : @"%@ (%@), Recovery suggestion: %@",
                                        [error localizedFailureReason],
                                        [error localizedDescription],
                                        [error localizedRecoverySuggestion]];

            const char *errorMessage = [nsErrorMessage UTF8String];
            throwException(env, JAVA_IO_IOEXCEPTION, errorMessage);
            return FALSE;
        }
    }

    CGImageSourceRef sourceRef = CGImageSourceCreateWithURL((CFURLRef) url, NULL);
    if (sourceRef == NULL) {
        throwException(env, JAVA_IO_IOEXCEPTION, "Failed to create CGImageSource");
    } else {
        success = [self loadFromSource : sourceRef
                                JNIEnv : env];
    }

    return success;
}

-(BOOL) loadFromBuffer : (JNIEnv *) env {

    BOOL success = FALSE;

    CGImageSourceRef sourceRef = CGImageSourceCreateWithData((CFTypeRef) buffer, NULL);

    if (sourceRef == NULL) {
        throwException(env, JAVA_IO_IOEXCEPTION, "Failed to create CGImageSource");
    } else {
        success = [self loadFromSource : sourceRef
                                JNIEnv : env];
    }

    return success;
}

-(BOOL) hasLessThan8BitsPerChannel {
    return CGImageGetBitsPerPixel([self cgImage]) < 24;
}

/*

 In order to resize an image it is necessary to create a new bitmap context. However, not all
 color spaces are supported for new bitmap contexts. Therefore all non-alpha images are
 converted to

 - kCGImageAlphaNoneSkipLast

 and all images with an alpha component are converted to

 - kCGImageAlphaPremultipliedLast

 (Not premultiplied RGB is not supported)

 For the list of all supported pixel formats, see

 https://developer.apple.com/library/ios/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html

 (Supported Pixel Formats section)

 */

-(void) resize : (int) newWidth : (int) newHeight {

    // in case a different size is requested or the original color model uses less than 8 bits per component, create a new bitmap context
    if ([self width] != newWidth ||
        [self height] != newHeight ||
        [self hasLessThan8BitsPerChannel]) {

        const CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage);

        const CGBitmapInfo bitmapInfo = [self hasAlpha : alphaInfo] ?
        kCGImageAlphaPremultipliedLast : kCGImageAlphaNoneSkipLast;

        const int nComp = 4;
        CGColorSpaceRef cgColorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef cgContext = CGBitmapContextCreate(
                                                       NULL,
                                                       newWidth,
                                                       newHeight,
                                                       8,
                                                       nComp * newWidth,
                                                       cgColorSpace,
                                                       bitmapInfo);

        CGContextDrawImage(cgContext,
                           CGRectMake(0.0f, 0.0f,
                                      (CGFloat) newWidth,
                                      (CGFloat) newHeight),
                           cgImage);

        CGImageRef cgImageNew = CGBitmapContextCreateImage(cgContext);

        [self setCgImage : cgImageNew];
        [self setNComponents : nComp];
        [self setColorSpace : [self resolveJavaColorSpace : CGColorSpaceGetModel(cgColorSpace)
                                                alphaInfo : bitmapInfo]];

        CGContextRelease(cgContext);

        IIOLog(@"Image was resized to %dx%d", newWidth, newHeight);
    }
}

-(CFDataRef) copyImagePixels : (CGImageRef) image {
    return CGDataProviderCopyData(CGImageGetDataProvider(image));
}

-(jbyteArray) getDecompressedBuffer : (JNIEnv *) env
                         imageIndex : (int) imageIndex {

    jboolean iscopy = FALSE;

    int bufferSize = [self width] * [self height] * [self nComponents];
    jbyteArray outBuffer = (*env)->NewByteArray(env, bufferSize);
    if (outBuffer == NULL) {
        throwException(env,
                       JAVA_OOM_ERROR,
                       "Cannot initilialize memory buffer for decoded image data");
    } else {
        jbyte *jByteBuffer = (*env)->GetPrimitiveArrayCritical(env,
                                                               outBuffer,
                                                               &iscopy);

        CFDataRef cfDataRef = [self copyImagePixels : cgImage];
        size_t dataLength = (size_t) CFDataGetLength(cfDataRef);
        memcpy(jByteBuffer, (UInt8 *) CFDataGetBytePtr(cfDataRef), dataLength);
        CFRelease(cfDataRef);

        (*env)->ReleasePrimitiveArrayCritical(env,
                                              outBuffer,
                                              jByteBuffer,
                                              JNI_ABORT);

        CGImageRelease(cgImage);

        // prepare the next image of an animated image
        if (imageIndex + 1 < nImages) {
            CGImageRef image = [self createImageAtIndex : imageIndex];
            [self setCgImage : image];
        }

        IIOLog(@"ImageLoader: sent %lu data to Java", dataLength);
    }

    return outBuffer;
}

@end
