/*
 * 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.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Path2D;
import static java.awt.geom.Path2D.WIND_NON_ZERO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

/**
 * @test
 * @summary Simple crash rendering test using huge GeneralPaths with the Marlin renderer
 * @run main/othervm -mx512m CrashTest
 * @ignore tests that take a long time and consumes 5Gb memory
 * @run main/othervm -ms4g -mx4g CrashTest -slow
*/
public class CrashTest {

    static final boolean SAVE_IMAGE = false;
    static boolean USE_ROUND_CAPS_AND_JOINS = true;

    public static void main(String[] args) {
        boolean runSlowTests = (args.length != 0 && "-slow".equals(args[0]));

        // First display which renderer is tested:
        System.setProperty("sun.java2d.renderer.verbose", "true");

        // try insane image sizes:

        // subpixel coords may overflow:
        // check MAX_VALUE / (8 * 2); overflow may happen due to orientation flag
        // But as it is impossible to allocate an image larger than 2Gb (byte) then
        // it is also impossible to have rowAAChunk larger than 2Gb !

        // Disabled test as it consumes 4GB heap + offheap (2Gb) ie > 6Gb !
        if (runSlowTests) {
            testHugeImage((Integer.MAX_VALUE >> 4) - 100, 16);
        }

        // larger than 23 bits: (RLE)
        testHugeImage(8388608 + 1, 10);

        if (runSlowTests) {
            test(0.1f, false, 0);
            test(0.1f, true, 7f);
        }

        // Exceed 2Gb OffHeap buffer for edges:
        try {
            USE_ROUND_CAPS_AND_JOINS = true;
            test(0.1f, true, 0.1f);
            System.out.println("Exception MISSING.");
        }
        catch (Throwable th) {
            if (th instanceof ArrayIndexOutOfBoundsException) {
                System.out.println("ArrayIndexOutOfBoundsException expected.");
            } else {
                throw new RuntimeException("Unexpected exception", th);
            }
        }
    }

    private static void test(final float lineStroke,
                             final boolean useDashes,
                             final float dashMinLen)
        throws ArrayIndexOutOfBoundsException
    {
        System.out.println("---\n" + "test: "
            + "lineStroke=" + lineStroke
            + ", useDashes=" + useDashes
            +", dashMinLen=" + dashMinLen
        );

        final BasicStroke stroke = createStroke(lineStroke, useDashes, dashMinLen);

        // TODO: test Dasher.firstSegmentsBuffer resizing ?
// array.dasher.firstSegmentsBuffer.d_float[2] sum: 6 avg: 3.0 [3 | 3]
        /*
         // Marlin growable arrays:
         = new StatLong("array.dasher.firstSegmentsBuffer.d_float");
         = new StatLong("array.stroker.polystack.curves.d_float");
         = new StatLong("array.stroker.polystack.curveTypes.d_byte");
         = new StatLong("array.marlincache.rowAAChunk.d_byte");
         = new StatLong("array.marlincache.touchedTile.int");
         = new StatLong("array.renderer.alphaline.int");
         = new StatLong("array.renderer.crossings.int");
         = new StatLong("array.renderer.aux_crossings.int");
         = new StatLong("array.renderer.edgeBuckets.int");
         = new StatLong("array.renderer.edgeBucketCounts.int");
         = new StatLong("array.renderer.edgePtrs.int");
         = new StatLong("array.renderer.aux_edgePtrs.int");
         */
        // size > 8192 (exceed both tile and buckets arrays)
        final int size = 9000;
        System.out.println("image size = " + size);

        final BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);

        final Graphics2D g2d = (Graphics2D) image.getGraphics();
        try {
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

            g2d.setClip(0, 0, size, size);
            g2d.setBackground(Color.WHITE);
            g2d.clearRect(0, 0, size, size);

            g2d.setStroke(stroke);
            g2d.setColor(Color.BLACK);

            final long start = System.nanoTime();

            paint(g2d, size - 10f);

            final long time = System.nanoTime() - start;

            System.out.println("paint: duration= " + (1e-6 * time) + " ms.");

            if (SAVE_IMAGE) {
                try {
                    final File file = new File("CrashTest-dash-" + useDashes + ".bmp");

                    System.out.println("Writing file: " + file.getAbsolutePath());
                    ImageIO.write(image, "BMP", file);
                } catch (IOException ex) {
                    System.out.println("Writing file failure:");
                    ex.printStackTrace();
                }
            }
        } finally {
            g2d.dispose();
        }
    }

    private static void testHugeImage(final int width, final int height)
        throws ArrayIndexOutOfBoundsException
    {
        System.out.println("---\n" + "testHugeImage: "
            + "width=" + width + ", height=" + height);

        final BasicStroke stroke = createStroke(2.5f, false, 0);

        // size > 24bits (exceed both tile and buckets arrays)
        System.out.println("image size = " + width + " x "+height);

        final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);

        final Graphics2D g2d = (Graphics2D) image.getGraphics();
        try {
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

            g2d.setBackground(Color.WHITE);
            g2d.clearRect(0, 0, width, height);

            g2d.setStroke(stroke);
            g2d.setColor(Color.BLACK);

            final Path2D.Float path = new Path2D.Float(WIND_NON_ZERO, 32);
            path.moveTo(0, 0);
            path.lineTo(width, 0);
            path.lineTo(width, height);
            path.lineTo(0, height);
            path.lineTo(0, 0);

            final long start = System.nanoTime();

            g2d.draw(path);

            final long time = System.nanoTime() - start;

            System.out.println("paint: duration= " + (1e-6 * time) + " ms.");

            if (SAVE_IMAGE) {
                try {
                    final File file = new File("CrashTest-huge-"
                        + width + "x" +height + ".bmp");

                    System.out.println("Writing file: " + file.getAbsolutePath());
                    ImageIO.write(image, "BMP", file);
                } catch (IOException ex) {
                    System.out.println("Writing file failure:");
                    ex.printStackTrace();
                }
            }
        } finally {
            g2d.dispose();
        }
    }

    private static void paint(final Graphics2D g2d, final float size) {
        final double halfSize = size / 2.0;

        final Path2D.Float path = new Path2D.Float(WIND_NON_ZERO, 32 * 1024);

        // show cross:
        path.moveTo(0, 0);
        path.lineTo(size, size);

        path.moveTo(size, 0);
        path.lineTo(0, size);

        path.moveTo(0, 0);
        path.lineTo(size, 0);

        path.moveTo(0, 0);
        path.lineTo(0, size);

        path.moveTo(0, 0);

        double r = size;

        final int ratio = 100;
        int repeats = 1;

        int n = 0;

        while (r > 1.0) {
            repeats *= ratio;

            if (repeats > 10000) {
                repeats = 10000;
            }

            for (int i = 0; i < repeats; i++) {
                path.lineTo(halfSize - 0.5 * r + i * r / repeats,
                            halfSize - 0.5 * r);
                n++;
                path.lineTo(halfSize - 0.5 * r + i * r / repeats + 0.1,
                            halfSize + 0.5 * r);
                n++;
            }

            r -= halfSize;
        }
        System.out.println("draw : " + n + " lines.");
        g2d.draw(path);
    }

    private static BasicStroke createStroke(final float width,
                                            final boolean useDashes,
                                            final float dashMinLen) {
        final float[] dashes;

        if (useDashes) {
            // huge dash array (exceed Dasher.INITIAL_ARRAY)
            dashes = new float[512];

            float cur = dashMinLen;
            float step = 0.01f;

            for (int i = 0; i < dashes.length; i += 2) {
                dashes[i] = cur;
                dashes[i + 1] = cur;
                cur += step;
            }
        } else {
            dashes = null;
        }

        if (USE_ROUND_CAPS_AND_JOINS) {
            // Use both round Caps & Joins:
            return new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 100.0f, dashes, 0.0f);
        }
        return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 100.0f, dashes, 0.0f);
    }
}