// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.widgets;

import java.awt.Component;
import java.awt.Point;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;

import org.openstreetmap.josm.tools.Logging;

/**
 * Utility class that helps to display popup menus on mouse events.
 * @since 2688
 */
public class PopupMenuLauncher extends MouseAdapter {
    protected JPopupMenu menu;
    private final boolean checkEnabled;

    /**
     * Creates a new {@link PopupMenuLauncher} with no defined menu.
     * It is then needed to override the {@link #launch} method.
     * @see #launch(MouseEvent)
     */
    public PopupMenuLauncher() {
        this(null);
    }

    /**
     * Creates a new {@link PopupMenuLauncher} with the given menu.
     * @param menu The popup menu to display
     */
    public PopupMenuLauncher(JPopupMenu menu) {
        this(menu, false);
    }

    /**
     * Creates a new {@link PopupMenuLauncher} with the given menu.
     * @param menu The popup menu to display
     * @param checkEnabled if {@code true}, the popup menu will only be displayed if the component triggering the mouse event is enabled
     * @since 5886
     */
    public PopupMenuLauncher(JPopupMenu menu, boolean checkEnabled) {
        this.menu = menu;
        this.checkEnabled = checkEnabled;
    }

    @Override
    public void mousePressed(MouseEvent e) {
        processEvent(e);
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        processEvent(e);
    }

    private void processEvent(MouseEvent e) {
        if (e.isPopupTrigger() && (!checkEnabled || e.getComponent().isEnabled())) {
            launch(e);
        }
    }

    /**
     * Launches the popup menu according to the given mouse event.
     * This method needs to be overridden if the default constructor has been called.
     * @param evt A mouse event
     */
    public void launch(final MouseEvent evt) {
        if (evt != null) {
            final Component component = evt.getComponent();
            if (checkSelection(component, evt.getPoint())) {
                checkFocusAndShowMenu(component, evt);
            }
        }
    }

    protected boolean checkSelection(Component component, Point p) {
        if (component instanceof JList) {
            return checkListSelection((JList<?>) component, p) > -1;
        } else if (component instanceof JTable) {
            return checkTableSelection((JTable) component, p) > -1;
        } else if (component instanceof JTree) {
            return checkTreeSelection((JTree) component, p) != null;
        }
        return true;
    }

    protected void checkFocusAndShowMenu(final Component component, final MouseEvent evt) {
        if (component != null && component.isFocusable() && !component.hasFocus() && component.requestFocusInWindow()) {
            component.addFocusListener(new FocusAdapter() {
                @Override
                public void focusGained(FocusEvent e) {
                    showMenu(evt);
                    component.removeFocusListener(this);
                }
            });
        } else {
            showMenu(evt);
        }
    }

    protected void showMenu(MouseEvent evt) {
        if (menu != null && evt != null) {
            Component component = evt.getComponent();
            if (component.isShowing()) {
                menu.show(component, evt.getX(), evt.getY());
            }
        } else {
            Logging.warn("Unable to display menu {0} - {1}", menu, evt);
        }
    }

    protected int checkListSelection(JList<?> list, Point p) {
        int idx = list.locationToIndex(p);
        if (idx >= 0 && idx < list.getModel().getSize() && list.getSelectedIndices().length < 2 && !list.isSelectedIndex(idx)) {
            list.setSelectedIndex(idx);
        }
        return idx;
    }

    protected int checkTableSelection(JTable table, Point p) {
        int row = table.rowAtPoint(p);
        if (row >= 0 && row < table.getRowCount() && table.getSelectedRowCount() < 2 && table.getSelectedRow() != row) {
            table.getSelectionModel().setSelectionInterval(row, row);
        }
        return row;
    }

    protected TreePath checkTreeSelection(JTree tree, Point p) {
        TreePath path = tree.getPathForLocation(p.x, p.y);
        if (path != null && tree.getSelectionCount() < 2 && !tree.isPathSelected(path)) {
            tree.setSelectionPath(path);
        }
        return path;
    }

    protected static boolean isDoubleClick(MouseEvent e) {
        return e != null && SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2;
    }

    /**
     * Returns the popup menu.
     * @return the popup menu if defined, {@code null} otherwise.
     * @since 5884
     */
    public final JPopupMenu getMenu() {
        return menu;
    }

    /**
     * Empties the internal undo manager, if any.
     * @since 14977
     */
    public void discardAllUndoableEdits() {
        if (menu instanceof TextContextualPopupMenu) {
            ((TextContextualPopupMenu) menu).discardAllUndoableEdits();
        }
    }
}
