// 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.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.TransferHandler;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.PrimitiveId;
import org.openstreetmap.josm.gui.PrimitiveRenderer;
import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
import org.openstreetmap.josm.plugins.turnrestrictions.dnd.PrimitiveIdListProvider;
import org.openstreetmap.josm.plugins.turnrestrictions.dnd.PrimitiveIdListTransferHandler;
import org.openstreetmap.josm.plugins.turnrestrictions.dnd.PrimitiveIdTransferable;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.Shortcut;

/**
 * <p>ViaList is a JList which displays the 'via' members of a turn restriction.</p>
 *
 * <p>A ViaList is connected to a {@link TurnRestrictionEditorModel} through its
 * {@link ViaListModel}.</p>
 *
 */
public class ViaList extends JList<OsmPrimitive> {

    //private static final Logger logger = Logger.getLogger(ViaList.class.getName());

    private ViaListModel model;
    private DeleteAction actDelete;
    private MoveUpAction actMoveUp;
    private MoveDownAction actMoveDown;
    private CopyAction actCopy;
    private PasteAction actPaste;
    private TransferHandler transferHandler;

    /**
     * Constructor
     *
     * @param model the via list model. Must not be null.
     * @param selectionModel the selection model. Must not be null.
     *
     */
    public ViaList(ViaListModel model, DefaultListSelectionModel selectionModel) {
        super(model);
        this.model = model;
        setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        setSelectionModel(selectionModel);
        setCellRenderer(new PrimitiveRenderer());
        setDragEnabled(true);
        setTransferHandler(transferHandler = new ViaListTransferHandler(model));
        setVisibleRowCount(4);

        actDelete = new DeleteAction();
        selectionModel.addListSelectionListener(actDelete);
        registerKeyboardAction(actDelete, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

        actMoveDown = new MoveDownAction();
        selectionModel.addListSelectionListener(actMoveDown);
        registerKeyboardAction(actMoveDown,
                KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

        actMoveUp = new MoveUpAction();
        selectionModel.addListSelectionListener(actMoveUp);
        registerKeyboardAction(actMoveUp,
                KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.ALT_DOWN_MASK), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

        actCopy = new CopyAction();
        actPaste = new PasteAction();
        getSelectionModel().addListSelectionListener(actCopy);

        addMouseListener(new ViaListPopupMenuLaucher());
    }

    /**
     * The transfer handler for Drag-and-Drop.
     */
    class ViaListTransferHandler extends PrimitiveIdListTransferHandler {
        Logger logger = Logger.getLogger(ViaListTransferHandler.class.getName());

        private boolean isViaListInDragOperation = false;
        private List<Integer> selectedRowsMemento = null;

        ViaListTransferHandler(PrimitiveIdListProvider provider) {
            super(provider);
        }

        @Override
        public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
            // a drag operation on itself is always allowed
            if (isViaListInDragOperation) return true;
            return isSupportedFlavor(transferFlavors);
        }

        @SuppressWarnings("unchecked")
        @Override
        public boolean importData(JComponent comp, Transferable t) {
            if (!isSupportedFlavor(t.getTransferDataFlavors())) return false;
            if (isViaListInDragOperation) {
                // this is a drag operation on itself
                int targetRow = getSelectedIndex();
                if (targetRow < 0) return true;
                model.moveVias(selectedRowsMemento, targetRow);
            } else {
                // this is a drag operation from another component
                try {
                    List<PrimitiveId> idsToAdd = (List<PrimitiveId>) t.getTransferData(PrimitiveIdTransferable.PRIMITIVE_ID_LIST_FLAVOR);
                    model.insertVias(idsToAdd);
                } catch (IOException | UnsupportedFlavorException e) {
                    Logging.error(e);
                }
            }
            return true;
        }

        @Override
        protected void exportDone(JComponent source, Transferable data, int action) {
            isViaListInDragOperation = false;
            super.exportDone(source, data, action);
        }

        @Override
        public void exportAsDrag(JComponent comp, InputEvent e, int action) {
            isViaListInDragOperation = true;
            selectedRowsMemento = model.getSelectedRows();
            super.exportAsDrag(comp, e, action);
        }
    }

    class DeleteAction extends AbstractAction implements ListSelectionListener {
        DeleteAction() {
            putValue(NAME, tr("Remove"));
            new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this);
            putValue(SHORT_DESCRIPTION, tr("Remove the currently selected vias"));
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
            updateEnabledState();
        }

        @Override
        public void valueChanged(ListSelectionEvent e) {
            updateEnabledState();
        }

        public void updateEnabledState() {
            setEnabled(getSelectedIndex() >= 0);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            model.removeSelectedVias();
        }
    }

    class MoveDownAction extends AbstractAction implements ListSelectionListener {
        MoveDownAction() {
            putValue(NAME, tr("Move down"));
            putValue(SHORT_DESCRIPTION, tr("Move the selected vias down by one position"));
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK));
            new ImageProvider("dialogs", "down").getResource().attachImageIcon(this);
            updateEnabledState();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            model.moveDown();
        }

        public void updateEnabledState() {
            if (getSelectedIndex() < 0) {
                setEnabled(false);
                return;
            }
            setEnabled(getSelectionModel().getMaxSelectionIndex() < getModel().getSize() -1);
        }

        @Override
        public void valueChanged(ListSelectionEvent e) {
            updateEnabledState();
        }
    }

    class MoveUpAction extends AbstractAction implements ListSelectionListener {
        MoveUpAction() {
            putValue(NAME, tr("Move up"));
            putValue(SHORT_DESCRIPTION, tr("Move the selected vias up by one position"));
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.ALT_DOWN_MASK));
            new ImageProvider("dialogs", "up").getResource().attachImageIcon(this);
            updateEnabledState();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            model.moveUp();
        }

        public void updateEnabledState() {
            if (getSelectedIndex() < 0) {
                setEnabled(false);
                return;
            }
            setEnabled(getSelectionModel().getMinSelectionIndex() > 0);
        }

        @Override
        public void valueChanged(ListSelectionEvent e) {
            updateEnabledState();
        }
    }

    class CopyAction extends AbstractAction implements ListSelectionListener {
        private Action delegate;

        CopyAction() {
            putValue(NAME, tr("Copy"));
            putValue(SHORT_DESCRIPTION, tr("Copy the selected vias to the clipboard"));
            new ImageProvider("copy").getResource().attachImageIcon(this);
            putValue(ACCELERATOR_KEY, Shortcut.getCopyKeyStroke());
            delegate = ViaList.this.getActionMap().get("copy");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            delegate.actionPerformed(e);
        }

        protected void updateEnabledState() {
            setEnabled(!model.getSelectedVias().isEmpty());
        }

        @Override
        public void valueChanged(ListSelectionEvent e) {
            updateEnabledState();
        }
    }

    class PasteAction extends AbstractAction {
        private Action delegate;

        public boolean canPaste() {
            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
            for (DataFlavor df: clipboard.getAvailableDataFlavors()) {
                if (df.equals(PrimitiveIdTransferable.PRIMITIVE_ID_LIST_FLAVOR)) return true;
            }
            // FIXME: check whether there are selected objects in the JOSM copy/paste buffer
            return false;
        }

        PasteAction() {
            putValue(NAME, tr("Paste"));
            putValue(SHORT_DESCRIPTION, tr("Insert ''via'' objects from the clipboard"));
            new ImageProvider("paste").getResource().attachImageIcon(this);
            putValue(ACCELERATOR_KEY, Shortcut.getPasteKeyStroke());
            delegate = ViaList.this.getActionMap().get("paste");
            updateEnabledState();
        }

        public void updateEnabledState() {
            setEnabled(canPaste());
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            delegate.actionPerformed(e);
        }
    }

    class ViaListPopupMenu extends JPopupMenu {
        ViaListPopupMenu() {
            JMenuItem item = add(actCopy);
            item.setTransferHandler(transferHandler);
            item = add(actPaste);
            actPaste.updateEnabledState();
            item.setTransferHandler(transferHandler);
            addSeparator();
            add(actDelete);
            addSeparator();
            add(actMoveUp);
            add(actMoveDown);
        }
    }

    class ViaListPopupMenuLaucher extends PopupMenuLauncher {
        @Override
        public void launch(MouseEvent evt) {
            if (getSelectedIndex() < 0) {
                int idx = locationToIndex(evt.getPoint());
                if (idx >= 0) {
                    setSelectedIndex(idx);
                }
            }
            new ViaListPopupMenu().show(ViaList.this, evt.getX(), evt.getY());
        }
    }
}
