/*
 * Copyright (c) 2001, 2023, 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
  @summary unit test for ability of FocusTraversalPolicyProvider
  @key headful
*/

import java.awt.Button;
import java.awt.Component;
import java.awt.Container;
import java.awt.ContainerOrderFocusTraversalPolicy;
import java.awt.DefaultFocusTraversalPolicy;
import java.awt.EventQueue;
import java.awt.FocusTraversalPolicy;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Panel;
import java.awt.Robot;
import java.awt.Window;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.LayoutFocusTraversalPolicy;

public class FocusTraversalPolicyProviderTest {
    final String errorOrderMessage = "Test Failed. Traversal Order not correct.";
    final String successStage = "Test stage completed.Passed.";

    final int n_buttons = 4;
    final int jumps = 3 * n_buttons;
    Container[] cycle_roots = new Container[3];
    Panel[] a_conts = new Panel[cycle_roots.length];
    Panel[] b_conts = new Panel[cycle_roots.length];
    Component[][] a_buttons = new Component[cycle_roots.length][n_buttons];
    Component[][] b_buttons = new Component[cycle_roots.length][n_buttons];

    static volatile Frame mainFrame = null;
    static volatile Frame frame = null;
    static volatile JFrame jframe = null;

    static Robot robot;

    public static void main(String[] args) throws Exception {
        FocusTraversalPolicyProviderTest test
                = new FocusTraversalPolicyProviderTest();
        try {
            robot = new Robot();
            EventQueue.invokeAndWait(test::init);
            robot.delay(1000);
            EventQueue.invokeAndWait(test::testStages);

            EventQueue.invokeAndWait(test::initSwingContInFrame);
            robot.delay(1000);
            EventQueue.invokeAndWait(test::testSwingContInFrame);
            // test for Swing container in java.awt.Frame
            System.out.println("Test passed.");
        } finally {
            EventQueue.invokeAndWait(() -> {
                if (mainFrame != null) mainFrame.dispose();
                if (frame != null) frame.dispose();
                if (jframe != null) jframe.dispose();
            });
        }
    }

    public void init() {
        mainFrame = new Frame("FocusTraversalPolicyProviderTest - main");
        mainFrame.setSize(400, 400);
        mainFrame.setLocationRelativeTo(null);
        mainFrame.setVisible(true);

        for (int i = 0; i < cycle_roots.length; i++) {
            cycle_roots[i] = new Panel();
            cycle_roots[i].setFocusable(false);
            cycle_roots[i].setName("root" + i);
            cycle_roots[i].setFocusCycleRoot(true);
            cycle_roots[i].setLayout (new GridLayout(1, 2));
            mainFrame.add(cycle_roots[i]);

            a_conts[i] = new Panel();
            a_conts[i].setName("ac" + i);
            a_conts[i].setFocusable(false);
            cycle_roots[i].add(a_conts[i]);

            b_conts[i] = new Panel();
            b_conts[i].setName("bc" + i);
            b_conts[i].setFocusable(false);
            cycle_roots[i].add(b_conts[i]);

            for (int j = 0; j < n_buttons; j++){
                String name = "a" + i + "x" + j;
                a_buttons[i][j] = new Button(name);
                a_buttons[i][j].setName(name);
                a_conts[i].add(a_buttons[i][j]);
            }

            for (int j = 0; j < n_buttons; j++){
                String name = "b" + i + "x" + j;
                b_buttons[i][j] = new Button(name);
                b_buttons[i][j].setName(name);
                b_conts[i].add(b_buttons[i][j]);
            }
        }

        cycle_roots[0].setFocusTraversalPolicy(new DefaultFocusTraversalPolicy());
        cycle_roots[1].setFocusTraversalPolicy(new ContainerOrderFocusTraversalPolicy());
        cycle_roots[2].setFocusTraversalPolicy(new LayoutFocusTraversalPolicy());
    }

    public void testStages() {
        for (int i = 0; i < cycle_roots.length; i++) {
            testStage(cycle_roots[i], a_conts[i], b_conts[i],
                    a_buttons[i], b_buttons[i]);
        }
    }

    void testStage(Container aFCR, Container aCont, Container bCont,
                   Component[] a_comps, Component[] b_comps) {
            System.out.println("focus cycle root = " + aFCR.getName());
            System.out.println("policy = " + aFCR.getFocusTraversalPolicy());
            System.out.println("aContainer = " + aCont.getName());
            System.out.println("bContainer = " + bCont.getName());
            System.out.println("Both containers are not Providers.");

            Component[] a_comps_backward = revertArray(a_comps);
            Component[] b_comps_backward = revertArray(b_comps);

            testForwardStage(aFCR, aCont, bCont,
                    false, a_comps, false, b_comps);
            testBackwardStage(aFCR, aCont, bCont,
                    false, a_comps_backward,
                    false, b_comps_backward);

            System.out.println("Both containers are Providers.");
            testForwardStage(aFCR, aCont, bCont,
                    true, a_comps, true, b_comps);
            testForwardStage(aFCR, aCont, bCont,
                             true, shakeArray(a_comps),
                             true, shakeArray(b_comps));

            testBackwardStage(aFCR, aCont, bCont,
                              true, a_comps_backward,
                    true, b_comps_backward);
            testBackwardStage(aFCR, aCont, bCont,
                              true, shakeArray(a_comps_backward),
                              true, shakeArray(b_comps_backward));

            System.out.println("aContainer.isProvider = true. "
                    + "bContainer.isProvider = false.");
            testForwardStage(aFCR, aCont, bCont,
                             true, a_comps, false, b_comps);
            testForwardStage(aFCR, aCont, bCont,
                             true, shakeArray(a_comps),
                    false, b_comps);
            testBackwardStage(aFCR, aCont, bCont,
                              true, a_comps_backward,
                    false, b_comps_backward);
            testBackwardStage(aFCR, aCont, bCont,
                              true, shakeArray(a_comps_backward),
                              false, b_comps_backward);

            System.out.println("aContainer.isProvider = false. "
                    + "bContainer.isProvider = true.");
            testForwardStage(aFCR, aCont, bCont,
                             false, a_comps,
                    true, b_comps);
            testForwardStage(aFCR, aCont, bCont,
                             false, a_comps,
                    true, shakeArray(b_comps));
            testBackwardStage(aFCR, aCont, bCont,
                              false, a_comps_backward,
                    true, b_comps_backward);
            testBackwardStage(aFCR, aCont, bCont,
                              false, a_comps_backward,
                              true, shakeArray(b_comps_backward));

            System.out.println("Stage completed.");
    }

    public void printGoldOrder(Component[] comps) {
        String goldOrderStr = "";
        for (int i =0;i < jumps; i++){
            goldOrderStr += " " + comps[i].getName();
        }
        System.out.println("GoldOrder: " + goldOrderStr);
    }

    public void testForwardStage(Container focusCycleRoot,
                                 Container aContainer,
                                 Container bContainer,
                                 boolean aProvider, Component[] aComps,
                                 boolean bProvider, Component[] bComps) {
        System.out.println("test forward traversal");
        System.out.println("\taProvider = " + aProvider);
        System.out.println("\tbProvider = " + bProvider);
        Component[] goldOrder = new Component[2*aComps.length + bComps.length];
        System.arraycopy(aComps, 0, goldOrder, 0, aComps.length);
        System.arraycopy(bComps, 0, goldOrder,
                aComps.length, bComps.length);
        System.arraycopy(aComps, 0, goldOrder,
                aComps.length + bComps.length,
                         aComps.length);
        printGoldOrder(goldOrder);

        String jumpStr = "";
        aContainer.setFocusTraversalPolicyProvider(aProvider);
        aContainer.setFocusTraversalPolicy(
            new ArrayOrderFocusTraversalPolicy(aContainer, aComps));
        bContainer.setFocusTraversalPolicyProvider(bProvider);
        bContainer.setFocusTraversalPolicy(
            new ArrayOrderFocusTraversalPolicy(bContainer, bComps));
        FocusTraversalPolicy policy = focusCycleRoot.getFocusTraversalPolicy();
        System.out.println("policy=" + policy);
        Component current = policy.getFirstComponent(focusCycleRoot);

        for (int i = 0;i<jumps;i++){
            jumpStr += " " + current.getName();
            if (current != goldOrder[i]) {
                System.out.println("i=" + i + " label = "+ current.getName()
                               + " i%8= " + i%goldOrder.length );
                throw new RuntimeException(errorOrderMessage);
            }
            System.out.println("getComponentAfter() on " + focusCycleRoot + ", " + current);
            current = policy.getComponentAfter(focusCycleRoot, current);
            System.out.println("RealOrder :" + jumpStr);
        }
        System.out.println(successStage);
    }

    public void testBackwardStage(Container focusCycleRoot,
                                  Container aContainer,
                                  Container bContainer,
                                  boolean aProvider, Component[] aComps,
                                  boolean bProvider, Component[] bComps)
    {
        System.out.println("test backward traversal");
        System.out.println("\taProvider = " + aProvider);
        System.out.println("\tbProvider = " + bProvider);
        Component[] goldOrder = new Component[2*bComps.length + bComps.length];
        System.arraycopy(bComps, 0, goldOrder, 0, bComps.length);
        System.arraycopy(aComps, 0, goldOrder, bComps.length, aComps.length);
        System.arraycopy(bComps, 0, goldOrder,
                aComps.length + bComps.length, bComps.length);
        printGoldOrder(goldOrder);

        String jumpStr = "";
        aContainer.setFocusTraversalPolicyProvider(aProvider);
        aContainer.setFocusTraversalPolicy(
            new ArrayOrderFocusTraversalPolicy(aContainer, revertArray(aComps)));
        bContainer.setFocusTraversalPolicyProvider(bProvider);
        bContainer.setFocusTraversalPolicy(
            new ArrayOrderFocusTraversalPolicy(bContainer, revertArray(bComps)));
        FocusTraversalPolicy policy = focusCycleRoot.getFocusTraversalPolicy();
        System.out.println("policy=" + policy);
        Component current = policy.getLastComponent(focusCycleRoot);

        for (int i = 0;i<jumps;i++){
            jumpStr += " " + current.getName();
            if (current != goldOrder[i]) {
                System.out.println("i=" + i + " label = "+ current.getName());
                throw new RuntimeException(errorOrderMessage);
            }
            System.out.println("getComponentBefore() on "
                    + focusCycleRoot.getName() + ", " + current.getName());
            current = policy.getComponentBefore(focusCycleRoot, current);
            System.out.println("RealOrder :" + jumpStr);
        }
        System.out.println(successStage);
    }

    Component[] shakeArray(Component[] comps) {
        Component[] new_comps = new Component[comps.length];
        System.arraycopy(comps, 0, new_comps, 0, comps.length);
        new_comps[0] = comps[1];
        new_comps[1] = comps[0];
        return new_comps;
    }

    Component[] revertArray(Component[] comps) {
        Component[] new_comps = new Component[comps.length];
        for (int i=0; i < comps.length; i++) {
            new_comps[i] = comps[comps.length - 1 - i];
        }

        return new_comps;
    }

    public void initSwingContInFrame()  {
        System.out.println("test Swing policy provider in AWT Frame.");

        jframe = new JFrame("FocusTraversalPolicyProviderTest - JFrame");
        jframe.setName("JFrame");
        JPanel panel1 = createPanel();
        jframe.getContentPane().add(panel1);

        frame = new Frame("FocusTraversalPolicyProviderTest - Frame");
        frame.setName("Frame");
        JPanel panel2 = createPanel();
        panel2.setFocusTraversalPolicyProvider(true);
        panel2.setFocusTraversalPolicy(jframe.getFocusTraversalPolicy());
        frame.add(panel2);

        jframe.pack();
        jframe.setVisible(true);
        frame.pack();
        frame.setVisible(true);
    }

    public void testSwingContInFrame() {
        FocusTraversalPolicy policy = frame.getFocusTraversalPolicy();
        FocusTraversalPolicy jpolicy = jframe.getFocusTraversalPolicy();

        System.out.println("policy = " + policy);
        System.out.println("jpolicy = " + jpolicy);

        assertEquals("Different default components.",
                jpolicy.getDefaultComponent(jframe),
                policy.getDefaultComponent(frame));

        assertEquals("Different first components.",
                jpolicy.getFirstComponent(jframe),
                policy.getFirstComponent(frame));

        assertEquals("Different last components.",
                jpolicy.getLastComponent(jframe),
                policy.getLastComponent(frame));

        System.out.println("test forward traversal order.");

        Component jcur = jpolicy.getFirstComponent(jframe);
        Component cur = jpolicy.getFirstComponent(frame);

        for (int i = 0; i < 2 * n_buttons; i++) {
            assertEquals("Wrong sequence (step=" + i + ")",
                    jcur, cur);
            jcur = jpolicy.getComponentAfter(jframe, jcur);
            cur = policy.getComponentAfter(frame, cur);
        }

        System.out.println("test backward traversal order.");

        jcur = jpolicy.getLastComponent(jframe);
        cur = jpolicy.getLastComponent(frame);

        for (int i = 0; i < 2 * n_buttons; i++) {
            assertEquals("Wrong sequence (step=" + i + ")",
                    jcur, cur);
            jcur = jpolicy.getComponentBefore(jframe, jcur);
            cur = policy.getComponentBefore(frame, cur);
        }
    }

    public void assertEquals(String msg, Component expected, Component actual) {
        if (expected == null && actual != null
            || actual == null && expected != null)
        {
            throw new RuntimeException(msg + "(expected=" + expected
                                       + ", actual=" + actual + ")");
        }

        String expected_name = expected.getName();
        String actual_name = actual.getName();

        if ((expected_name != null && !expected_name.equals(actual_name))
            || (actual_name != null && !actual_name.equals(expected_name)))
        {
            throw new RuntimeException(msg + "(expected_name=" + expected_name
                                       + ", actual_name=" + actual_name + ")");
        }
    }

    public JPanel createPanel() {
        JPanel pane = new JPanel();
        pane.setName("jpanel");
        for (int i = 0; i < n_buttons; i++) {
            JButton btn = new JButton("jbtn" + i);
            btn.setName("jbtn" + i);
            pane.add(btn);
        }
        return pane;
    }
}

class ArrayOrderFocusTraversalPolicy extends FocusTraversalPolicy {

    final Component[] comps;
    final Container cont;

    public ArrayOrderFocusTraversalPolicy(Container aCont, Component[] aComps) {
        if (aCont == null) {
            throw new NullPointerException("aCont is null.");
        }
        cont = aCont;
        comps = new Component[aComps.length];
        for (int i = 0; i < comps.length; i++) {
            comps[i] = aComps[i];
        }
    }

    private void checkContainer(Container aCont) {
        if (aCont != cont) {
            System.err.println("aCont = " + aCont);
            System.err.println("cont = " + cont);
            throw new IllegalArgumentException(
                    "Policy is not registered for this container.");
        }
    }

    private int findIndex(Component aComp) {
        for (int i = 0; i < comps.length; i++) {
            if (aComp == comps[i]) {
                return i;
            }
        }

        return -1;
    }

    public Component getComponentAfter(Container focusCycleRoot,
                                       Component aComponent) {
        checkContainer(focusCycleRoot);

        int current_index = findIndex(aComponent);
        if (current_index < 0) {
            return null;
        }

        current_index++;

        if (current_index < comps.length) {
            return comps[current_index];
        }

        if (focusCycleRoot.isFocusCycleRoot()) {
            return getFirstComponent(focusCycleRoot);
        } else {
            return null;
        }
    }

    public Component getComponentBefore(Container focusCycleRoot,
                                        Component aComponent) {
        checkContainer(focusCycleRoot);

        int current_index = findIndex(aComponent);
        if (current_index < 0) {
            return null;
        }

        current_index--;

        if (current_index >= 0) {
            return comps[current_index];
        }

        if (focusCycleRoot.isFocusCycleRoot()) {
            return getLastComponent(focusCycleRoot);
        } else {
            return null;
        }
    }

    public Component getFirstComponent(Container focusCycleRoot) {
        checkContainer(focusCycleRoot);
        return comps[0];
    }

    public Component getLastComponent(Container focusCycleRoot) {
        checkContainer(focusCycleRoot);
        return comps[comps.length - 1];
    }

    public Component getDefaultComponent(Container focusCycleRoot) {
        return getFirstComponent(focusCycleRoot);
    }

    public Component getInitialComponent(Window window) {
        throw new UnsupportedOperationException("getInitialComponent() is not supported.");
    }

    public Component[] getCycle(Container focusCycleRoot) {
        checkContainer(focusCycleRoot);
        Component[] temp = new Component[comps.length];
        System.arraycopy(comps, 0, temp, 0, comps.length);
        return temp;
    }
}