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

// http://developer.apple.com/documentation/graphicsimaging/reference/CGContext/Reference/reference.html
// http://developer.apple.com/DOCUMENTATION/GraphicsImaging/Reference/CGPath/Reference/reference.html

#import "Graphics2D.h"

#import "common.h"
#import "com_sun_rain_graphics_Graphics2D.h"
#import "com_sun_rain_graphics_Image.h"
#import "com_sun_rain_graphics_Color.h"
#import "com_sun_rain_graphics_Font.h"
#import "com_sun_rain_shapes_Shape.h"

static jclass jGraphics2DClass;
static jclass jFontClass;
static jclass jColorClass;
static jclass jByteBufferClass;
static jclass jImageClass;
static jclass jShapeClass;

#define DEGREES_TO_RADIANS(__ANGLE__) ((__ANGLE__) / 180.0f * (float)M_PI)

@implementation Graphics2D

- (CGContextRef)graphicsPort
{
    return [[NSGraphicsContext currentContext] graphicsPort];
}

@end

static inline CTFontRef getMacFont(JNIEnv *env, jobject jfont)
{
    return (CTFontRef)jlong_to_ptr((*env)->GetLongField(env, jfont,
                                                        (*env)->GetFieldID(env, jFontClass, "ptr", "J")));
}

static inline CGColorRef getMacColor(JNIEnv *env, jobject jcolor)
{
    return (CGColorRef)jlong_to_ptr((*env)->GetLongField(env, jcolor,
                                                         (*env)->GetFieldID(env, jColorClass, "ptr", "J")));
}

static inline CGImageRef getMacImage(JNIEnv *env, jobject jimage)
{
    return (CGImageRef)jlong_to_ptr((*env)->GetLongField(env, jimage,
                                                         (*env)->GetFieldID(env, jImageClass, "ptr", "J")));
}

static inline CGMutablePathRef getMacShape(JNIEnv *env, jobject jshape)
{
    return (CGMutablePathRef)jlong_to_ptr((*env)->GetLongField(env, jshape,
                                                               (*env)->GetFieldID(env, jShapeClass, "ptr", "J")));
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _initIDs
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1initIDs
  (JNIEnv *env, jclass jGraphicsClass)
{
    jGraphics2DClass = (*env)->NewGlobalRef(env, jGraphicsClass);
    jFontClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/sun/rain/graphics/Font"));
    jColorClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/sun/rain/graphics/Color"));
    jByteBufferClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/nio/ByteBuffer"));
    jImageClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/sun/rain/graphics/Image"));
    jShapeClass = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "com/sun/rain/shapes/Shape"));
}

/*
 * Class:     com_sun_rain_graphics_Graphics2D
 * Method:    _createGraphics
 * Signature: ()J
 */
JNIEXPORT jlong JNICALL Java_com_sun_rain_graphics_Graphics2D__1createGraphics
(JNIEnv *env, jobject jGraphics)
{
    Graphics2D *graphics = [[Graphics2D alloc] init];
    return ptr_to_jlong(graphics);
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _setColor
 * Signature: (JLcom/sun/rain/graphics/Color;)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1setColor
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr, jobject jcolor)
{
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    graphics->cgColor = getMacColor(env, jcolor);
    CGContextSetFillColorWithColor(cgContext, graphics->cgColor);
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _setFont
 * Signature: (JLcom/sun/rain/graphics/Font;)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1setFont
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr, jobject jfont)
{
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
	
    graphics->ctFont = getMacFont(env, jfont);
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _drawImage
 * Signature: (JLcom/sun/rain/graphics/Image;FFFF)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1drawImage
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr,
     jobject jimage, jfloat x, jfloat y, jfloat width, jfloat height)
{
    //NSLog(@"Java_com_sun_rain_graphics_Graphics2D__1drawImage");
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    // flip the image, but just applying the scale will invert the coordinate system too
    // so move vertically to where we want to draw and set the draw rect origin at x,0
    
    CGContextSaveGState(cgContext);
    CGContextTranslateCTM(cgContext, 0, y+height);
    CGContextScaleCTM(cgContext, 1, -1);
    CGContextDrawImage(cgContext, CGRectMake(x, 0, width, height), getMacImage(env, jimage));
    CGContextRestoreGState(cgContext);
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _drawString
 * Signature: (JLjava/lang/String;FF)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1drawString
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr,
     jstring jString, jfloat x, jfloat y)
{
    //NSLog(@"Java_com_sun_rain_graphics_Graphics2D__1drawString");
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];

	if ((jString != NULL) && (graphics->ctFont != NULL) && (graphics->cgColor != NULL))
	{
		const char* chars = (*env)->GetStringUTFChars(env, jString, 0);
		if (strlen(chars) > 0)
		{
			//NSLog(@"	chars: %s", chars);
			CFStringRef keys[] = { kCTFontAttributeName, kCTForegroundColorAttributeName };
			CFTypeRef values[] = { graphics->ctFont, graphics->cgColor };
			CFDictionaryRef attr =
                            CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values,
                                               sizeof(keys) / sizeof(keys[0]),
                                               &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
			CFStringRef cfString = CFStringCreateWithCString(kCFAllocatorDefault, chars, kCFStringEncodingUTF8);
			CFAttributedStringRef attrString = CFAttributedStringCreate(NULL, cfString, attr);

			CTLineRef line = CTLineCreateWithAttributedString(attrString);
			CGContextSetTextMatrix(cgContext, CGAffineTransformScale(CGAffineTransformIdentity, 1, -1));
			CGContextSetTextPosition(cgContext, x, y);
			CTLineDraw(line, cgContext);

			CFRelease(line);
			CFRelease(attrString);
			CFRelease(cfString);
			CFRelease(attr);
		}
		(*env)->ReleaseStringUTFChars(env, jString, chars);
	}
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _fillRect
 * Signature: (JFFFF)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1fillRect
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr,
     jfloat x, jfloat y, jfloat width, jfloat height)
{
    //NSLog(@"Java_com_sun_rain_graphics_Graphics2D__1fillRect");
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    CGContextFillRect(cgContext, CGRectMake(x, y, width, height));
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _fillEllipse
 * Signature: (JFFFF)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1fillEllipse
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr,
     jfloat x, jfloat y, jfloat width, jfloat height)
{
    //NSLog(@"Java_com_sun_rain_graphics_Graphics2D__1fillEllipse");
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    CGContextFillEllipseInRect(cgContext, CGRectMake(x, y, width, height));
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _fillShape
 * Signature: (JLcom/sun/rain/shapes/Shape;Ljava/nio/ByteBuffer;FF)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1fillShape
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr,
     jobject jshape, jobject jbuffer, jfloat x, jfloat y)
{
    //NSLog(@"Java_com_sun_rain_graphics_Graphics2D__1fillShape");
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    CGMutablePathRef shape = getMacShape(env, jshape);
    if (shape == NULL)
    {
        shape = CGPathCreateMutable();
        
        int length = (*env)->GetDirectBufferCapacity(env, jbuffer);
        int *bufferInts = (*env)->GetDirectBufferAddress(env, jbuffer);
        float *bufferFloats = (*env)->GetDirectBufferAddress(env, jbuffer);
        
        while (length > 0)
        {
            int type = *bufferInts;
            
            bufferInts += 1;
            bufferFloats += 1;
            length -= 4;
            
            switch (type)
            {
                case com_sun_rain_shapes_Shape_MOVE_TO:
                {
                    CGPathMoveToPoint(shape, NULL, bufferFloats[0], bufferFloats[1]);
                    bufferInts += 1;
                    bufferFloats += 1;
                    length -= 4;
                    break;
                }
                case com_sun_rain_shapes_Shape_LINE_TO:
                {
                    CGPathAddLineToPoint(shape, NULL, bufferFloats[0], bufferFloats[1]);
                    bufferInts += 1;
                    bufferFloats += 1;
                    length -= 4;
                    break;
                }
                case com_sun_rain_shapes_Shape_QUAD_TO:
                {
                    CGPathAddQuadCurveToPoint(shape, NULL,
                                              bufferFloats[0], bufferFloats[1],
                                              bufferFloats[2], bufferFloats[3]);
                    bufferInts += 2;
                    bufferFloats += 2;
                    length -= 8;
                    break;
                }
            }
        }
        CGPathCloseSubpath(shape);

        (*env)->SetLongField(env, jshape,
                             (*env)->GetFieldID(env, jShapeClass, "ptr", "J"),
                             ptr_to_jlong(shape));
    }
    
    CGContextSaveGState(cgContext);
    CGContextTranslateCTM(cgContext, x, y + CGPathGetBoundingBox(shape).size.height);
    CGContextScaleCTM(cgContext, 1, -1); // flip the shape vertically or it'll be upside down
    CGContextAddPath(cgContext, shape);
    CGContextFillPath(cgContext);
    CGContextRestoreGState(cgContext);
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _translate
 * Signature: (JFF)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1translate
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr, jfloat x, jfloat y)
{
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    CGContextTranslateCTM(cgContext, x, y);
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _scale
 * Signature: (JFF)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1scale
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr, jfloat x, jfloat y)
{
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    CGContextScaleCTM(cgContext, x, y);
}

/*
 * Class:     com_sun_rain_graphics_Graphics2D
 * Method:    _rotate
 * Signature: (JF)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1rotate
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr, jfloat a)
{
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    // negative angles for CGContextRotateCTM rotates positive X axis towards
    // negative Y axis (positive Y with Glass), so we need to negate the angle
    CGContextRotateCTM(cgContext, DEGREES_TO_RADIANS(-a));
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _pushCTM
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1pushCTM
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr)
{
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    CGContextSaveGState(cgContext);
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _popCTM
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1popCTM
    (JNIEnv *env, jobject jgraphics, jlong jGraphicsPtr)
{
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    CGContextRestoreGState(cgContext);
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _clip
 * Signature: (JFFFF)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1clip
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr,
     jfloat x, jfloat y, jfloat w, jfloat h)
{
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    CGContextAddRect(cgContext, CGRectMake(x, y, w, h));
    CGContextClip(cgContext);
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _pushClip
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1pushClip
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr)
{
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    CGContextSaveGState(cgContext);
}

/*
 * Class:     com_sun_rain_shapes_Graphics2D
 * Method:    _popClip
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1popClip
    (JNIEnv *env, jobject jGraphics, jlong jGraphicsPtr)
{
    Graphics2D *graphics = (Graphics2D*)jlong_to_ptr(jGraphicsPtr);
    CGContextRef cgContext = [graphics graphicsPort];
    
    CGContextRestoreGState(cgContext);
}

/*
 * Class:     com_sun_rain_graphics_Graphics2D
 * Method:    _begin
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1begin
(JNIEnv *env, jobject jGraphics, jlong graphicsPtr, jlong viewPtr)
{
	//fprintf(stderr, "Java_com_sun_rain_graphics_Graphics2D__1begin\n");
}

/*
 * Class:     com_sun_rain_graphics_Graphics2D
 * Method:    _end
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_sun_rain_graphics_Graphics2D__1end
(JNIEnv *env, jobject jGraphics, jlong graphicsPtr)
{
	//fprintf(stderr, "Java_com_sun_rain_graphics_Graphics2D__1end\n");
}

