/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.gui.menu;

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

import java.beans.PropertyChangeEvent;

import javax.swing.AbstractButton;
import javax.swing.JMenuItem;

import junit.framework.TestCase;
import net.sf.jaxodraw.util.JaxoConstants;

/**
 * Test of JaxoMenuBar.
 */
public abstract class AbstractMenuBarTest extends TestCase {

    private JaxoMenuBar menuBar;
    private final ActionRecorder actionRecorder = new ActionRecorder();

    /**
     * Constructor.
     *
     * @param testName name of the test.
     */
    public AbstractMenuBarTest(String testName) {
        super(testName);
    }

    /** JUnit setup: initialize the menuBar. */
    @Override
    protected void setUp() {
        actionRecorder.reset();
        menuBar = new JaxoMenuBar(actionRecorder);
    }

    /** Test the setup of this Test. */
    public void testSetup() {
        assertNotNull(menuBar);
        assertEquals(Integer.MIN_VALUE, actionRecorder.lastAction());

        assertSetup();
    }

    /** Test updateLanguage. */
    public void testLanguage() {
        menuBar.updateLanguage();

        assertLanguage();
    }

    /**
     * Fires a PropertyChangeEvent on the MenuBar with this Test as source.
     *
     * @param name the name of the event.
     * @param old the old property value.
     * @param newValue the new property value.
     */
    protected void firePropertyChange(String name, Object old, Object newValue) {
        menuBar.propertyChange(new PropertyChangeEvent(
                this, name, old, newValue));
    }

    /**
     * Returns the last Action registered by the MenuBar.
     *
     * @return an integer corresponding to a mode in JaxoConstants,
     *          or Integer.MIN_VALUE if no Action has happened.
     */
    protected int lastAction() {
        return actionRecorder.lastAction();
    }

    /**
     * Returns the MenuItem for a given mode.
     *
     * @param mode the mode to look for.
     * @return a MenuItem or null, if none was found.
     * @see net.sf.jaxodraw.util.JaxoConstants
     */
    protected JMenuItem getMenuItemForMode(int mode) {
        Component[] children = menuBar.getComponents();
        for (int i = 0; i < children.length; ++i) {
            if (children[i] instanceof JaxoMenu) {
                JMenuItem item = getMenuItemForMode((JaxoMenu) children[i], mode);
                if (item != null) {
                    return item;
                }
            }
        }

        return null;
    }

    /**
     * Lookup the MenuItem for the given mode, assert it is not null and enabled,
     * perform a programmatic click and assert the registered action in the MenuBar.
     *
     * @param mode the mode that fires the action.
     */
    protected void assertAction(int mode) {
        JMenuItem mi = getMenuItemForMode(mode);
        assertNotNull(mi);
        assertTrue(mi.isEnabled());

        mi.doClick();
        assertEquals(mode, lastAction());
    }

    /**
     * Lookup the MenuItem for the given mode, assert it is not null and disabled,
     * assert that the boolean property with name property enables and disables
     * the MenuItem, and finally perform a programmatic click and assert the
     * registered action in the MenuBar.
     *
     * @param mode the mode that fires the action.
     * @param property the corresponding property.
     */
    protected void assertEnabled(int mode, String property) {
        JMenuItem mi = getMenuItemForMode(mode);
        assertNotNull(mi);
        assertFalse(mi.isEnabled());
        firePropertyChange(property, Boolean.FALSE, Boolean.TRUE);
        assertTrue(mi.isEnabled());
        firePropertyChange(property, Boolean.TRUE, Boolean.FALSE);
        assertFalse(mi.isEnabled());

        mi.setEnabled(true);
        mi.doClick();
        assertEquals(mode, lastAction());
    }

    /**
     * Lookup the MenuItem for the given mode, and assert it is not null and selected.
     *
     * @param mode the mode that fires the action.
     */
    protected void assertSelected(int mode) {
        JMenuItem mi = getMenuItemForMode(mode);
        assertNotNull(mi);
        assertTrue(mi.isSelected());
    }

    /**
     * Lookup the MenuItem for the given mode, assert it is not null and enabled,
     * assert that the boolean property with name property checks and un-checks
     * the MenuItem, and finally perform a programmatic click and assert the
     * registered action in the MenuBar.
     *
     * @param mode
     * @param property
     */
    protected void assertChecked(int mode, String property) {
        JMenuItem mi = getMenuItemForMode(mode);
        assertNotNull(mi);
        assertTrue(mi.isEnabled());
        JaxoMenuUtils.setChecked(mi, false);
        firePropertyChange(property, Boolean.FALSE, Boolean.TRUE);
        assertTrue(JaxoMenuUtils.isChecked(mi));
        firePropertyChange(property, Boolean.TRUE, Boolean.FALSE);
        assertFalse(JaxoMenuUtils.isChecked(mi));

        mi.setEnabled(true);
        mi.doClick();
        assertEquals(mode, lastAction());
    }

    /**
     * Assert state after construction.
     */
    public abstract  void assertSetup();

    /**
     * Assert state after updateLanguage() was called.
     */
    public abstract void assertLanguage();

      //
     // private
    //

    private JMenuItem getMenuItemForMode(JaxoMenu menu, int mode) {
        String actionCommand = JaxoConstants.getModeAsString(mode);

        Component[] children = menu.getMenuComponents();

        for (int i = 0; i < children.length; ++i) {
            if (children[i] instanceof JaxoMenu) {
                JMenuItem child = getMenuItemForMode((JaxoMenu) children[i], mode);
                if (child != null) {
                    return child;
                }
            } else if (children[i] instanceof JMenuItem) {
                JMenuItem mItem = (JMenuItem) children[i];
                if (actionCommand.equals(mItem.getActionCommand())) {
                    return mItem;
                }
            }
        }

        return null;
    }

    private class ActionRecorder implements ActionListener {

        private String actionCommand;

        public int lastAction() {
            if (actionCommand == null) {
                return Integer.MIN_VALUE;
            } else {
                return JaxoConstants.getModeAsInt(actionCommand);
            }
        }

        public void reset() {
            this.actionCommand = null;
        }

        public void actionPerformed(ActionEvent e) {
            AbstractButton source = (AbstractButton) e.getSource();
            this.actionCommand = source.getActionCommand();
        }
    }
}
