/*
 * Copyright (c) 2010, 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.
 */

package sun.java2d.jules;

import java.awt.*;
import java.awt.geom.*;
import sun.awt.X11GraphicsEnvironment;
import sun.java2d.pipe.*;
import sun.java2d.xr.*;

public class JulesPathBuf {
    static final double[] emptyDash = new double[0];

    private static final byte CAIRO_PATH_OP_MOVE_TO = 0;
    private static final byte CAIRO_PATH_OP_LINE_TO = 1;
    private static final byte CAIRO_PATH_OP_CURVE_TO = 2;
    private static final byte CAIRO_PATH_OP_CLOSE_PATH = 3;

    private static final int  CAIRO_FILL_RULE_WINDING = 0;
    private static final int CAIRO_FILL_RULE_EVEN_ODD = 1;

    GrowablePointArray points = new GrowablePointArray(128);
    GrowableByteArray ops = new GrowableByteArray(1, 128);
    int[] xTrapArray = new int[512];

    private static final boolean isCairoAvailable;

    static {
        isCairoAvailable =
           java.security.AccessController.doPrivileged(
                          new java.security.PrivilegedAction<Boolean>() {
            public Boolean run() {
                boolean loadSuccess = false;
                if (X11GraphicsEnvironment.isXRenderAvailable()) {
                    try {
                        System.loadLibrary("jules");
                        loadSuccess = true;
                        if (X11GraphicsEnvironment.isXRenderVerbose()) {
                            System.out.println(
                                       "Xrender: INFO: Jules library loaded");
                        }
                    } catch (UnsatisfiedLinkError ex) {
                        loadSuccess = false;
                        if (X11GraphicsEnvironment.isXRenderVerbose()) {
                            System.out.println(
                                "Xrender: INFO: Jules library not installed.");
                        }
                    }
                }
                return Boolean.valueOf(loadSuccess);
            }
        });
    }

    public static boolean isCairoAvailable() {
        return isCairoAvailable;
    }

    public TrapezoidList tesselateFill(Shape s, AffineTransform at, Region clip) {
        int windingRule = convertPathData(s, at);
        xTrapArray[0] = 0;

        xTrapArray = tesselateFillNative(points.getArray(), ops.getArray(),
                                         points.getSize(), ops.getSize(),
                                         xTrapArray, xTrapArray.length,
                                         getCairoWindingRule(windingRule),
                                         clip.getLoX(), clip.getLoY(),
                                         clip.getHiX(), clip.getHiY());

        return new TrapezoidList(xTrapArray);
    }

    public TrapezoidList tesselateStroke(Shape s, BasicStroke bs, boolean thin,
                                         boolean adjust, boolean antialias,
                                         AffineTransform at, Region clip) {

        float lw;
        if (thin) {
            if (antialias) {
                lw = 0.5f;
            } else {
                lw = 1.0f;
            }
        } else {
            lw = bs.getLineWidth();
        }

        convertPathData(s, at);

        double[] dashArray = floatToDoubleArray(bs.getDashArray());
        xTrapArray[0] = 0;

        xTrapArray =
             tesselateStrokeNative(points.getArray(), ops.getArray(),
                                   points.getSize(), ops.getSize(),
                                   xTrapArray, xTrapArray.length, lw,
                                   bs.getEndCap(), bs.getLineJoin(),
                                   bs.getMiterLimit(), dashArray,
                                   dashArray.length, bs.getDashPhase(),
                                   1, 0, 0, 0, 1, 0,
                                   clip.getLoX(), clip.getLoY(),
                                   clip.getHiX(), clip.getHiY());

        return new TrapezoidList(xTrapArray);
    }

    protected double[] floatToDoubleArray(float[] dashArrayFloat) {
        double[] dashArrayDouble = emptyDash;
        if (dashArrayFloat != null) {
            dashArrayDouble = new double[dashArrayFloat.length];

            for (int i = 0; i < dashArrayFloat.length; i++) {
                dashArrayDouble[i] = dashArrayFloat[i];
            }
        }

        return dashArrayDouble;
    }

    protected int convertPathData(Shape s, AffineTransform at) {
        PathIterator pi = s.getPathIterator(at);

        double[] coords = new double[6];
        double currX = 0;
        double currY = 0;

        while (!pi.isDone()) {
            int curOp = pi.currentSegment(coords);

            int pointIndex;
            switch (curOp) {

            case PathIterator.SEG_MOVETO:
                ops.addByte(CAIRO_PATH_OP_MOVE_TO);
                pointIndex = points.getNextIndex();
                points.setX(pointIndex, DoubleToCairoFixed(coords[0]));
                points.setY(pointIndex, DoubleToCairoFixed(coords[1]));
                currX = coords[0];
                currY = coords[1];
                break;

            case PathIterator.SEG_LINETO:
                ops.addByte(CAIRO_PATH_OP_LINE_TO);
                pointIndex = points.getNextIndex();
                points.setX(pointIndex, DoubleToCairoFixed(coords[0]));
                points.setY(pointIndex, DoubleToCairoFixed(coords[1]));
                currX = coords[0];
                currY = coords[1];
                break;

                /**
                 *    q0 = p0
                 *    q1 = (p0+2*p1)/3
                 *    q2 = (p2+2*p1)/3
                 *    q3 = p2
                 */
            case PathIterator.SEG_QUADTO:
                double x1 = coords[0];
                double y1 = coords[1];
                double x2, y2;
                double x3 = coords[2];
                double y3 = coords[3];

                x2 = x1 + (x3 - x1) / 3;
                y2 = y1 + (y3 - y1) / 3;
                x1 = currX + 2 * (x1 - currX) / 3;
                y1 =currY + 2 * (y1 - currY) / 3;

                ops.addByte(CAIRO_PATH_OP_CURVE_TO);
                pointIndex = points.getNextIndex();
                points.setX(pointIndex, DoubleToCairoFixed(x1));
                points.setY(pointIndex, DoubleToCairoFixed(y1));
                pointIndex = points.getNextIndex();
                points.setX(pointIndex, DoubleToCairoFixed(x2));
                points.setY(pointIndex, DoubleToCairoFixed(y2));
                pointIndex = points.getNextIndex();
                points.setX(pointIndex, DoubleToCairoFixed(x3));
                points.setY(pointIndex, DoubleToCairoFixed(y3));
                currX = x3;
                currY = y3;
                break;

            case PathIterator.SEG_CUBICTO:
                ops.addByte(CAIRO_PATH_OP_CURVE_TO);
                pointIndex = points.getNextIndex();
                points.setX(pointIndex, DoubleToCairoFixed(coords[0]));
                points.setY(pointIndex, DoubleToCairoFixed(coords[1]));
                pointIndex = points.getNextIndex();
                points.setX(pointIndex, DoubleToCairoFixed(coords[2]));
                points.setY(pointIndex, DoubleToCairoFixed(coords[3]));
                pointIndex = points.getNextIndex();
                points.setX(pointIndex, DoubleToCairoFixed(coords[4]));
                points.setY(pointIndex, DoubleToCairoFixed(coords[5]));
                currX = coords[4];
                currY = coords[5];
                break;

            case PathIterator.SEG_CLOSE:
                ops.addByte(CAIRO_PATH_OP_CLOSE_PATH);
                break;
            }

            pi.next();
        }

        return pi.getWindingRule();
    }

    private static native int[]
         tesselateStrokeNative(int[] pointArray, byte[] ops,
                               int pointCnt, int opCnt,
                               int[] xTrapArray, int xTrapArrayLength,
                               double lineWidth, int lineCap, int lineJoin,
                               double miterLimit, double[] dashArray,
                               int dashCnt, double offset,
                               double m00, double m01, double m02,
                               double m10, double m11, double m12,
                               int clipLowX, int clipLowY,
                               int clipWidth, int clipHeight);

    private static native int[]
        tesselateFillNative(int[] pointArray, byte[] ops, int pointCnt,
                            int opCnt, int[] xTrapArray, int xTrapArrayLength,
                            int windingRule, int clipLowX, int clipLowY,                                    int clipWidth, int clipHeight);

    public void clear() {
        points.clear();
        ops.clear();
        xTrapArray[0] = 0;
    }

    private static int DoubleToCairoFixed(double dbl) {
        return (int) (dbl * 256);
    }

    private static int getCairoWindingRule(int j2dWindingRule) {
        switch(j2dWindingRule) {
        case PathIterator.WIND_EVEN_ODD:
            return CAIRO_FILL_RULE_EVEN_ODD;

        case PathIterator.WIND_NON_ZERO:
            return CAIRO_FILL_RULE_WINDING;

            default:
                throw new IllegalArgumentException("Illegal Java2D winding rule specified");
        }
    }
}