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

/*
  @test
  @bug 5089429 6982632 8145808
  @summary Checks that we don't crash if rendering operations and state
  changes are performed on a graphics context from different threads.

  @author Dmitri.Trembovetski@sun.com area=Graphics
  @run main MTGraphicsAccessTest
 */

import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.util.concurrent.atomic.AtomicInteger;

public class MTGraphicsAccessTest {

    // in seconds
    static final long STANDALONE_RUN_TIME = 20;
    static final long JTREG_RUN_TIME = 7;

    static boolean standaloneMode;
    static boolean allowExceptions = true;
    static long testRunTime;

    volatile boolean done;
    AtomicInteger stillRunning = new AtomicInteger(0);
    volatile int numexceptions;

    Graphics2D sharedGraphics;
    BufferedImage sharedBI =
            new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB);

    static final Paint colors[] = {
        Color.red,
        new Color(0x7f, 0xff, 0x00, 0x7f),
        new GradientPaint(0,  0, Color.red,
                          50, 50, new Color(0x7f, 0xff, 0x00, 0x7f)),
    };
    static final Font fonts[] = {
        new Font("Dialog", Font.PLAIN, 12),
        new Font("Dialog", Font.BOLD, 16),
        new Font("Dialog", Font.ITALIC, 18),
    };
    static final AlphaComposite comps[] = {
        AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f),
        AlphaComposite.Src,
        AlphaComposite.Xor,
        AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f),
        null,
    };
    static final Stroke strokes[] = {
        new BasicStroke(),
        new BasicStroke(0.0f),
        new BasicStroke(2.0f),
        new BasicStroke(2.0f, BasicStroke.CAP_ROUND,
                        BasicStroke.JOIN_BEVEL),
        new BasicStroke(5.0f, BasicStroke.CAP_SQUARE,
                        BasicStroke.JOIN_ROUND),
        new BasicStroke(0.0f, BasicStroke.CAP_ROUND,
                        BasicStroke.JOIN_ROUND, 0,
                        new float[]{0,6,0,6}, 0),
    };
    static final AffineTransform transforms[] = {
        new AffineTransform(),
        AffineTransform.getRotateInstance(10.0),
        AffineTransform.getShearInstance(10.0, 4.0),
        AffineTransform.getScaleInstance(1.1, 1.2),
        AffineTransform.getScaleInstance(3.0, 2.0),
    };

    public MTGraphicsAccessTest() {
        BufferedImage bi =
            new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB);
        sharedGraphics = (Graphics2D)bi.getGraphics();

        done = false;
        numexceptions = 0;

        for (int i = 0; i < (standaloneMode ? stateChangers.length : 3); i++) {
            (new TesterThread(stateChangers[i])).start();
        }
        for (int i = 0; i < (standaloneMode ? renderTests.length : 5); i++) {
            (new TesterThread(renderTests[i])).start();
        }

        mysleep(testRunTime);
        done = true;
        while (stillRunning.get() > 0) { mysleep(500); }

        if (numexceptions == 0) {
            System.err.println("Test passed");
        } else if (!allowExceptions) {
            throw new RuntimeException("Test failed with "+
                                       numexceptions+" exceptions");
        } else {
            System.err.println("Test finished with "+
                               numexceptions+" exceptions");
        }
    }

    private void mysleep(long time) {
        try {
            // add +/-5ms variance to increase randomness
            Thread.sleep(time + (long)(5 - Math.random()*10));
        } catch (InterruptedException e) {};
    }

    public static void usage(String message) {
        if (message != null) {
            System.err.println(message);
        }
        System.err.println("Usage: MTGraphicsAccessTest [-full] "+
            "[-time N/forever] [-help]");
        System.err.println(" -full: run full suite of tests "+
            "(default: limited number of tests is run)");
        System.err.println(" -time N: test duration in seconds/forever"+
            " (default: "+JTREG_RUN_TIME+"s for the short suite, "+
            STANDALONE_RUN_TIME+"s for the full suite)");
        System.err.println(" -help: print this help page");
        System.exit(1);
    }

    public static void main(String[] args) {
        boolean testRunSet = false;
        for (int i = 0; i < args.length; i++) {
            if ("-full".equals(args[i])) {
                standaloneMode = true;
                System.err.println("Running complete list of tests");
            } else if ("-noexc".equals(args[i])) {
                allowExceptions = false;
            } else if ("-time".equals(args[i])) {
                try {
                    String time = args[++i];
                    if ("forever".equals(time)) {
                        testRunTime = (Long.MAX_VALUE - 20)/1000;
                    } else {
                        testRunTime = 1000*Integer.parseInt(time);
                    }
                    testRunSet = true;
                } catch (NumberFormatException e) {
                    usage("Can't parse number of seconds: " + args[i]);
                } catch (ArrayIndexOutOfBoundsException e1) {
                    usage("Missing the 'seconds' argument for -time parameter");
                }
            } else if ("-help".equals(args[i])) {
                usage(null);
            } else {
                usage("Unknown argument:" + args[i]);
            }
        }

        if (!testRunSet) {
            testRunTime = 1000 *
                (standaloneMode ? STANDALONE_RUN_TIME : JTREG_RUN_TIME);
        }

        System.err.println("Approximate test run time: "+
             testRunTime/1000+" seconds");

        new MTGraphicsAccessTest();
    }

    class TesterThread extends Thread {
        Runnable testRunnable;

        public TesterThread(Runnable testRunnable) {
            stillRunning.incrementAndGet();
            this.testRunnable = testRunnable;
        }

        public void run() {
            try {
                while (!done) {
                    try {
                        testRunnable.run();
                        yield();
                    } catch (Throwable t) {
                        numexceptions++;
                        t.printStackTrace();
                    }
                }
            } finally {
                stillRunning.decrementAndGet();
            }
        }
    }

    final Runnable stateChangers[] = {
        new Runnable() {
            public void run() {
                sharedGraphics.setClip(10, 10, 30, 30);
                mysleep(10);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.setClip(10, 10, 30, 30);
                mysleep(10);
            }
        },
        new Runnable() {
            int c = 0;
            public void run() {
                sharedGraphics.setPaint(colors[c++ % colors.length]);
                mysleep(10);
            }
        },
        new Runnable() {
            boolean AA = false;
            public void run() {
                if (AA) {
                    sharedGraphics.setRenderingHint(
                        RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
                } else {
                    sharedGraphics.setRenderingHint(
                        RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_OFF);
                }
                AA = !AA;
                mysleep(10);
            }
        },
        new Runnable() {
            int t = 0;
            public void run() {
                sharedGraphics.setTransform(
                    transforms[t++ % transforms.length]);
                mysleep(10);
            }
        },
        new Runnable() {
            int c = 0;
            public void run() {
                AlphaComposite comp = comps[c++ % comps.length];
                if (comp == null) {
                    sharedGraphics.setXORMode(Color.green);
                } else {
                    sharedGraphics.setComposite(comp);
                }
                mysleep(10);
            }
        },
        new Runnable() {
            int s = 0;
            public void run() {
                sharedGraphics.setStroke(strokes[s++ % strokes.length]);
                mysleep(10);
            }
        },
        new Runnable() {
            int f = 0;
            public void run() {
                sharedGraphics.setFont(fonts[f++ % fonts.length]);
                mysleep(10);
            }
        },
    };

    final Runnable renderTests[] = {
        new Runnable() {
            public void run() {
                sharedGraphics.drawLine(10, 10, 30, 30);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.drawLine(10, 10, 30, 30);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.drawRect(10, 10, 30, 30);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.fillRect(10, 10, 30, 30);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.drawString("Stuff", 10, 10);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.draw3DRect(10, 10, 30, 30, true);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.drawImage(sharedBI, 10, 10, null);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.fill3DRect(10, 10, 30, 30, false);
            }
        },
        // REMIND: copyArea doesn't work when transform is set..
        //          new Runnable() {
        //              public void run() {
        //                  sharedGraphics.copyArea(10, 10, 30, 30, 20, 20);
        //              }
        //          },
        new Runnable() {
            public void run() {
                sharedGraphics.drawRoundRect(10, 10, 30, 30, 20, 20);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.fillRoundRect(10, 10, 30, 30, 20, 20);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.drawArc(10, 10, 30, 30, 0, 90);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.fillArc(10, 10, 30, 30, 0, 90);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.drawOval(10, 10, 30, 30);
            }
        },
        new Runnable() {
            public void run() {
                sharedGraphics.fillOval(10, 10, 30, 30);
            }
        }
    };
}
