// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.turnrestrictions.editor;

import static org.openstreetmap.josm.tools.I18n.tr;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.TableColumn;

import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.plugins.turnrestrictions.TurnRestrictionBuilder;
import org.openstreetmap.josm.plugins.turnrestrictions.list.TurnRestrictionCellRenderer;
import org.openstreetmap.josm.tools.CheckParameterUtil;
import org.openstreetmap.josm.tools.ImageProvider;

/**
 * TurnRestrictionSelectionPopupPanel is displayed in a {@link Popup} to select whether
 * the user wants to create a new turn restriction or whether he wants to edit one
 * of a list of turn restrictions.
 *
 */
public class TurnRestrictionSelectionPopupPanel extends JPanel {

    /** the parent popup */
    private Popup parentPopup;
    /** the button for creating a new turn restriction */
    private JButton btnNew;
    /** the table with the turn restrictions which can be edited */
    private JTable tblTurnRestrictions;
    private OsmDataLayer layer;

    /**
     * Replies the collection of turn restrictions the primitives in {@code primitives}
     * currently participate in.
     *
     * @param primitives the collection of primitives. May be null.
     * @return the collection of "parent" turn restrictions.
     */
    public static Collection<Relation> getTurnRestrictionsParticipatingIn(Collection<OsmPrimitive> primitives) {
        HashSet<Relation> ret = new HashSet<>();
        if (primitives == null) return ret;
        for (OsmPrimitive p: primitives) {
            if (p == null) continue;
            if (p.isDeleted() || !p.isVisible()) continue;
            for (OsmPrimitive parent: p.getReferrers()) {
                if (!(parent instanceof Relation)) continue;
                String type = parent.get("type");
                if (type == null || !type.equals("restriction")) continue;
                if (parent.isDeleted() || !parent.isVisible()) continue;
                ret.add((Relation) parent);
            }
        }
        return ret;
    }

    /**
     * Registers 1..9 shortcuts for the first 9 turn restrictions to
     * edit
     *
     * @param editCandiates the edit candidates
     */
    protected void registerEditShortcuts(Collection<Relation> editCandiates) {
        for (int i = 1; i <= Math.min(editCandiates.size(), 9); i++) {
            int vkey = 0;
            switch(i) {
            case 1: vkey = KeyEvent.VK_1; break;
            case 2: vkey = KeyEvent.VK_2; break;
            case 3: vkey = KeyEvent.VK_3; break;
            case 4: vkey = KeyEvent.VK_4; break;
            case 5: vkey = KeyEvent.VK_5; break;
            case 6: vkey = KeyEvent.VK_6; break;
            case 7: vkey = KeyEvent.VK_7; break;
            case 8: vkey = KeyEvent.VK_8; break;
            case 9: vkey = KeyEvent.VK_9; break;
            }
            registerKeyboardAction(new EditTurnRestrictionAction(i-1), KeyStroke.getKeyStroke(vkey, 0), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        }
    }

    /**
     * Builds the panel with the turn restrictions table
     *
     * @param editCandiates the list of edit candiates
     * @return the panel
     */
    protected JPanel buildTurnRestrictionTablePanel(Collection<Relation> editCandiates) {
        tblTurnRestrictions = new JTable(new TurnRestrictionTableModel(editCandiates), new TurnRestrictionTableColumnModel());
        tblTurnRestrictions.setColumnSelectionAllowed(false);
        tblTurnRestrictions.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
        TurnRestrictionCellRenderer renderer = new TurnRestrictionCellRenderer();
        tblTurnRestrictions.setRowHeight((int) renderer.getPreferredSize().getHeight());

        // create a scroll pane, remove the table header
        JScrollPane pane = new JScrollPane(tblTurnRestrictions);
        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        tblTurnRestrictions.setTableHeader(null);
        pane.setColumnHeaderView(null);

        // respond to double click and ENTER
        EditSelectedTurnRestrictionAction action = new EditSelectedTurnRestrictionAction();
        tblTurnRestrictions.addMouseListener(action);
        tblTurnRestrictions.registerKeyboardAction(action, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), WHEN_FOCUSED);

        tblTurnRestrictions.addFocusListener(new FocusHandler());

        JPanel pnl = new JPanel(new BorderLayout());
        pnl.add(pane, BorderLayout.CENTER);

        pnl.setBackground(UIManager.getColor("Table.background"));
        pane.setBackground(UIManager.getColor("Table.background"));
        return pnl;
    }

    /**
     * Builds the panel
     *
     * @param editCandiates the edit candidates
     */
    protected void build(Collection<Relation> editCandiates) {
        setLayout(new BorderLayout());
        add(btnNew = new JButton(new NewAction()), BorderLayout.NORTH);
        btnNew.setFocusable(true);
        btnNew.registerKeyboardAction(btnNew.getAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), WHEN_FOCUSED);
        registerKeyboardAction(new CloseAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        registerKeyboardAction(btnNew.getAction(), KeyStroke.getKeyStroke(KeyEvent.VK_N, 0), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

        btnNew.addFocusListener(new FocusHandler());

        if (editCandiates != null && !editCandiates.isEmpty()) {
            add(buildTurnRestrictionTablePanel(editCandiates), BorderLayout.CENTER);
            registerEditShortcuts(editCandiates);
        }

        setBackground(UIManager.getColor("Table.background"));
    }


    /**
     * Creates the panel
     *
     * @param layer the reference OSM data layer. Must not be null.
     * @throws IllegalArgumentException thrown if {@code layer} is null
     */
    public TurnRestrictionSelectionPopupPanel(OsmDataLayer layer) throws IllegalArgumentException {
        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
        this.layer = layer;
        build(getTurnRestrictionsParticipatingIn(layer.data.getSelected()));
    }

    /**
     * Creates the panel
     *
     * @param layer the reference OSM data layer. Must not be null.
     * @param editCandidates a collection of turn restrictions as edit candidates. May be null.
     * @throws IllegalArgumentException thrown if {@code layer} is null
     */
    public TurnRestrictionSelectionPopupPanel(OsmDataLayer layer, Collection<Relation> editCandidates) {
        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
        this.layer = layer;
        build(editCandidates);
    }

    /**
     * Launches a popup with this panel as content
     */
    public void launch() {
        PointerInfo info = MouseInfo.getPointerInfo();
        Point pt = info.getLocation();
        parentPopup = PopupFactory.getSharedInstance().getPopup(MainApplication.getMap().mapView, this, pt.x, pt.y);
        parentPopup.show();
        btnNew.requestFocusInWindow();
    }

    @Override
    public Dimension getPreferredSize() {
        int bestheight = (int) btnNew.getPreferredSize().getHeight()
              + Math.min(2, tblTurnRestrictions.getRowCount()) * tblTurnRestrictions.getRowHeight()
              + 5;
        return new Dimension(300, bestheight);
    }

    /* --------------------------------------------------------------------------------------- */
    /* inner classes                                                                           */
    /* --------------------------------------------------------------------------------------- */

    private class NewAction extends AbstractAction {
        NewAction() {
            putValue(NAME, tr("Create new turn restriction"));
            putValue(SHORT_DESCRIPTION, tr("Launch the turn restriction editor to create a new turn restriction"));
            new ImageProvider("new").getResource().attachImageIcon(this);
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_N, 0));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            Relation tr = new TurnRestrictionBuilder().buildFromSelection(layer);
            TurnRestrictionEditor editor = new TurnRestrictionEditor(MainApplication.getMap().mapView, layer, tr);
            TurnRestrictionEditorManager.getInstance().positionOnScreen(editor);
            TurnRestrictionEditorManager.getInstance().register(layer, tr, editor);
            if (parentPopup != null) {
                parentPopup.hide();
            }
            editor.setVisible(true);
        }
    }

    private abstract class AbstractEditTurnRestrictionAction extends AbstractAction {
        protected void launchEditor(Relation tr) {
            TurnRestrictionEditorManager manager = TurnRestrictionEditorManager.getInstance();
            TurnRestrictionEditor editor = manager.getEditorForRelation(layer, tr);
            if (parentPopup != null) {
                parentPopup.hide();
            }
            if (editor != null) {
                editor.setVisible(true);
                editor.toFront();
            } else {
                editor = new TurnRestrictionEditor(MainApplication.getMap().mapView, layer, tr);
                manager.positionOnScreen(editor);
                manager.register(layer, tr, editor);
                editor.setVisible(true);
            }
        }
    }

    private class EditTurnRestrictionAction extends AbstractEditTurnRestrictionAction {
        private int idx;

        EditTurnRestrictionAction(int idx) {
            this.idx = idx;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            Relation tr = (Relation) tblTurnRestrictions.getModel().getValueAt(idx, 1);
            launchEditor(tr);
        }
    }

    private class EditSelectedTurnRestrictionAction extends AbstractEditTurnRestrictionAction implements MouseListener {
        public void editTurnRestrictionAtRow(int row) {
            if (row < 0) return;
            Relation tr = (Relation) tblTurnRestrictions.getModel().getValueAt(row, 1);
            launchEditor(tr);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int row = tblTurnRestrictions.getSelectedRow();
            editTurnRestrictionAtRow(row);
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            if (!(SwingUtilities.isLeftMouseButton(e) && e.getClickCount() >= 2)) return;
            int row = tblTurnRestrictions.rowAtPoint(e.getPoint());
            if (row < 0) return;
            editTurnRestrictionAtRow(row);
        }

        @Override
        public void mouseEntered(MouseEvent e) {}

        @Override
        public void mouseExited(MouseEvent e) {}

        @Override
        public void mousePressed(MouseEvent e) {}

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

    private class CloseAction extends AbstractAction {
        @Override
        public void actionPerformed(ActionEvent e) {
            if (parentPopup != null) {
                parentPopup.hide();
            }
        }
    }

    private static class TurnRestrictionTableModel extends AbstractTableModel {
        private final ArrayList<Relation> turnrestrictions = new ArrayList<>();

        TurnRestrictionTableModel(Collection<Relation> turnrestrictions) {
            this.turnrestrictions.clear();
            if (turnrestrictions != null) {
                this.turnrestrictions.addAll(turnrestrictions);
            }
            fireTableDataChanged();
        }

        @Override
        public int getRowCount() {
            return turnrestrictions.size();
        }

        @Override
        public int getColumnCount() {
            return 2;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            switch(columnIndex) {
            case 0:
                if (rowIndex <= 8) {
                    return Integer.toString(rowIndex+1);
                } else {
                    return "";
                }
            case 1:
                return turnrestrictions.get(rowIndex);
            }
            // should not happen
            return null;
        }
    }

    private static class TurnRestrictionTableColumnModel extends DefaultTableColumnModel {
        TurnRestrictionTableColumnModel() {
            // the idx column
            TableColumn col = new TableColumn(0);
            col.setResizable(false);
            col.setWidth(50);
            addColumn(col);

            // the column displaying turn restrictions
            col = new TableColumn(1);
            col.setResizable(false);
            col.setPreferredWidth(400);
            col.setCellRenderer(new TurnRestrictionCellRenderer());
            addColumn(col);
        }
    }

    private class FocusHandler extends FocusAdapter {
        @Override
        public void focusLost(FocusEvent e) {
            // if we loose the focus to a component outside of the popup panel
            // we hide the popup
            if (e.getOppositeComponent() == null ||
                    !SwingUtilities.isDescendingFrom(e.getOppositeComponent(), TurnRestrictionSelectionPopupPanel.this)) {
                if (parentPopup != null) {
                    parentPopup.hide();
                }
            }
        }
    }
}
