/*
 * Copyright (c) 2021, 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.*;
import java.awt.event.*;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.security.SecureRandom;


/*
 * @author Dmitriy Ermashov (dmitriy.ermashov@oracle.com)
 */
public abstract class Common {

    ExtendedRobot robot;
    Class<? extends Frame> windowClass;
    Frame background;
    BufferedImage foreground;
    Window window;
    Container componentsContainer;

    float opacity = 1.0f;
    static final int STATIC_STEP = 30;
    static final int STATIC_WIDTH = 25;
    static final int STATIC_BLOCKS = 30;
    static final Color BG_COLOR = Color.BLUE;
    static final Color FG_COLOR = Color.RED;
    static final int delay = 1000;
    static final SecureRandom random = new SecureRandom();
    static final int dl = 100;
    static final Class[] WINDOWS_TO_TEST = { Window.class, Frame.class, Dialog.class };

    public Common(Class windowClass, float opacity) throws Exception{
        this.opacity = opacity;
        robot = new ExtendedRobot();
        this.windowClass = windowClass;
        EventQueue.invokeAndWait(this::initBackgroundFrame);
        EventQueue.invokeAndWait(this::initGUI);
    }

    public Common(Class windowClass) throws Exception{
        this(windowClass, 1.0f);
    }

    public void doTest() throws Exception {
        robot.waitForIdle(delay);
    };

    public void dispose() {
        window.dispose();
        background.dispose();
    }

    public abstract void applyShape();

    public void applyDynamicShape() {
        final Area a = new Area();
        Dimension size = window.getSize();
        for (int x = 0; x < 3; x++) {
            for (int y = 0; y < 3; y++) {
                a.add(new Area(new Rectangle2D.Double(
                        x * size.getWidth() / 17*6, y * size.getHeight() / 17*6,
                        size.getWidth() / 17*5, size.getHeight() / 17*5)));
            }
        }
        window.setShape(a);
    }

    public void applyStaticShape() {
        final Area a = new Area();
        for (int x = 0; x < STATIC_BLOCKS; x++) {
            for (int y = 0; y < STATIC_BLOCKS; y++) {
                a.add(new Area(new Rectangle2D.Float(
                        x*STATIC_STEP, y*STATIC_STEP,
                        STATIC_WIDTH, STATIC_WIDTH)));
            }
        }
        window.setShape(a);
    }

    public BufferedImage getForegroundWindow() throws Exception {
        final BufferedImage f[] = new BufferedImage[1];
        EventQueue.invokeAndWait( () -> {
            f[0] = new BufferedImage(window.getWidth(),
                    window.getHeight(), BufferedImage.TYPE_INT_RGB);
            window.printAll(f[0].createGraphics());
        });
        robot.waitForIdle(delay);
        return f[0];
    }

    public static boolean checkTranslucencyMode(GraphicsDevice.WindowTranslucency mode) {

        if (!GraphicsEnvironment
                .getLocalGraphicsEnvironment()
                .getDefaultScreenDevice()
                .isWindowTranslucencySupported(mode)){
            System.out.println(mode+" translucency mode isn't supported");
            return false;
        } else {
            return true;
        }

    }

    public void applyAppDragNResizeSupport() {
        MouseAdapter m = new MouseAdapter() {

            private Point dragOrigin = null;
            private Dimension origSize = null;
            private Point origLoc = null;
            private boolean left = false;
            private boolean top = false;
            private boolean bottom = false;
            private boolean right = false;

            public void mousePressed(MouseEvent e) {
                dragOrigin = e.getLocationOnScreen();
                origSize = window.getSize();
                origLoc = window.getLocationOnScreen();
                right = (origLoc.x + window.getWidth() - dragOrigin.x) < 5;
                left = !right && dragOrigin.x - origLoc.x < 5;
                bottom = (origLoc.y + window.getHeight() - dragOrigin.y) < 5;
                top = !bottom && dragOrigin.y - origLoc.y < 5;
            }

            public void mouseReleased(MouseEvent e) { resize(e); }
            public void mouseDragged(MouseEvent e) { resize(e); }

            void resize(MouseEvent e) {
                Point dragDelta = e.getLocationOnScreen();
                dragDelta.translate(-dragOrigin.x, -dragOrigin.y);
                Point newLoc = new Point(origLoc);
                newLoc.translate(dragDelta.x, dragDelta.y);
                Dimension newSize = new Dimension(origSize);
                if (left || right) {
                    newSize.width += right ? dragDelta.x : -dragDelta.x;
                }
                if (top || bottom) {
                    newSize.height += bottom ? dragDelta.y : -dragDelta.y;
                }
                if (right || (top || bottom) && !left) {
                    newLoc.x = origLoc.x;
                }
                if (bottom || (left || right) && !top) {
                    newLoc.y = origLoc.y;
                }
                window.setBounds(newLoc.x, newLoc.y, newSize.width, newSize.height);
            }
        };
        for (Component comp : window.getComponents()) {
            comp.addMouseListener(m);
            comp.addMouseMotionListener(m);
        }

        window.addMouseListener(m);
        window.addMouseMotionListener(m);
    }

    public void checkTranslucentShape() throws Exception {
        foreground = getForegroundWindow();
        Point[] points = new Point[4];

        Dimension size = window.getSize();
        Point location = window.getLocationOnScreen();

        points[0] = new Point(20, 20);
        points[1] = new Point(20, size.height-20);
        points[2] = new Point(size.width-20, 20);
        points[3] = new Point(size.width-20, size.height-20);

        for (Point p : points){
            p.translate(location.x, location.y);
            Color actual = robot.getPixelColor(p.x, p.y);
            if (actual.equals(BG_COLOR)|| actual.equals(FG_COLOR))
                throw new RuntimeException("Error in point "+p+": "+actual+" equals to foreground or background color");
            else
                System.out.println("OK with foreground point "+p);
        }
    }

    public void checkStaticShape() throws Exception {
        Point[] points = new Point[4];

        Dimension size = window.getSize();
        int xFactor = (int) Math.floor(size.getWidth()/STATIC_STEP)-1;
        int yFactor = (int) Math.floor(size.getHeight()/STATIC_STEP)-1;

        // background
        points[0] = new Point((STATIC_STEP+STATIC_WIDTH)/2, (STATIC_STEP+STATIC_WIDTH)/2);
        points[1] = new Point(STATIC_STEP*xFactor+(STATIC_STEP+STATIC_WIDTH)/2, STATIC_STEP*yFactor+(STATIC_STEP+STATIC_WIDTH)/2);
        points[2] = new Point((STATIC_STEP+STATIC_WIDTH)/2, STATIC_STEP*yFactor+(STATIC_STEP+STATIC_WIDTH)/2);
        points[3] = new Point(STATIC_STEP*xFactor+(STATIC_STEP+STATIC_WIDTH)/2, (STATIC_STEP+STATIC_WIDTH)/2);
        checkShape(points, true);

        // foreground
        if (opacity < 1.0f){
            checkTranslucentShape();
        } else {
            points[0] = new Point((STATIC_WIDTH) / 2, (STATIC_WIDTH) / 2);
            points[1] = new Point(STATIC_STEP * xFactor + (STATIC_WIDTH) / 2, STATIC_STEP * yFactor + (STATIC_WIDTH) / 2);
            points[2] = new Point((STATIC_WIDTH) / 2, STATIC_STEP * yFactor + (STATIC_WIDTH) / 2);
            points[3] = new Point(STATIC_STEP * xFactor + (STATIC_WIDTH) / 2, (STATIC_WIDTH) / 2);
            checkShape(points, false);
        }
    }

    public void checkDynamicShape() throws Exception {
        Point[] points = new Point[4];

        Dimension size = window.getSize();

        int blockSizeX = (int) (size.getWidth() / 17);
        int blockSizeY = (int) (size.getHeight() / 17);

        // background
        points[0] = new Point((int) (blockSizeX * 5.5), (int) (blockSizeY * 5.5));
        points[1] = new Point((int) (size.getWidth() - blockSizeX * 5.5), (int) (size.getHeight() - blockSizeY * 5.5));
        points[2] = new Point((int) (blockSizeX * 5.5), (int) (size.getHeight() - blockSizeY * 5.5));
        points[3] = new Point((int) (size.getWidth() - blockSizeX * 5.5), (int) (blockSizeY * 5.5));
        checkShape(points, true);

        // foreground
        if (opacity < 1.0f){
            checkTranslucentShape();
        } else {
            points[0] = new Point(3 * blockSizeX, 3 * blockSizeY);
            points[1] = new Point(14 * blockSizeX, 14 * blockSizeY);
            points[2] = new Point(3 * blockSizeX, 14 * blockSizeY);
            points[3] = new Point(14 * blockSizeX, 3 * blockSizeY);
            checkShape(points, false);
        }
    }

    public void checkShape(Point[] points, boolean areBackgroundPoints) throws Exception {

        Point location = window.getLocationOnScreen();

        for (Point p : points) {
            p.translate(location.x, location.y);
            if (areBackgroundPoints) {
                if (!robot.getPixelColor(p.x, p.y).equals(BG_COLOR))
                    throw new RuntimeException("Background point " + p + " color " + robot.getPixelColor(p.x, p.y) +
                            " does not equal to background color " + BG_COLOR);
                else
                    System.out.println("OK with background point " + p);
            } else {
                if (robot.getPixelColor(p.x, p.y).equals(BG_COLOR))
                    throw new RuntimeException("Foreground point " + p +
                            " equals to background color " + BG_COLOR);
                else
                    System.out.println("OK with foreground point " + p);
            }
        }
    }

    public void initBackgroundFrame() {
        background = new Frame();
        background.setUndecorated(true);
        background.setBackground(BG_COLOR);
        background.setSize(500, 500);
        background.setLocation(dl, dl);
        background.setVisible(true);
    }

    public void initGUI() {
        if (windowClass.equals(Frame.class)) {
            window = new Frame();
            ((Frame) window).setUndecorated(true);
        } else  if (windowClass.equals(Dialog.class)) {
            window = new Dialog(background);
            ((Dialog) window).setUndecorated(true);
        } else {
            window = new Window(background);
        }

        window.setBackground(FG_COLOR);
        componentsContainer = new Panel();
        window.add(componentsContainer, BorderLayout.CENTER);
        window.setLocation(2 * dl, 2 * dl);
        window.setSize(255, 255);
        if (opacity < 1.0f)
            window.setOpacity(opacity);
        window.addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent e) {
                applyShape();
            }
        });
        applyShape();
        window.setVisible(true);
        applyAppDragNResizeSupport();
        window.toFront();
    }
}