/*
 * Copyright (c) 2000, 2024, 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.BorderLayout;
import java.awt.Button;
import java.awt.Canvas;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Label;
import java.awt.LayoutManager;
import java.awt.Panel;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.TextField;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;

import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;

import jtreg.SkippedException;

/*
 * @test
 * @bug 4312921
 * @key multimon
 * @library /java/awt/regtesthelpers /test/lib
 * @build PassFailJFrame
 * @summary Tests that no garbage is painted on primary screen with DGA
 * @run main/manual MultiScreenTest
 */

public class MultiScreenTest {
    static GraphicsEnvironment ge;
    static GraphicsDevice[] gs;

    public static void main(String[] args) throws Exception {
        ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        gs = ge.getScreenDevices();
        if (gs.length < 2) {
            throw new SkippedException("You have only one monitor in your system - test passed");
        }
        MultiScreenTest obj = new MultiScreenTest();
        String INSTRUCTIONS =
                "This test is to be run only on multiscreen machine. " +
                "You have " + gs.length + " monitors in your system.\n" +
                "Actively drag the DitherTest frames on the secondary screen and " +
                "if you see garbage appearing on your primary screen " +
                "test failed otherwise it passed.";;

        PassFailJFrame.builder()
                .title("MultiScreenTest Instruction")
                .instructions(INSTRUCTIONS)
                .rows((int) INSTRUCTIONS.lines().count() + 2)
                .columns(40)
                .testUI(obj::init)
                .build()
                .awaitAndCheck();
    }

    public List<JFrame> init() {
        List<JFrame> list = new ArrayList<>();
        for (int j = 0; j < gs.length; j++) {
            GraphicsConfiguration[] gc = gs[j].getConfigurations();
            if (gc.length > 0) {
                for (int i = 0; i < gc.length / 2; i++) {
                    JFrame f = new JFrame(gc[i]); //test JFrame( gc )
                    GCCanvas c = new GCCanvas(gc[i]);//test canvas( gc )
                    Rectangle gcBounds = gc[i].getBounds(); //test getBounds()
                    int xoffs = gcBounds.x;
                    int yoffs = gcBounds.y;

                    f.getContentPane().add(c);
                    f.setTitle("Screen# " + Integer.toString(j) + ", GC#" + Integer.toString(i));
                    f.setSize(300, 200);
                    f.setLocation(400 + xoffs, (i * 150) + yoffs);//test displaying in right location
                    list.add(f);

                    Frame ditherfs = new Frame("DitherTest GC#" + Integer.toString(i), gc[i]);
                    ditherfs.setLayout(new BorderLayout()); //showDitherTest
                    DitherTest ditherTest = new DitherTest(gc[i]);
                    ditherfs.add("Center", ditherTest);
                    ditherfs.setBounds(300, 200, 300, 200);
                    ditherfs.setLocation(750 + xoffs, (i * 50) + yoffs);
                    ditherfs.pack();
                    ditherfs.show();
                    ditherTest.start();
                }
            }
        }
        return list;
    }
}

class GCCanvas extends Canvas {

    GraphicsConfiguration gc;
    Rectangle bounds;
    Graphics g = this.getGraphics();
    Dimension size = getSize();

    public GCCanvas(GraphicsConfiguration gc) {
        super(gc);
        this.gc = gc;
        bounds = gc.getBounds();
    }

    public void paint( Graphics _g ) {

        Graphics2D g = (Graphics2D) _g;

        g.drawRect(0, 0, size.width-1, size.height-1);
        g.setColor(Color.lightGray);
        g.draw3DRect(1, 1, size.width-3, size.height-3, true);

        g.setColor(Color.red);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g.drawString("HELLO!", 110, 10);

        g.setColor(Color.blue);
        g.drawString("ScreenSize="+Integer.toString(bounds.width)+"X"+
                     Integer.toString(bounds.height), 10, 20);
        g.setColor(Color.green);
        g.drawString(gc.toString(), 10, 30);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);

        g.setColor(Color.orange);
        g.fillRect(40, 20, 50, 50);

        g.setColor(Color.red);
        g.drawRect(100, 20, 30, 30);

        g.setColor(Color.gray);
        g.drawLine(220, 20, 280, 40);

        g.setColor(Color.cyan);
        g.fillArc(150, 30, 30, 30, 0, 200);
    }

    public Dimension getPreferredSize(){
         return new Dimension(300, 200);
    }
}

class DitherCanvas extends Canvas {
    Image img;
    static String calcString = "Calculating...";

    GraphicsConfiguration mGC;

    public DitherCanvas(GraphicsConfiguration gc) {
        super(gc);
        mGC = gc;
    }

    public GraphicsConfiguration getGraphicsConfig() {
        return mGC;
    }

    public void paint(Graphics g) {
        int w = getSize().width;
        int h = getSize().height;
        if (img == null) {
            super.paint(g);
            g.setColor(Color.black);
            FontMetrics fm = g.getFontMetrics();
            int x = (w - fm.stringWidth(calcString)) / 2;
            int y = h / 2;
            g.drawString(calcString, x, y);
        } else {
            g.drawImage(img, 0, 0, w, h, this);
        }
    }

    public void update(Graphics g) {
        paint(g);
    }

    public Dimension getMinimumSize() {
        return new Dimension(20, 20);
    }

    public Dimension getPreferredSize() {
        return new Dimension(200, 200);
    }

    public Image getImage() {
        return img;
    }

    public void setImage(Image img) {
        this.img = img;
        paint(getGraphics());
    }
}

class DitherTest extends Panel implements Runnable {
    final static int NOOP = 0;
    final static int RED = 1;
    final static int GREEN = 2;
    final static int BLUE = 3;
    final static int ALPHA = 4;
    final static int SATURATION = 5;

    Thread runner;

    DitherControls XControls;
    DitherControls YControls;
    DitherCanvas canvas;

    public DitherTest(GraphicsConfiguration gc) {
        String xspec, yspec;
        int xvals[] = new int[2];
        int yvals[] = new int[2];

        xspec = "red";
        yspec = "blue";
        int xmethod = colormethod(xspec, xvals);
        int ymethod = colormethod(yspec, yvals);

        setLayout(new BorderLayout());
        XControls = new DitherControls(this, xvals[0], xvals[1],
                xmethod, false);
        YControls = new DitherControls(this, yvals[0], yvals[1],
                ymethod, true);
        YControls.addRenderButton();
        add("North", XControls);
        add("South", YControls);
        add("Center", canvas = new DitherCanvas(gc));
    }

    public void start() {
        runner = new Thread(this);
        runner.start();
    }

    int colormethod(String s, int vals[]) {
        int method = NOOP;

        if (s == null) {
            s = "";
        }

        String lower = s.toLowerCase();
        int len = 0;
        if (lower.startsWith("red")) {
            method = RED;
            lower = lower.substring(3);
        } else if (lower.startsWith("green")) {
            method = GREEN;
            lower = lower.substring(5);
        } else if (lower.startsWith("blue")) {
            method = BLUE;
            lower = lower.substring(4);
        } else if (lower.startsWith("alpha")) {
            method = ALPHA;
            lower = lower.substring(4);
        } else if (lower.startsWith("saturation")) {
            method = SATURATION;
            lower = lower.substring(10);
        }

        if (method == NOOP) {
            vals[0] = 0;
            vals[1] = 0;
            return method;
        }

        int begval = 0;
        int endval = 255;

        try {
            int dash = lower.indexOf('-');
            if (dash < 0) {
                begval = endval = Integer.parseInt(lower);
            } else {
                begval = Integer.parseInt(lower.substring(0, dash));
                endval = Integer.parseInt(lower.substring(dash + 1));
            }
        } catch (Exception e) {
        }

        if (begval < 0) {
            begval = 0;
        }
        if (endval < 0) {
            endval = 0;
        }
        if (begval > 255) {
            begval = 255;
        }
        if (endval > 255) {
            endval = 255;
        }

        vals[0] = begval;
        vals[1] = endval;

        return method;
    }

    void applymethod(int c[], int method, int step, int total, int vals[]) {
        if (method == NOOP)
            return;
        int val = ((total < 2)
                ? vals[0]
                : vals[0] + ((vals[1] - vals[0]) * step / (total - 1)));
        switch (method) {
            case RED:
                c[0] = val;
                break;
            case GREEN:
                c[1] = val;
                break;
            case BLUE:
                c[2] = val;
                break;
            case ALPHA:
                c[3] = val;
                break;
            case SATURATION:
                int max = Math.max(Math.max(c[0], c[1]), c[2]);
                int min = max * (255 - val) / 255;
                if (c[0] == 0) {
                    c[0] = min;
                }
                if (c[1] == 0) {
                    c[1] = min;
                }
                if (c[2] == 0) {
                    c[2] = min;
                }
                break;
        }
    }

    public void run() {
        canvas.setImage(null);  // Wipe previous image
        Image img = calculateImage();
        synchronized (this) {
            if (img != null && runner == Thread.currentThread()) {
                canvas.setImage(img);
            }
        }
    }

    /**
     * Calculates and returns the image.  Halts the calculation and returns
     * null if stopped during the calculation.
     */
    Image calculateImage() {
        Thread me = Thread.currentThread();

        int width = canvas.getSize().width;
        int height = canvas.getSize().height;
        int xvals[] = new int[2];
        int yvals[] = new int[2];
        int xmethod = XControls.getParams(xvals);
        int ymethod = YControls.getParams(yvals);
        int pixels[] = new int[width * height];
        int c[] = new int[4];
        int index = 0;

        for (int j = 0; j < height; j++) {
            for (int i = 0; i < width; i++) {
                c[0] = c[1] = c[2] = 0;
                c[3] = 255;
                if (xmethod < ymethod) {
                    applymethod(c, xmethod, i, width, xvals);
                    applymethod(c, ymethod, j, height, yvals);
                } else {
                    applymethod(c, ymethod, j, height, yvals);
                    applymethod(c, xmethod, i, width, xvals);
                }
                pixels[index++] = ((c[3] << 24) |
                        (c[0] << 16) |
                        (c[1] << 8) |
                        (c[2] << 0));

            }
            // Poll once per row to see if we've been told to stop.
            if (runner != me) {
                return null;
            }
        }

        return createImage(new MemoryImageSource(width, height,
                ColorModel.getRGBdefault(), pixels, 0, width));
    }

    public String getInfo() {
        return "An interactive demonstration of dithering.";
    }

    public String[][] getParameterInfo() {
        String[][] info = {
                {"xaxis", "{RED, GREEN, BLUE, PINK, ORANGE, MAGENTA, CYAN, WHITE, YELLOW, GRAY, DARKGRAY}",
                 "The color of the Y axis.  Default is RED."},
                {"yaxis", "{RED, GREEN, BLUE, PINK, ORANGE, MAGENTA, CYAN, WHITE, YELLOW, GRAY, DARKGRAY}",
                 "The color of the X axis.  Default is BLUE."}
        };
        return info;
    }
}

class DitherControls extends Panel implements ActionListener {
    TextField start;
    TextField end;
    Button button;
    Choice choice;
    DitherTest dt;

    static LayoutManager dcLayout = new FlowLayout(FlowLayout.CENTER, 10, 5);

    public DitherControls(DitherTest app, int s, int e, int type,
                          boolean vertical) {
        dt = app;
        setLayout(dcLayout);
        add(new Label(vertical ? "Vertical" : "Horizontal"));
        add(choice = new Choice());
        choice.addItem("Noop");
        choice.addItem("Red");
        choice.addItem("Green");
        choice.addItem("Blue");
        choice.addItem("Alpha");
        choice.addItem("Saturation");
        choice.select(type);
        add(start = new TextField(Integer.toString(s), 4));
        add(end = new TextField(Integer.toString(e), 4));
    }

    public void addRenderButton() {
        add(button = new Button("New Image"));
        button.addActionListener(this);
    }

    public int getParams(int vals[]) {
        vals[0] = Integer.parseInt(start.getText());
        vals[1] = Integer.parseInt(end.getText());
        return choice.getSelectedIndex();
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == button) {
            dt.start();
        }
    }
}
