/*
 * Copyright (c) 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.
 *
 * 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.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.IllegalPathStateException;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;

/**
 * @test
 * @bug 8076419
 * @summary Check Path2D copy constructor (trims arrays)
 *          and constructor with zero capacity
 * @run main Path2DCopyConstructor
 */
public class Path2DCopyConstructor {

    private final static float EPSILON = 5e-6f;
    private final static float FLATNESS = 1e-2f;

    private final static AffineTransform at
        = AffineTransform.getScaleInstance(1.3, 2.4);

    private final static Rectangle2D.Double rect2d
        = new Rectangle2D.Double(3.2, 4.1, 5.0, 10.0);

    private final static Point2D.Double pt2d
        = new Point2D.Double(2.0, 2.5);

    public static boolean verbose;

    static void log(String msg) {
        if (verbose) {
            System.out.println(msg);
        }
    }

    public static void main(String argv[]) {
        verbose = (argv.length != 0);

        testEmptyDoublePaths();
        testDoublePaths();

        testEmptyFloatPaths();
        testFloatPaths();

        testEmptyGeneralPath();
        testGeneralPath();
    }

    static void testEmptyDoublePaths() {
        log("\n - Test(Path2D.Double[0]) ---");
        test(() -> new Path2D.Double(Path2D.WIND_NON_ZERO, 0));
    }

    static void testDoublePaths() {
        log("\n - Test(Path2D.Double) ---");
        test(() -> new Path2D.Double());
    }

    static void testEmptyFloatPaths() {
        log("\n - Test(Path2D.Float[0]) ---");
        test(() -> new Path2D.Float(Path2D.WIND_NON_ZERO, 0));
    }

    static void testFloatPaths() {
        log("\n - Test(Path2D.Float) ---");
        test(() -> new Path2D.Float());
    }

    static void testEmptyGeneralPath() {
        log("\n - Test(GeneralPath[0]) ---");
        test(() -> new GeneralPath(Path2D.WIND_NON_ZERO, 0));
    }

    static void testGeneralPath() {
        log("\n - Test(GeneralPath) ---");
        test(() -> new GeneralPath());
    }

    interface PathFactory {
        Path2D makePath();
    }

    static void test(PathFactory pf) {
        log("\n --- test: path(empty) ---");
        test(pf.makePath(), true);
        log("\n\n --- test: path(addMove) ---");
        test(addMove(pf.makePath()), false);
        log("\n\n --- test: path(addMoveAndLines) ---");
        test(addMoveAndLines(pf.makePath()), false);
        log("\n\n --- test: path(addMoveAndQuads) ---");
        test(addMoveAndQuads(pf.makePath()), false);
        log("\n\n --- test: path(addMoveAndCubics) ---");
        test(addMoveAndCubics(pf.makePath()), false);
        log("\n\n --- test: path(addMoveAndClose) ---");
        test(addMoveAndClose(pf.makePath()), false);
    }

    static Path2D addMove(Path2D p2d) {
        p2d.moveTo(1.0, 0.5);
        return p2d;
    }

    static Path2D addMoveAndLines(Path2D p2d) {
        addMove(p2d);
        addLines(p2d);
        return p2d;
    }

    static Path2D addLines(Path2D p2d) {
        for (int i = 0; i < 10; i++) {
            p2d.lineTo(1.1 * i, 2.3 * i);
        }
        return p2d;
    }

    static Path2D addMoveAndCubics(Path2D p2d) {
        addMove(p2d);
        addCubics(p2d);
        return p2d;
    }

    static Path2D addCubics(Path2D p2d) {
        for (int i = 0; i < 10; i++) {
            p2d.curveTo(1.1 * i, 1.2 * i, 1.3 * i, 1.4 * i, 1.5 * i, 1.6 * i);
        }
        return p2d;
    }

    static Path2D addMoveAndQuads(Path2D p2d) {
        addMove(p2d);
        addQuads(p2d);
        return p2d;
    }

    static Path2D addQuads(Path2D p2d) {
        for (int i = 0; i < 10; i++) {
            p2d.quadTo(1.1 * i, 1.2 * i, 1.3 * i, 1.4 * i);
        }
        return p2d;
    }

    static Path2D addMoveAndClose(Path2D p2d) {
        addMove(p2d);
        addClose(p2d);
        return p2d;
    }

    static Path2D addClose(Path2D p2d) {
        p2d.closePath();
        return p2d;
    }

    static void test(Path2D p2d, boolean isEmpty) {
        testEqual(new Path2D.Float(p2d), p2d);
        testEqual(new Path2D.Double(p2d), p2d);
        testEqual(new GeneralPath(p2d), p2d);

        testIterator(new Path2D.Float(p2d), p2d);
        testIterator(new Path2D.Double(p2d), p2d);
        testIterator((Path2D) p2d.clone(), p2d);

        testFlattening(new Path2D.Float(p2d), p2d);
        testFlattening(new Path2D.Double(p2d), p2d);
        testFlattening((Path2D) p2d.clone(), p2d);

        testAddMove(new Path2D.Float(p2d));
        testAddMove(new Path2D.Double(p2d));
        testAddMove((Path2D) p2d.clone());

        // These should expect exception if empty
        testAddLine(new Path2D.Float(p2d), isEmpty);
        testAddLine(new Path2D.Double(p2d), isEmpty);
        testAddLine((Path2D) p2d.clone(), isEmpty);

        testAddQuad(new Path2D.Float(p2d), isEmpty);
        testAddQuad(new Path2D.Double(p2d), isEmpty);
        testAddQuad((Path2D) p2d.clone(), isEmpty);

        testAddCubic(new Path2D.Float(p2d), isEmpty);
        testAddCubic(new Path2D.Double(p2d), isEmpty);
        testAddCubic((Path2D) p2d.clone(), isEmpty);

        testAddClose(new Path2D.Float(p2d), isEmpty);
        testAddClose(new Path2D.Double(p2d), isEmpty);
        testAddClose((Path2D) p2d.clone(), isEmpty);

        testGetBounds(new Path2D.Float(p2d), p2d);
        testGetBounds(new Path2D.Double(p2d), p2d);
        testGetBounds((Path2D) p2d.clone(), p2d);

        testTransform(new Path2D.Float(p2d));
        testTransform(new Path2D.Double(p2d));
        testTransform((Path2D) p2d.clone());

        testIntersect(new Path2D.Float(p2d), p2d);
        testIntersect(new Path2D.Double(p2d), p2d);
        testIntersect((Path2D) p2d.clone(), p2d);

        testContains(new Path2D.Float(p2d), p2d);
        testContains(new Path2D.Double(p2d), p2d);
        testContains((Path2D) p2d.clone(), p2d);

        testGetCurrentPoint(new Path2D.Float(p2d), p2d);
        testGetCurrentPoint(new Path2D.Double(p2d), p2d);
        testGetCurrentPoint((Path2D) p2d.clone(), p2d);
    }

    static void testEqual(Path2D pathA, Path2D pathB) {
        final PathIterator itA = pathA.getPathIterator(null);
        final PathIterator itB = pathB.getPathIterator(null);

        float[] coordsA = new float[6];
        float[] coordsB = new float[6];

        int n = 0;
        for (; !itA.isDone() && !itB.isDone(); itA.next(), itB.next(), n++) {
            int typeA = itA.currentSegment(coordsA);
            int typeB = itB.currentSegment(coordsB);

            if (typeA != typeB) {
                throw new IllegalStateException("Path-segment[" + n + "] "
                    + " type are not equals [" + typeA + "|" + typeB + "] !");
            }
            if (!equalsArray(coordsA, coordsB, getLength(typeA))) {
                throw new IllegalStateException("Path-segment[" + n + "] coords"
                    + " are not equals [" + Arrays.toString(coordsA) + "|"
                    + Arrays.toString(coordsB) + "] !");
            }
        }
        if (!itA.isDone() || !itB.isDone()) {
            throw new IllegalStateException("Paths do not have same lengths !");
        }
        log("testEqual: " + n + " segments.");
    }

    static void testIterator(Path2D pathA, Path2D pathB) {
        final PathIterator itA = pathA.getPathIterator(at);
        final PathIterator itB = pathB.getPathIterator(at);

        float[] coordsA = new float[6];
        float[] coordsB = new float[6];

        int n = 0;
        for (; !itA.isDone() && !itB.isDone(); itA.next(), itB.next(), n++) {
            int typeA = itA.currentSegment(coordsA);
            int typeB = itB.currentSegment(coordsB);

            if (typeA != typeB) {
                throw new IllegalStateException("Path-segment[" + n + "] "
                    + "type are not equals [" + typeA + "|" + typeB + "] !");
            }
            // Take care of floating-point precision:
            if (!equalsArrayEps(coordsA, coordsB, getLength(typeA))) {
                throw new IllegalStateException("Path-segment[" + n + "] coords"
                    + " are not equals [" + Arrays.toString(coordsA) + "|"
                    + Arrays.toString(coordsB) + "] !");
            }
        }
        if (!itA.isDone() || !itB.isDone()) {
            throw new IllegalStateException("Paths do not have same lengths !");
        }
        log("testIterator: " + n + " segments.");
    }

    static void testFlattening(Path2D pathA, Path2D pathB) {
        final PathIterator itA = pathA.getPathIterator(at, FLATNESS);
        final PathIterator itB = pathB.getPathIterator(at, FLATNESS);

        float[] coordsA = new float[6];
        float[] coordsB = new float[6];

        int n = 0;
        for (; !itA.isDone() && !itB.isDone(); itA.next(), itB.next(), n++) {
            int typeA = itA.currentSegment(coordsA);
            int typeB = itB.currentSegment(coordsB);

            if (typeA != typeB) {
                throw new IllegalStateException("Path-segment[" + n + "] "
                    + "type are not equals [" + typeA + "|" + typeB + "] !");
            }
            // Take care of floating-point precision:
            if (!equalsArrayEps(coordsA, coordsB, getLength(typeA))) {
                throw new IllegalStateException("Path-segment[" + n + "] coords"
                    + " are not equals [" + Arrays.toString(coordsA) + "|"
                    + Arrays.toString(coordsB) + "] !");
            }
        }
        if (!itA.isDone() || !itB.isDone()) {
            throw new IllegalStateException("Paths do not have same lengths !");
        }
        log("testFlattening: " + n + " segments.");
    }

    static void testAddMove(Path2D pathA) {
        addMove(pathA);
        log("testAddMove: passed.");
    }

    static void testAddLine(Path2D pathA, boolean isEmpty) {
        try {
            addLines(pathA);
        }
        catch (IllegalPathStateException ipse) {
            if (isEmpty) {
                log("testAddLine: passed "
                    + "(expected IllegalPathStateException catched).");
                return;
            } else {
                throw ipse;
            }
        }
        if (isEmpty) {
            throw new IllegalStateException("IllegalPathStateException not thrown !");
        }
        log("testAddLine: passed.");
    }

    static void testAddQuad(Path2D pathA, boolean isEmpty) {
        try {
            addQuads(pathA);
        }
        catch (IllegalPathStateException ipse) {
            if (isEmpty) {
                log("testAddQuad: passed "
                    + "(expected IllegalPathStateException catched).");
                return;
            } else {
                throw ipse;
            }
        }
        if (isEmpty) {
            throw new IllegalStateException("IllegalPathStateException not thrown !");
        }
        log("testAddQuad: passed.");
    }

    static void testAddCubic(Path2D pathA, boolean isEmpty) {
        try {
            addCubics(pathA);
        }
        catch (IllegalPathStateException ipse) {
            if (isEmpty) {
                log("testAddCubic: passed "
                    + "(expected IllegalPathStateException catched).");
                return;
            } else {
                throw ipse;
            }
        }
        if (isEmpty) {
            throw new IllegalStateException("IllegalPathStateException not thrown !");
        }
        log("testAddCubic: passed.");
    }

    static void testAddClose(Path2D pathA, boolean isEmpty) {
        try {
            addClose(pathA);
        }
        catch (IllegalPathStateException ipse) {
            if (isEmpty) {
                log("testAddClose: passed "
                    + "(expected IllegalPathStateException catched).");
                return;
            } else {
                throw ipse;
            }
        }
        if (isEmpty) {
            throw new IllegalStateException("IllegalPathStateException not thrown !");
        }
        log("testAddClose: passed.");
    }

    static void testGetBounds(Path2D pathA, Path2D pathB) {
        final Rectangle rA = pathA.getBounds();
        final Rectangle rB = pathB.getBounds();

        if (!rA.equals(rB)) {
            throw new IllegalStateException("Bounds are not equals [" + rA
                + "|" + rB + "] !");
        }
        final Rectangle2D r2dA = pathA.getBounds2D();
        final Rectangle2D r2dB = pathB.getBounds2D();

        if (!equalsRectangle2D(r2dA, r2dB)) {
            throw new IllegalStateException("Bounds2D are not equals ["
                + r2dA + "|" + r2dB + "] !");
        }
        log("testGetBounds: passed.");
    }

    static void testTransform(Path2D pathA) {
        pathA.transform(at);
        log("testTransform: passed.");
    }

    static void testIntersect(Path2D pathA, Path2D pathB) {
        boolean resA = pathA.intersects(rect2d);
        boolean resB = pathB.intersects(rect2d);
        if (resA != resB) {
            throw new IllegalStateException("Intersects(rect2d) are not equals ["
                + resA + "|" + resB + "] !");
        }
        resA = pathA.intersects(1.0, 2.0, 13.0, 17.0);
        resB = pathB.intersects(1.0, 2.0, 13.0, 17.0);
        if (resA != resB) {
            throw new IllegalStateException("Intersects(doubles) are not equals ["
                + resA + "|" + resB + "] !");
        }
        log("testIntersect: passed.");
    }

    static void testContains(Path2D pathA, Path2D pathB) {
        boolean resA = pathA.contains(pt2d);
        boolean resB = pathB.contains(pt2d);
        if (resA != resB) {
            throw new IllegalStateException("Contains(pt) are not equals ["
                + resA + "|" + resB + "] !");
        }
        resA = pathA.contains(pt2d.getX(), pt2d.getY());
        resB = pathB.contains(pt2d.getX(), pt2d.getY());
        if (resA != resB) {
            throw new IllegalStateException("Contains(x,y) are not equals ["
                + resA + "|" + resB + "] !");
        }
        resA = pathA.contains(rect2d);
        resB = pathB.contains(rect2d);
        if (resA != resB) {
            throw new IllegalStateException("Contains(rect2d) are not equals ["
                + resA + "|" + resB + "] !");
        }
        resA = pathA.contains(1.0, 2.0, 13.0, 17.0);
        resB = pathB.contains(1.0, 2.0, 13.0, 17.0);
        if (resA != resB) {
            throw new IllegalStateException("Contains(doubles) are not equals ["
                + resA + "|" + resB + "] !");
        }
        log("testContains: passed.");
    }

    static void testGetCurrentPoint(Path2D pathA, Path2D pathB) {
        final Point2D ptA = pathA.getCurrentPoint();
        final Point2D ptB = pathA.getCurrentPoint();
        if (((ptA == null) && (ptB != null))
            || ((ptA != null) && !ptA.equals(ptB)))
        {
            throw new IllegalStateException("getCurrentPoint() are not equals ["
                + ptA + "|" + ptB + "] !");
        }
        log("testGetCurrentPoint: passed.");
    }

    static int getLength(int type) {
        switch(type) {
            case PathIterator.SEG_CUBICTO:
                return 6;
            case PathIterator.SEG_QUADTO:
                return 4;
            case PathIterator.SEG_LINETO:
            case PathIterator.SEG_MOVETO:
                return 2;
            case PathIterator.SEG_CLOSE:
                return 0;
            default:
                throw new IllegalStateException("Invalid type: " + type);
        }
    }


    // Custom equals methods ---

    public static boolean equalsArray(float[] a, float[] a2, final int len) {
        for (int i = 0; i < len; i++) {
            if (Float.floatToIntBits(a[i]) != Float.floatToIntBits(a2[i])) {
                return false;
            }
        }
        return true;
    }

    static boolean equalsArrayEps(float[] a, float[] a2, final int len) {
        for (int i = 0; i < len; i++) {
            if (!equalsEps(a[i], a2[i])) {
                return false;
            }
        }

        return true;
    }

    static boolean equalsRectangle2D(Rectangle2D a, Rectangle2D b) {
        if (a == b) {
            return true;
        }
        return equalsEps(a.getX(), b.getX())
            && equalsEps(a.getY(), b.getY())
            && equalsEps(a.getWidth(), b.getWidth())
            && equalsEps(a.getHeight(), b.getHeight());
    }

    static boolean equalsEps(float a, float b) {
        return (Math.abs(a - b) <= EPSILON);
    }

    static boolean equalsEps(double a, double b) {
        return (Math.abs(a - b) <= EPSILON);
    }
}