/*
 * Copyright (c) 2011, 2012, 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 "java_awt_image_BufferedImage.h"
#import "java_awt_geom_PathIterator.h"
#import "sun_java2d_OSXSurfaceData.h"

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

#import "ImageSurfaceData.h"


//#define DEBUG 1
#if defined DEBUG
    #define QUARTZ_RENDERER_INLINE
    #define PRINT(msg) {fprintf(stderr, "%s\n", msg);fflush(stderr);}
#else
    #define QUARTZ_RENDERER_INLINE static inline
    #define PRINT(msg) {}
#endif

// Copied the following from Math.java
#define PI 3.14159265358979323846f

#define BATCHED_POINTS_SIZE 1024

// same value as defined in Sun's own code
#define XOR_ALPHA_CUTOFF 128


static CGFloat gRoundRectCtrlpts[10][12] =
{
    {0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
    {0.0f, 0.0f, 1.0f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
    {0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.0f},
    {1.0f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
    {1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, -0.5f},
    {1.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
    {1.0f, 0.0f, 0.0f, 0.0f,  1.0f, 0.0f, 0.0f, 0.0f, 1.0f, -0.5f, 0.0f, 0.0f},
    {0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
    {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f},
    {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
};

CG_EXTERN CGRect CGRectApplyAffineTransform(CGRect rect, CGAffineTransform t);


CGRect sanitizedRect(CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2) {
    CGFloat temp;
    if (x1 > x2) {
        temp = x2;
        x2 = x1;
        x1 = temp;
    }
    if (y1 > y2) {
        temp = y2;
        y2 = y1;
        y1 = temp;
    }
    return CGRectMake(x1, y1, x2-x1, y2-y1);
}

QUARTZ_RENDERER_INLINE SDRenderType doLineUsingCG(CGContextRef cgRef, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, BOOL simple, CGFloat offsetX, CGFloat offsetY)
{
//fprintf(stderr, "doLine start=(%f, %f), end=(%f, %f), linewidth:%f, offsetX:%f, offsetY:%f\n", x1, y1, x2, y2, CGContextGetLineWidth(cgRef), offsetX, offsetY);
    SDRenderType renderType = SD_Nothing;

    if (simple == YES)
    {
        struct CGPoint oneLinePoints[2];

        oneLinePoints[0] = CGPointMake(x1+offsetX, y1+offsetY);
        oneLinePoints[1] = CGPointMake(x2+offsetX, y2+offsetY);

        CGContextStrokeLineSegments(cgRef, oneLinePoints, 2);
        renderType = SD_Nothing;
    }
    else
    {
        CGContextMoveToPoint(cgRef, x1+offsetX, y1+offsetY);
        CGContextAddLineToPoint(cgRef, x2+offsetX, y2+offsetY);
        renderType = SD_Stroke;
    }

    return renderType;
}
QUARTZ_RENDERER_INLINE SDRenderType doLine(QuartzSDOps *qsdo, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2)
{
PRINT(" doLine")
    if (YES)
    {
        return doLineUsingCG(qsdo->cgRef, x1, y1, x2, y2,
                                qsdo->graphicsStateInfo.simpleStroke, qsdo->graphicsStateInfo.offsetX, qsdo->graphicsStateInfo.offsetY);
    }
    // here we can add other implementations (ex. using QuickDraw, OpenGL, etc.)
}


QUARTZ_RENDERER_INLINE SDRenderType doRectUsingCG(CGContextRef cgRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h, BOOL fill, BOOL simple, CGFloat offsetX, CGFloat offsetY)
{
//fprintf(stderr, "doRect point=(%f, %f), size=(%f, %f), offsets=(%f, %f) fill=%d simple=%d\n", x, y, w, h, offsetX, offsetY, fill, simple);
//CGRect clip = CGContextGetClipBoundingBox(cgRef);
//fprintf(stderr, "    clip: ((%f, %f), (%f, %f))\n", clip.origin.x, clip.origin.y, clip.size.width, clip.size.height);
//CGAffineTransform ctm = CGContextGetCTM(cgRef);
//fprintf(stderr, "    ctm: (%f, %f, %f, %f, %f, %f)\n", ctm.a, ctm.b, ctm.c, ctm.d, ctm.tx, ctm.ty);
    SDRenderType renderType = SD_Nothing;

    if (fill == YES)
    {
        if (simple == YES)
        {
            CGContextFillRect(cgRef, CGRectMake(x, y, w, h));
            renderType = SD_Nothing;
        }
        else
        {
            CGContextAddRect(cgRef, CGRectMake(x, y, w, h));
            renderType = SD_Fill;
        }
    }
    else
    {
        if (simple == YES)
        {
            CGContextStrokeRect(cgRef, CGRectMake(x+offsetX, y+offsetY, w, h));
            renderType = SD_Nothing;
        }
        else
        {
            CGContextAddRect(cgRef, CGRectMake(x+offsetX, y+offsetY, w, h));
            renderType = SD_Stroke;
        }
    }

    return renderType;
}
QUARTZ_RENDERER_INLINE SDRenderType doRect(QuartzSDOps *qsdo, CGFloat x, CGFloat y, CGFloat w, CGFloat h, BOOL fill)
{
PRINT(" doRect")
    if (YES)
    {
        return doRectUsingCG(qsdo->cgRef, x, y, w, h, fill,
                                qsdo->graphicsStateInfo.simpleStroke, qsdo->graphicsStateInfo.offsetX, qsdo->graphicsStateInfo.offsetY);
    }
    // here we can add other implementations (ex. using QuickDraw, OpenGL, etc.)
}

// from RoundRectIterator.java
QUARTZ_RENDERER_INLINE SDRenderType doRoundRectUsingCG(CGContextRef cgRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h, CGFloat arcWidth, CGFloat arcHeight, BOOL fill, CGFloat offsetX, CGFloat offsetY)
{
    SDRenderType renderType = SD_Nothing;

    if (fill == YES)
    {
        renderType = SD_Fill;
    }
    else
    {
        renderType = SD_Stroke;
    }

    // radr://3593731 RoundRects with corner width/height of 0 don't draw
    arcWidth = (arcWidth > 0.0f) ? arcWidth : 0.0f;
    arcHeight = (arcHeight > 0.0f) ? arcHeight : 0.0f;

    CGFloat aw = (w < arcWidth) ? w : arcWidth;
    CGFloat ah = (h < arcHeight) ? h : arcHeight;

    CGFloat *ctrls, p1, q1, p2, q2, p3, q3;
    ctrls = gRoundRectCtrlpts[0];
    p1 = (x + ctrls[0] * w + ctrls[1] * aw);
    q1 = (y + ctrls[2] * h + ctrls[3] * ah);
    CGContextMoveToPoint(cgRef, p1+offsetX, q1+offsetY);

    ctrls = gRoundRectCtrlpts[1];
    p1 = (x + ctrls[0] * w + ctrls[1] * aw);
    q1 = (y + ctrls[2] * h + ctrls[3] * ah);
    CGContextAddLineToPoint(cgRef, p1+offsetX, q1+offsetY);

    ctrls = gRoundRectCtrlpts[2];
    p1 = (x + ctrls[0] * w + ctrls[1] * aw);
    q1 = (y + ctrls[2] * h + ctrls[3] * ah);
    p2 = (x + ctrls[4] * w + ctrls[5] * aw);
    q2 = (y + ctrls[6] * h + ctrls[7] * ah);
    p3 = (x + ctrls[8] * w + ctrls[9] * aw);
    q3 = (y + ctrls[10] * h + ctrls[11] * ah);
    CGContextAddCurveToPoint(cgRef, p1+offsetX, q1+offsetY, p2+offsetX, q2+offsetY, p3+offsetX, q3+offsetY);

    ctrls = gRoundRectCtrlpts[3];
    p1 = (x + ctrls[0] * w + ctrls[1] * aw);
    q1 = (y + ctrls[2] * h + ctrls[3] * ah);
    CGContextAddLineToPoint(cgRef, p1+offsetX, q1+offsetY);

    ctrls = gRoundRectCtrlpts[4];
    p1 = (x + ctrls[0] * w + ctrls[1] * aw);
    q1 = (y + ctrls[2] * h + ctrls[3] * ah);
    p2 = (x + ctrls[4] * w + ctrls[5] * aw);
    q2 = (y + ctrls[6] * h + ctrls[7] * ah);
    p3 = (x + ctrls[8] * w + ctrls[9] * aw);
    q3 = (y + ctrls[10] * h + ctrls[11] * ah);
    CGContextAddCurveToPoint(cgRef, p1+offsetX, q1+offsetY, p2+offsetX, q2+offsetY, p3+offsetX, q3+offsetY);

    ctrls = gRoundRectCtrlpts[5];
    p1 = (x + ctrls[0] * w + ctrls[1] * aw);
    q1 = (y + ctrls[2] * h + ctrls[3] * ah);
    CGContextAddLineToPoint(cgRef, p1+offsetX, q1+offsetY);

    ctrls = gRoundRectCtrlpts[6];
    p1 = (x + ctrls[0] * w + ctrls[1] * aw);
    q1 = (y + ctrls[2] * h + ctrls[3] * ah);
    p2 = (x + ctrls[4] * w + ctrls[5] * aw);
    q2 = (y + ctrls[6] * h + ctrls[7] * ah);
    p3 = (x + ctrls[8] * w + ctrls[9] * aw);
    q3 = (y + ctrls[10] * h + ctrls[11] * ah);
    CGContextAddCurveToPoint(cgRef, p1+offsetX, q1+offsetY, p2+offsetX, q2+offsetY, p3+offsetX, q3+offsetY);

    ctrls = gRoundRectCtrlpts[7];
    p1 = (x + ctrls[0] * w + ctrls[1] * aw);
    q1 = (y + ctrls[2] * h + ctrls[3] * ah);
    CGContextAddLineToPoint(cgRef, p1+offsetX, q1+offsetY);

    ctrls = gRoundRectCtrlpts[8];
    p1 = (x + ctrls[0] * w + ctrls[1] * aw);
    q1 = (y + ctrls[2] * h + ctrls[3] * ah);
    p2 = (x + ctrls[4] * w + ctrls[5] * aw);
    q2 = (y + ctrls[6] * h + ctrls[7] * ah);
    p3 = (x + ctrls[8] * w + ctrls[9] * aw);
    q3 = (y + ctrls[10] * h + ctrls[11] * ah);
    CGContextAddCurveToPoint(cgRef, p1+offsetX, q1+offsetY, p2+offsetX, q2+offsetY, p3+offsetX, q3+offsetY);

    CGContextClosePath(cgRef);

    return renderType;
}

QUARTZ_RENDERER_INLINE SDRenderType doRoundRect(QuartzSDOps *qsdo, CGFloat x, CGFloat y, CGFloat w, CGFloat h, CGFloat arcWidth, CGFloat arcHeight, BOOL fill)
{
PRINT(" doRoundRect")
    if (YES)
    {
        return doRoundRectUsingCG(qsdo->cgRef, x, y, w, h, arcWidth, arcHeight, fill,
                                    qsdo->graphicsStateInfo.offsetX, qsdo->graphicsStateInfo.offsetY);
    }
    // here we can add other implementations (ex. using QuickDraw, OpenGL, etc.)
}

// from EllipseIterator.java
QUARTZ_RENDERER_INLINE SDRenderType doOvalUsingCG(CGContextRef cgRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h, BOOL fill, BOOL simple, CGFloat offsetX, CGFloat offsetY)
{
    SDRenderType renderType = SD_Nothing;

    if (simple == YES)
    {
        if (fill == YES)
        {
            CGContextFillEllipseInRect(cgRef, CGRectMake(x+offsetX, y+offsetY, w, h));
        }
        else
        {
            CGContextStrokeEllipseInRect(cgRef, CGRectMake(x+offsetX, y+offsetY, w, h));
        }
    }
    else
    {
        if (fill == YES)
        {
            renderType = SD_Fill;
        }
        else
        {
            renderType = SD_Stroke;
        }

        CGContextAddEllipseInRect(cgRef, CGRectMake(x+offsetX, y+offsetY, w, h));
    }

    return renderType;
}
QUARTZ_RENDERER_INLINE SDRenderType doOval(QuartzSDOps *qsdo, CGFloat x, CGFloat y, CGFloat w, CGFloat h, BOOL fill)
{
PRINT(" doOval")
    if (YES)
    {
        return doOvalUsingCG(qsdo->cgRef, x, y, w, h, fill,
                                qsdo->graphicsStateInfo.simpleStroke, qsdo->graphicsStateInfo.offsetX, qsdo->graphicsStateInfo.offsetY);
    }
    // here we can add other implementations (ex. using QuickDraw, OpenGL, etc.)
}

// from ArcIterator.java
QUARTZ_RENDERER_INLINE CGFloat btan(CGFloat increment)
{
    increment /= 2.0f;
    CGFloat a = 1.0f - cos(increment);
    CGFloat b = tan(increment);
    CGFloat c = sqrt(1.0f + b * b) - 1.0f + a;

    return 4.0f / 3.0f * a * b / c;
}
QUARTZ_RENDERER_INLINE SDRenderType doArcUsingCG(CGContextRef cgRef, CGFloat x, CGFloat y, CGFloat w, CGFloat h, CGFloat angleStart, CGFloat angleExtent, jint arcType, BOOL fill, CGFloat offsetX, CGFloat offsetY)
{
//fprintf(stderr, "doArc\n");
    SDRenderType renderType = SD_Nothing;

    if (fill == YES)
    {
        renderType = SD_Fill;
    }
    else
    {
        renderType = SD_Stroke;
    }

    CGFloat angStRad, angExtDeg;
    jint arcSegs;
    jint lineSegs;
    jint index = 1;

    w = w / 2.0f;
    h = h / 2.0f;
    x = x + w;
    y = y + h;
    angStRad = -(angleStart / 180.0f * PI);
    angExtDeg = -angleExtent;
    CGFloat ext = (angExtDeg>0) ? angExtDeg : -angExtDeg;
    if (ext >= 360.0f)
    {
        arcSegs = 4;
    }
    else
    {
        arcSegs = (jint)ceil(ext/90.0f);
    }
    switch (arcType)
    {
        case 0:
            lineSegs = 0;
            break;
        case 1:
            lineSegs = 1;
            break;
        case 2:
            lineSegs = 2;
            break;
    }
    if (w < 0 || h < 0)
    {
        arcSegs = lineSegs = -1;
    }

    CGFloat angle = angStRad;
    CGContextMoveToPoint(cgRef, (x + cos(angle) * w)+offsetX, (y + sin(angle) * h)+offsetY);

    CGFloat increment = angExtDeg;
    if (increment > 360.0f)
    {
        increment = 360.0f;
    }
    else if (increment < -360.0f)
    {
        increment = -360.0f;
    }
    increment /= arcSegs;
    increment = (increment / 180.0f * PI);
    CGFloat z = btan(increment);
    CGFloat angleBase = angle;
    CGFloat p1, q1, p2, q2, p3, q3;
    while (index <= arcSegs)
    {
        angle = angleBase + increment * (index - 1);
        CGFloat relx = cos(angle);
        CGFloat rely = sin(angle);
        p1 = (x + (relx - z * rely) * w);
        q1 = (y + (rely + z * relx) * h);
        angle += increment;
        relx = cos(angle);
        rely = sin(angle);
        p2 = (x + (relx + z * rely) * w);
        q2 = (y + (rely - z * relx) * h);
        p3 = (x + relx * w);
        q3 = (y + rely * h);

        CGContextAddCurveToPoint(cgRef, p1+offsetX, q1+offsetY, p2+offsetX, q2+offsetY, p3+offsetX, q3+offsetY);

        index++;
    }

    switch (arcType)
    {
        case 1:
            CGContextClosePath(cgRef);
            break;
        case 2:
            CGContextAddLineToPoint(cgRef, x+offsetX, y+offsetY);
            CGContextClosePath(cgRef);
            break;
        default:
            break;
    }

    return renderType;
}
QUARTZ_RENDERER_INLINE SDRenderType doArc(QuartzSDOps *qsdo, CGFloat x, CGFloat y, CGFloat w, CGFloat h, CGFloat angleStart, CGFloat angleExtent, jint arcType, BOOL fill)
{
PRINT(" doArc")
    if (YES)
    {
        return doArcUsingCG(qsdo->cgRef, x, y, w, h, angleStart, angleExtent, arcType, fill,
                                qsdo->graphicsStateInfo.offsetX, qsdo->graphicsStateInfo.offsetY);
    }
    // here we can add other implementations (ex. using QuickDraw, OpenGL, etc.)
}

QUARTZ_RENDERER_INLINE SDRenderType doPolyUsingCG(JNIEnv *env, CGContextRef cgRef, jintArray xpointsarray, jintArray ypointsarray, jint npoints, BOOL polygon, BOOL fill, CGFloat offsetX, CGFloat offsetY)
{
    SDRenderType renderType = SD_Nothing;

    if (xpointsarray == NULL || ypointsarray == NULL) {
        return SD_Nothing;
    }
    if (npoints > 1)
    {
        if (fill == YES)
        {
            renderType = SD_Fill;
        }
        else
        {
            renderType = SD_Stroke;
        }

        jint i;

        jint* xpoints = (jint*)(*env)->GetPrimitiveArrayCritical(env, xpointsarray, NULL);
        if (xpoints == NULL) {
            return SD_Nothing;
        }
        jint* ypoints = (jint*)(*env)->GetPrimitiveArrayCritical(env, ypointsarray, NULL);
        if (ypoints == NULL) {
            (*env)->ReleasePrimitiveArrayCritical(env, xpointsarray, xpoints, 0);
            return SD_Nothing;
        }

        CGContextMoveToPoint(cgRef, xpoints[0]+offsetX, ypoints[0]+offsetY);

        for (i=1; i<npoints; i++)
        {
            CGContextAddLineToPoint(cgRef, xpoints[i]+offsetX, ypoints[i]+offsetY);
        }

        if (polygon == YES)
        {
            if ((xpoints[0] != xpoints[npoints-1]) || (ypoints[0] != ypoints[npoints-1])) // according to the specs (only applies to polygons, not polylines)
            {
                CGContextAddLineToPoint(cgRef, xpoints[0]+offsetX, ypoints[0]+offsetY);
            }
        }

        (*env)->ReleasePrimitiveArrayCritical(env, ypointsarray, ypoints, 0);
        (*env)->ReleasePrimitiveArrayCritical(env, xpointsarray, xpoints, 0);
    }

    return renderType;
}
QUARTZ_RENDERER_INLINE SDRenderType doPoly(JNIEnv *env, QuartzSDOps *qsdo, jintArray xpointsarray, jintArray ypointsarray, jint npoints, BOOL polygon, BOOL fill)
{
PRINT(" doPoly")
    if (YES)
    {
        return doPolyUsingCG(env, qsdo->cgRef, xpointsarray, ypointsarray, npoints, polygon, fill,
            qsdo->graphicsStateInfo.offsetX, qsdo->graphicsStateInfo.offsetY);
    }
    // here we can add other implementations (ex. using QuickDraw, OpenGL, etc.)
}

SDRenderType doShape(QuartzSDOps *qsdo, jint *types, jfloat *coords, jint numtypes, BOOL fill, BOOL shouldApplyOffset)
{
PRINT(" doShape")
    if (YES)
    {
        CGFloat offsetX = 0.0f;
        CGFloat offsetY = 0.0f;
        if (shouldApplyOffset)
        {
            offsetX = qsdo->graphicsStateInfo.offsetX;
            offsetY = qsdo->graphicsStateInfo.offsetY;
        }
        return DoShapeUsingCG(qsdo->cgRef, types, coords, numtypes, fill, offsetX, offsetY); // defined in QuartzSurfaceData.m
    }
    // here we can add other implementations (ex. using QuickDraw, OpenGL, etc.)
}



QUARTZ_RENDERER_INLINE void doImageCG(JNIEnv *env, CGContextRef cgRef, jobject imageSurfaceData,
                                        jint interpolation, BOOL fliph, BOOL flipv, jint w, jint h, jint sx, jint sy, jint sw, jint sh, jint dx, jint dy, jint dw, jint dh)
{
//fprintf(stderr, "doImageCG\n");
//fprintf(stderr, "    flip:(%d, %d), size:(%d, %d), src:(%d, %d, %d, %d), dst:(%d, %d, %d, %d)\n", (jint)fliph, (jint)flipv, w, h, sx, sy, sw, sh, dx, dy, dw, dh);
    // gznote: need to handle interpolation
    ImageSDOps* isdo = LockImage(env, imageSurfaceData);

    CGFloat a = 1.0f;
    CGFloat b = 0.0f;
    CGFloat c = 0.0f;
    CGFloat d = -1.0f;
    CGFloat tx = dx;
    CGFloat ty = dy+dh;

    if (flipv == YES)
    {
        d = 1.0f;
        ty -= dh;
    }
    if (fliph == YES)
    {
        a = -1.0f;
        tx += dw;
    }

    makeSureImageIsCreated(isdo);

    CGContextSaveGState(cgRef);
    CGContextConcatCTM(cgRef, CGAffineTransformMake(a, b, c, d, tx, ty));
    jint alphaInfo = isdo->contextInfo.alphaInfo & kCGBitmapAlphaInfoMask;

    if ((sx == 0) && (sy == 0) && (sw == w) && (sh == h)) // no subimages allowed here
    {
        CGContextDrawImage(cgRef, CGRectMake(0, 0, dw, dh), isdo->imgRef);
    }
    else // handle subimages
    {
        CGImageRef subImg = CGImageCreateWithImageInRect(isdo->imgRef, CGRectMake(sx, sy, sw, sh));
        CGContextDrawImage(cgRef, CGRectMake(0.0f, 0.0f, dw, dh), subImg);
        CGImageRelease(subImg);
    }

    CGContextRestoreGState(cgRef);
    UnlockImage(env, isdo);
}

QUARTZ_RENDERER_INLINE void doImage(JNIEnv *env, QuartzSDOps *qsdo, jobject imageSurfaceData,
                                jboolean fliph, jboolean flipv, jint w, jint h, jint sx, jint sy, jint sw, jint sh, jint dx, jint dy, jint dw, jint dh)
{
    if ((w > 0) && (h > 0) && (sw > 0) && (sh > 0) && (dw > 0) && (dh > 0))
    {
       doImageCG(env, qsdo->cgRef, imageSurfaceData,
                            qsdo->graphicsStateInfo.interpolation, (BOOL)fliph, (BOOL)flipv, (jint)w, (jint)h, (jint)sx, (jint)sy, (jint)sw, (jint)sh, (jint)dx, (jint)dy, (jint)dw, (jint)dh);
    }
}



QUARTZ_RENDERER_INLINE void completePath(JNIEnv *env, QuartzSDOps *qsdo, CGContextRef cgRef, jint renderType)
{
    switch (renderType)
    {
        case SD_Stroke:
            if (CGContextIsPathEmpty(cgRef) == 0)
            {
                CGContextStrokePath(cgRef);
            }
            break;
        case SD_Fill:
            if (CGContextIsPathEmpty(cgRef) == 0)
            {
                CGContextFillPath(cgRef);
            }
            break;
        case SD_Image:
            break;
        case SD_Nothing:
                break;
        default:
fprintf(stderr, "completePath unknown renderType=%d\n", (int)renderType);
            break;
    }
}

/*
 * Class:     sun_java2d_CRenderer
 * Method:    init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_sun_java2d_CRenderer_init
(JNIEnv *env, jobject jthis)
{
PRINT("Java_sun_java2d_CRenderer_init")
    CGFloat angle = PI / 4.0f;
    CGFloat a = 1.0f - cos(angle);
    CGFloat b = tan(angle);
    CGFloat c = sqrt(1.0f + b * b) - 1.0f + a;
    CGFloat cv = 4.0f / 3.0f * a * b / c;
    CGFloat acv = (1.0f - cv) / 2.0f;

    gRoundRectCtrlpts[2][3] = -acv;
    gRoundRectCtrlpts[2][5] = acv;
    gRoundRectCtrlpts[4][1] = -acv;
    gRoundRectCtrlpts[4][7] = -acv;
    gRoundRectCtrlpts[6][3] = acv;
    gRoundRectCtrlpts[6][5] = -acv;
    gRoundRectCtrlpts[8][1] = acv;
    gRoundRectCtrlpts[8][7] = acv;
}

/*
 * Class:     sun_java2d_CRenderer
 * Method:    doLine
 * Signature: (Lsun/java2d/SurfaceData;Ljava/nio/IntBuffer;Ljava/nio/FloatBuffer;[Ljava/lang/Object;FFFF)V
 */
JNIEXPORT void JNICALL Java_sun_java2d_CRenderer_doLine
(JNIEnv *env, jobject jthis, jobject jsurfacedata, jfloat x1, jfloat y1, jfloat x2, jfloat y2)
{
PRINT("Java_sun_java2d_CRenderer_doLine")
    QuartzSDOps *qsdo = (QuartzSDOps*)SurfaceData_GetOps(env, jsurfacedata);
JNF_COCOA_ENTER(env);
    SDRenderType renderType = SD_Stroke;
    qsdo->BeginSurface(env, qsdo, renderType);
    if (qsdo->cgRef != NULL)
    {
        doLine(qsdo, x1, y1, x2, y2);
    }
    qsdo->FinishSurface(env, qsdo);
JNF_COCOA_RENDERER_EXIT(env);
}

/*
 * Class:     sun_java2d_CRenderer
 * Method:    doRect
 * Signature: (Lsun/java2d/SurfaceData;Ljava/nio/IntBuffer;Ljava/nio/FloatBuffer;[Ljava/lang/Object;FFFF)V
 */
JNIEXPORT void JNICALL Java_sun_java2d_CRenderer_doRect
(JNIEnv *env, jobject jthis, jobject jsurfacedata, jfloat x, jfloat y, jfloat w, jfloat h, jboolean isfill)
{
PRINT("Java_sun_java2d_CRenderer_doRect")
    QuartzSDOps *qsdo = (QuartzSDOps*)SurfaceData_GetOps(env, jsurfacedata);
JNF_COCOA_ENTER(env);
    SDRenderType renderType    = (isfill? SD_Fill : SD_Stroke);
    qsdo->BeginSurface(env, qsdo, renderType);
    if (qsdo->cgRef != NULL)
    {
        doRect(qsdo, x, y, w, h, isfill);
    }
    qsdo->FinishSurface(env, qsdo);
JNF_COCOA_RENDERER_EXIT(env);
}

/*
 * Class:     sun_java2d_CRenderer
 * Method:    doRoundRect
 * Signature: (Lsun/java2d/SurfaceData;Ljava/nio/IntBuffer;Ljava/nio/FloatBuffer;[Ljava/lang/Object;IIIIII)V
 */
JNIEXPORT void JNICALL Java_sun_java2d_CRenderer_doRoundRect
(JNIEnv *env, jobject jthis, jobject jsurfacedata, jfloat x, jfloat y, jfloat w, jfloat h, jfloat arcWidth, jfloat arcHeight, jboolean isfill)
{
PRINT("Java_sun_java2d_CRenderer_doRoundRect")
    QuartzSDOps *qsdo = (QuartzSDOps*)SurfaceData_GetOps(env, jsurfacedata);
JNF_COCOA_ENTER(env);
    SDRenderType renderType    = (isfill? SD_Fill : SD_Stroke);
    qsdo->BeginSurface(env, qsdo, renderType);
    if (qsdo->cgRef != NULL)
    {
        doRoundRect(qsdo, x, y, w, h, arcWidth, arcHeight, isfill);
    }
    qsdo->FinishSurface(env, qsdo);
JNF_COCOA_RENDERER_EXIT(env);
}

/*
 * Class:     sun_java2d_CRenderer
 * Method:    doOval
 * Signature: (Lsun/java2d/SurfaceData;Ljava/nio/IntBuffer;Ljava/nio/FloatBuffer;[Ljava/lang/Object;IIII)V
 */
JNIEXPORT void JNICALL Java_sun_java2d_CRenderer_doOval
(JNIEnv *env, jobject jthis, jobject jsurfacedata, jfloat x, jfloat y, jfloat w, jfloat h, jboolean isfill)
{
PRINT("Java_sun_java2d_CRenderer_doOval")
    QuartzSDOps *qsdo = (QuartzSDOps*)SurfaceData_GetOps(env, jsurfacedata);
JNF_COCOA_ENTER(env);
    SDRenderType renderType    = (isfill? SD_Fill : SD_Stroke);
    qsdo->BeginSurface(env, qsdo, renderType);
    if (qsdo->cgRef != NULL)
    {
        doOval(qsdo, x, y, w, h, isfill);
    }
    qsdo->FinishSurface(env, qsdo);
JNF_COCOA_RENDERER_EXIT(env);
}

/*
 * Class:     sun_java2d_CRenderer
 * Method:    doArc
 * Signature: (Lsun/java2d/SurfaceData;Ljava/nio/IntBuffer;Ljava/nio/FloatBuffer;[Ljava/lang/Object;IIIIII)V
 */
JNIEXPORT void JNICALL Java_sun_java2d_CRenderer_doArc
(JNIEnv *env, jobject jthis, jobject jsurfacedata, jfloat x, jfloat y, jfloat w, jfloat h, jfloat angleStart, jfloat angleExtent, jint arcType, jboolean isfill)
{
PRINT("Java_sun_java2d_CRenderer_doArc")
    QuartzSDOps *qsdo = (QuartzSDOps*)SurfaceData_GetOps(env, jsurfacedata);
JNF_COCOA_ENTER(env);
    SDRenderType renderType    = (isfill? SD_Fill : SD_Stroke);
    qsdo->BeginSurface(env, qsdo, renderType);
    if (qsdo->cgRef != NULL)
    {
        doArc(qsdo, x, y, w, h, angleStart, angleExtent, arcType, isfill);
    }
    qsdo->FinishSurface(env, qsdo);
JNF_COCOA_RENDERER_EXIT(env);
}

/*
 * Class:     sun_java2d_CRenderer
 * Method:    doPoly
 * Signature:
 */
JNIEXPORT void JNICALL Java_sun_java2d_CRenderer_doPoly
(JNIEnv *env, jobject jthis, jobject jsurfacedata, jintArray xpointsarray, jintArray ypointsarray, jint npoints, jboolean ispolygon, jboolean isfill)
{
PRINT("Java_sun_java2d_CRenderer_doPoly")
    QuartzSDOps *qsdo = (QuartzSDOps*)SurfaceData_GetOps(env, jsurfacedata);
JNF_COCOA_ENTER(env);
    BOOL eoFill = YES; // polys are WIND_EVEN_ODD by definition
    SDRenderType renderType    = (isfill? (eoFill ? SD_EOFill : SD_Fill) : SD_Stroke);
    qsdo->BeginSurface(env, qsdo, renderType);
    if (qsdo->cgRef != NULL)
    {
        doPoly(env, qsdo, xpointsarray, ypointsarray, npoints, ispolygon, isfill);
    }
    qsdo->FinishSurface(env, qsdo);
JNF_COCOA_RENDERER_EXIT(env);
}

/*
 * Class:     sun_java2d_CRenderer
 * Method:    doShape
 * Signature: (Lsun/java2d/SurfaceData;Ljava/nio/IntBuffer;Ljava/nio/FloatBuffer;[Ljava/lang/Object;ILjava/nio/FloatBuffer;Ljava/nio/IntBuffer;IZ)V
 */
JNIEXPORT void JNICALL Java_sun_java2d_CRenderer_doShape
(JNIEnv *env, jobject jthis, jobject jsurfacedata, jint length, jobject jFloatCoordinates, jobject jIntTypes, jint windingRule, jboolean isfill, jboolean shouldApplyOffset)
{
PRINT("Java_sun_java2d_CRenderer_doShape")
    QuartzSDOps *qsdo = (QuartzSDOps*)SurfaceData_GetOps(env, jsurfacedata);
JNF_COCOA_ENTER(env);
    BOOL eoFill = (windingRule == java_awt_geom_PathIterator_WIND_EVEN_ODD);
    SDRenderType renderType    = (isfill? (eoFill ? SD_EOFill : SD_Fill) : SD_Stroke);
    qsdo->BeginSurface(env, qsdo, renderType);
    if (qsdo->cgRef != NULL)
    {
        jfloat *coordinates = (jfloat*)((*env)->GetDirectBufferAddress(env, jFloatCoordinates));
        jint *types = (jint*)((*env)->GetDirectBufferAddress(env, jIntTypes));
        doShape(qsdo, types, coordinates, length, isfill, shouldApplyOffset);
    }
    qsdo->FinishSurface(env, qsdo);
JNF_COCOA_RENDERER_EXIT(env);
}

#define invalidContext(c) \
    ((c) == NULL /* || (c)->identifer != CGContextIdentifier */)

/*
 * Class:     sun_java2d_CRenderer
 * Method:    doImage
 * Signature: (Lsun/java2d/SurfaceData;Ljava/nio/IntBuffer;Ljava/nio/FloatBuffer;[Ljava/lang/Object;Lsun/java2d/SurfaceData;ZZIIIIIIII)V
 */
JNIEXPORT void JNICALL Java_sun_java2d_CRenderer_doImage
(JNIEnv *env, jobject jthis, jobject jsurfacedata, jobject imageSurfaceData, jboolean fliph, jboolean flipv, jint w, jint h, jint sx, jint sy, jint sw, jint sh, jint dx, jint dy, jint dw, jint dh)
{
PRINT("Java_sun_java2d_CRenderer_doImage")
    QuartzSDOps *qsdo = (QuartzSDOps*)SurfaceData_GetOps(env, jsurfacedata);
JNF_COCOA_ENTER(env);
    qsdo->BeginSurface(env, qsdo, SD_Image);
    if (qsdo->cgRef != NULL)
    {
        doImage(env, qsdo, imageSurfaceData, fliph, flipv, w, h, sx, sy, sw, sh, dx, dy, dw, dh);
    }
    qsdo->FinishSurface(env, qsdo);
JNF_COCOA_RENDERER_EXIT(env);
}
