/*
 * Copyright (c) 2020, 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
 * @key headful
 * @bug 8249548 8259237
 * @summary Test focus traversal in button group containing JToggleButton
 * and JRadioButton
 * @run main TestButtonGroupFocusTraversal
 */

import javax.swing.AbstractAction;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import java.awt.Component;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.ListIterator;

public class TestButtonGroupFocusTraversal {
    private static JFrame frame;
    private static JTextField textFieldFirst, textFieldLast;
    private static JToggleButton toggleButton1, toggleButton2;
    private static JCheckBox checkBox1, checkBox2;
    private static boolean toggleButtonActionPerformed;
    private static boolean checkboxActionPerformed;
    private static JRadioButton radioButton1, radioButton2;
    private static Robot robot;

    private static void blockTillDisplayed(Component comp) {
        Point p = null;
        while (p == null) {
            try {
                p = comp.getLocationOnScreen();
            } catch (IllegalStateException e) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ie) {
                }
            }
        }
    }

    private static void createUI() throws Exception {
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                textFieldFirst = new JTextField("First");
                textFieldLast = new JTextField("Last");
                toggleButton1 = new JToggleButton("1");
                toggleButton2 = new JToggleButton("2");
                radioButton1 = new JRadioButton("1");
                radioButton2 = new JRadioButton("2");
                checkBox1 = new JCheckBox("1");
                checkBox2 = new JCheckBox("2");

                if (useActionListenerOnToggle) {
                    toggleButton1.addActionListener(new AbstractAction() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                        }
                    });

                    toggleButton2.addActionListener(new AbstractAction() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            toggleButtonActionPerformed = true;
                        }
                    });
                } else {
                    toggleButton1.setAction(new AbstractAction() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                        }
                    });

                    toggleButton2.setAction(new AbstractAction() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            toggleButtonActionPerformed = true;
                        }
                    });
                }

                checkBox1.setAction(new AbstractAction() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        checkboxActionPerformed = true;
                    }
                });

                checkBox2.setAction(new AbstractAction() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        checkboxActionPerformed = true;
                    }
                });

                ButtonGroup toggleGroup = new ButtonGroup();
                toggleGroup.add(toggleButton1);
                toggleGroup.add(toggleButton2);

                ButtonGroup radioGroup = new ButtonGroup();
                radioGroup.add(radioButton1);
                radioGroup.add(radioButton2);

                ButtonGroup checkboxButtonGroup = new ButtonGroup();
                checkboxButtonGroup.add(checkBox1);
                checkboxButtonGroup.add(checkBox2);

                toggleButton2.setSelected(true);
                radioButton2.setSelected(true);
                checkBox2.setSelected(true);

                frame = new JFrame("Test");
                frame.setLayout(new FlowLayout());

                Container pane = frame.getContentPane();
                pane.add(textFieldFirst);
                pane.add(toggleButton1);
                pane.add(toggleButton2);
                pane.add(radioButton1);
                pane.add(radioButton2);
                pane.add(checkBox1);
                pane.add(checkBox2);
                pane.add(textFieldLast);

                frame.pack();
                frame.setAlwaysOnTop(true);
                frame.setLocationRelativeTo(null);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
            }
        });
    }

    private static boolean useActionListenerOnToggle = false;

    public static void main(String[] args) throws Exception {
        System.out.println(args.length);
        if (args.length >= 1) {
            useActionListenerOnToggle = Integer.parseInt(args[0]) != 0;
        }

        robot = new Robot();
        robot.setAutoDelay(100);

        UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
        for (UIManager.LookAndFeelInfo info : infos) {
            if (!
//                    info.getName().contains("Metal")
//                    info.getName().contains("Nimbus")
//                    info.getName().contains("GTK")
                    info.getName().contains("Motif")
            ) {
                continue;
            }
            UIManager.setLookAndFeel(info.getClassName());
            System.out.println();
            System.out.println(info.getClassName() + "\n");
            try {
                createUI();

                robot.waitForIdle();
                robot.delay(500);

                LayoutComparator layoutComparator = new LayoutComparator();

                layoutComparator.horizontal = true;
                layoutComparator.leftToRight = true;

                int compare = layoutComparator.compare(toggleButton1, radioButton1);

                System.out.println("RESULT: " + compare);

            } finally {
                if (frame != null) {
//                    SwingUtilities.invokeAndWait(frame::dispose);
                }
            }
        }
    }

    final static class LayoutComparator implements Comparator<Component>, java.io.Serializable {

        private static final int ROW_TOLERANCE = 10;

        private boolean horizontal = true;
        private boolean leftToRight = true;

        public int compare(Component a, Component b) {
            System.out.printf("\n\nComparing %s to %s\n\n", a.getClass().getName(), b.getClass().getName());
            System.out.println(a);
            System.out.printf("\tloc %s\n\n", a.getLocation());
            System.out.println(b);
            System.out.printf("\tloc %s\n\n", b.getLocation());


            if (a == b) {
                System.out.println("\t equal 0");
                return 0;
            }

            // Row/Column algorithm only applies to siblings. If 'a' and 'b'
            // aren't siblings, then we need to find their most inferior
            // ancestors which share a parent. Compute the ancestry lists for
            // each Component and then search from the Window down until the
            // hierarchy branches.
//            System.out.printf("\ta.getParent() %s b.getParent %s\n", a.getParent(), b.getParent());
            if (a.getParent() != b.getParent()) {
                System.out.println("\t parents are not equal");
                ArrayList<Component> aAncestory = new ArrayList<>();

                for(; a != null; a = a.getParent()) {
                    aAncestory.add(a);
                    if (a instanceof Window) {
                        break;
                    }
                }
                if (a == null) {
                    // 'a' is not part of a Window hierarchy. Can't cope.
                    throw new ClassCastException();
                }

                ArrayList<Component> bAncestory = new ArrayList<>();

                for(; b != null; b = b.getParent()) {
                    bAncestory.add(b);
                    if (b instanceof Window) {
                        break;
                    }
                }
                if (b == null) {
                    // 'b' is not part of a Window hierarchy. Can't cope.
                    throw new ClassCastException();
                }

                for (ListIterator<Component>
                     aIter = aAncestory.listIterator(aAncestory.size()),
                     bIter = bAncestory.listIterator(bAncestory.size()); ;) {
                    if (aIter.hasPrevious()) {
                        a = aIter.previous();
                    } else {
                        // a is an ancestor of b
                        System.out.println("\t a is an ancestor of b -1");
                        return -1;
                    }

                    if (bIter.hasPrevious()) {
                        b = bIter.previous();
                    } else {
                        // b is an ancestor of a
                        System.out.println("\t b is an ancestor of a 1");
                        return 1;
                    }

                    if (a != b) {
                        break;
                    }
                }
            }

            int ax = a.getX(), ay = a.getY(), bx = b.getX(), by = b.getY();
            System.out.println("\t ax = " + ax + ", ay = " + ay + ", bx = " + bx + ", by = " + by);

            int zOrder = a.getParent().getComponentZOrder(a) - b.getParent().getComponentZOrder(b);
            System.out.println("\t zOrder = " + zOrder);
            if (horizontal) {
                if (leftToRight) {

                    // LT - Western Europe (optional for Japanese, Chinese, Korean)

                    System.out.printf("\t Math.abs(ay - by) < ROW_TOLERANCE Math.abs(%d - %d) = %d < %d = %s%n",
                            ay, by, Math.abs(ay - by), ROW_TOLERANCE, Math.abs(ay - by) < ROW_TOLERANCE);

                    if (Math.abs(ay - by) < ROW_TOLERANCE) {
                        int ret = (ax < bx) ? -1 : ((ax > bx) ? 1 : zOrder);
                        System.out.println("\t (ax < bx) ? -1 : ((ax > bx) ? 1 : zOrder) " + ret); // 22.04
                        return ret;
                    } else {
                        int ret = (ay < by) ? -1 : 1;
                        System.out.println("\t (ay < by) ? -1 : 1 : zOrder) " + ret); // 24.04
                        return ret;
                    }
                } else { // !leftToRight
                    if (true) {
                        throw new RuntimeException("is not expected");
                    }
                    // RT - Middle East (Arabic, Hebrew)

                    if (Math.abs(ay - by) < ROW_TOLERANCE) {
                        return (ax > bx) ? -1 : ((ax < bx) ? 1 : zOrder);
                    } else {
                        return (ay < by) ? -1 : 1;
                    }
                }
            } else { // !horizontal
                if (true) {
                    throw new RuntimeException("is not expected");
                }
                if (leftToRight) {

                    // TL - Mongolian

                    if (Math.abs(ax - bx) < ROW_TOLERANCE) {
                        return (ay < by) ? -1 : ((ay > by) ? 1 : zOrder);
                    } else {
                        return (ax < bx) ? -1 : 1;
                    }
                } else { // !leftToRight

                    // TR - Japanese, Chinese, Korean

                    if (Math.abs(ax - bx) < ROW_TOLERANCE) {
                        return (ay < by) ? -1 : ((ay > by) ? 1 : zOrder);
                    } else {
                        return (ax > bx) ? -1 : 1;
                    }
                }
            }
        }
    }
}
