/**
 *  Copyright (C) 2002-2019   The FreeCol Team
 *
 *  This file is part of FreeCol.
 *
 *  FreeCol is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  FreeCol 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 for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with FreeCol.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.sf.freecol.client.gui.panel;

import java.awt.Component;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.io.IOException;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.function.Function;

import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.TransferHandler;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.PanelUI;

import net.miginfocom.swing.MigLayout;

import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.gui.ImageLibrary;
import net.sf.freecol.client.gui.label.FreeColLabel;
import net.sf.freecol.client.gui.plaf.FreeColSelectedPanelUI;
import net.sf.freecol.common.i18n.Messages;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.Europe;
import net.sf.freecol.common.model.FreeColGameObject;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.Location;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.StringTemplate;
import net.sf.freecol.common.model.TradeLocation;
import net.sf.freecol.common.model.TradeRoute;
import net.sf.freecol.common.model.TradeRouteStop;
import static net.sf.freecol.common.util.CollectionUtils.*;


/**
 * Allows the user to edit trade routes.
 */
public final class TradeRouteInputPanel extends FreeColPanel 
    implements ListSelectionListener {

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

    public static final DataFlavor STOP_FLAVOR
        = new DataFlavor(TradeRouteStop.class, "Stop");

    /**
     * Special label for cargo-to-carry type.
     *
     * Public for DragListener.
     */
    public static class TradeRouteCargoLabel extends FreeColLabel {

        /** The type of goods to load at a stop. */
        private final GoodsType goodsType;


        /**
         * Build a new cargo label for a given goods type.
         *
         * @param icon The label icon.
         * @param type The {@code GoodsType}.
         */         
        public TradeRouteCargoLabel(ImageIcon icon, GoodsType type) {
            super(icon);

            this.goodsType = type;
        }

        /**
         * Get the goods type of this label.
         *
         * @return The {@code GoodsType}.
         */
        public GoodsType getType() {
            return this.goodsType;
        }
    }


    /**
     * Panel for the cargo the carrier is supposed to take on board at
     * a certain stop.
     *
     * FIXME: create a single cargo panel for this purpose and the use
     * in the ColonyPanel, the EuropePanel and the CaptureGoodsDialog?
     */
    private static class TradeRouteCargoPanel extends MigPanel {

        /**
         * Build a new cargo panel.
         */
        public TradeRouteCargoPanel() {
            super(new MigLayout());

            setOpaque(false);
            setBorder(Utility.localizedBorder("cargoOnCarrier"));
        }

        /**
         * Set the labels to display on this panel.
         *
         * @param labels The {@code TradeRouteCargoLabel}s to display.
         */
        public void setLabels(List<TradeRouteCargoLabel> labels) {
            removeAll();
            if (labels != null) {
                for (TradeRouteCargoLabel label : labels) add(label);
            }
            revalidate();
            repaint();
        }

        /**
         * Add a single label.
         *
         * Do not repaint, that will be done top down.
         *
         * @param label The {@code TradeRouteCargoLabel} to add.
         */
        public void addLabel(TradeRouteCargoLabel label) {
            if (label != null) {
                add(label);
                revalidate();
            }
        }

        /**
         * Remove labels that correspond to a given goods type.
         *
         * @param gt The {@code GoodsType} to remove.
         */
        public void removeType(GoodsType gt) {
            for (Component child : getComponents()) {
                if (child instanceof TradeRouteCargoLabel) {
                    if (((TradeRouteCargoLabel)child).getType() == gt) {
                        remove(child);
                    }
                }
                revalidate();
            }
        }
    }

    /**
     * TransferHandler for TradeRouteCargoLabels.
     *
     * FIXME: check whether this could/should be folded into the
     * DefaultTransferHandler.
     */
    private static class CargoHandler extends TransferHandler {

        /** The enclosing panel. */
        private TradeRouteInputPanel tradeRouteInputPanel;


        /**
         * Build a new cargo handler for the given panel.
         *
         * @param trip The {@code TradeRouteInputPanel} to notify of changes.
         */
        public CargoHandler(TradeRouteInputPanel trip) {
            this.tradeRouteInputPanel = trip;
        }


        /**
         * Convenience function to try to get a {@code TradeRouteCargoLabel}
         * from a {@code Transferable}.
         *
         * @param data The {@code Transferable} to query.
         * @return The label found if any.
         * @exception IOException if the data is not available.
         * @exception UnsupportedFlavorException on flavor mismatch.
         */
        private TradeRouteCargoLabel tryTransfer(Transferable data)
            throws IOException, UnsupportedFlavorException {
            return (TradeRouteCargoLabel)data.getTransferData(DefaultTransferHandler.flavor);
        }
        
        /**
         * {@inheritDoc}
         */
        @Override
        protected Transferable createTransferable(JComponent c) {
            return new ImageSelection((TradeRouteCargoLabel) c);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int getSourceActions(JComponent c) {
            return COPY_OR_MOVE;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean importData(JComponent target, Transferable data) {
            if (!canImport(target, data.getTransferDataFlavors()))
                return false;
            TradeRouteCargoLabel label;
            try {
                label = tryTransfer(data);
            } catch (IOException|UnsupportedFlavorException ex) {
                logger.log(Level.WARNING, "CargoHandler import", ex);
                return false;
            }
            if (target instanceof TradeRouteCargoPanel) {
                this.tradeRouteInputPanel.enableImport(label.getType());
                return true;
            }
            return false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void exportDone(JComponent source, Transferable data,
                                  int action) {
            TradeRouteCargoLabel label;
            try {
                label = tryTransfer(data);
            } catch (IOException|UnsupportedFlavorException ex) {
                logger.log(Level.WARNING, "CargoHandler export", ex);
                return;
            }
            if (source.getParent() instanceof TradeRouteCargoPanel) {
                this.tradeRouteInputPanel.cancelImport(label.getType());
            }
        }
        
        /**
         * {@inheritDoc}
         */
        @Override
        public boolean canImport(JComponent c, DataFlavor[] flavors) {
            return any(flavors, matchKeyEquals(DefaultTransferHandler.flavor));
        }
    }

    private class DestinationCellRenderer extends JLabel
        implements ListCellRenderer<String> {

        public DestinationCellRenderer() {
            setOpaque(true);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Component getListCellRendererComponent(JList<? extends String> list,
                                                      String value,
                                                      int index,
                                                      boolean isSelected,
                                                      boolean cellHasFocus) {
            FreeColGameObject fcgo = getGame().getFreeColGameObject(value);
            if (fcgo instanceof Location) {
                setText(Messages.message(((Location)fcgo).getLocationLabel()));
            } else {
                setText(value);
            }
            setForeground((isSelected) ? list.getSelectionForeground()
                : list.getForeground());
            setBackground((isSelected) ? list.getSelectionBackground()
                : list.getBackground());
            return this;
        }
    }

    /**
     * Panel for all types of goods that can be loaded onto a carrier.
     */
    private static class TradeRouteGoodsPanel extends MigPanel {

        public TradeRouteGoodsPanel(List<TradeRouteCargoLabel> labels) {
            super(new GridLayout(0, 4, MARGIN, MARGIN));

            for (TradeRouteCargoLabel label : labels) add(label);
            setOpaque(false);
            setBorder(Utility.localizedBorder("goods"));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void setEnabled(boolean enable) {
            super.setEnabled(enable);
            for (Component child : getComponents()) {
                if (child instanceof TradeRouteCargoLabel) child.setEnabled(enable);
            }
        }
    }

    private static class StopListTransferable implements Transferable {

        private final List<TradeRouteStop> stops;


        public StopListTransferable(List<TradeRouteStop> stops) {
            this.stops = stops;
        }

        public List<TradeRouteStop> getStops() {
            return stops;
        }

        // Interface Transferable

        /**
         * {@inheritDoc}
         */
        @Override
        public Object getTransferData(DataFlavor flavor) {
            return (flavor == STOP_FLAVOR) ? stops : null;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[] { STOP_FLAVOR };
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return flavor == STOP_FLAVOR;
        }
    }

    /**
     * TransferHandler for Stops.
     */
    private class StopListHandler extends TransferHandler {

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean canImport(JComponent c, DataFlavor[] flavors) {
            return any(flavors, matchKeyEquals(STOP_FLAVOR));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected Transferable createTransferable(JComponent c) {
            JList list = (JList)c;
            final DefaultListModel model = (DefaultListModel)list.getModel();
            int[] indicies = list.getSelectedIndices();
            List<TradeRouteStop> stops = new ArrayList<>(indicies.length);
            for (int i : indicies) stops.add(i, (TradeRouteStop)model.get(i));
            return new StopListTransferable(stops);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int getSourceActions(JComponent c) {
            return MOVE;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean importData(JComponent target, Transferable data) {
            JList<TradeRouteStop> stl = TradeRouteInputPanel.this.stopList;
            if (target == stl
                && data instanceof StopListTransferable
                && canImport(target, data.getTransferDataFlavors())) {
                List<TradeRouteStop> stops
                    = ((StopListTransferable)data).getStops();
                DefaultListModel<TradeRouteStop> model
                    = new DefaultListModel<>();
                int index = stl.getMaxSelectionIndex();
                for (TradeRouteStop stop : stops) {
                    if (index < 0) {
                        model.addElement(stop);
                    } else {
                        index++;
                        model.add(index, stop);
                    }
                }
                stl.setModel(model);
                return true;
            }
            return false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void exportDone(JComponent source, Transferable data,
                                  int action) {
            try {
                if (source instanceof JList && action == MOVE) {
                    JList stopList = (JList)source;
                    DefaultListModel listModel
                        = (DefaultListModel)stopList.getModel();
                    for (Object o : (List) data.getTransferData(STOP_FLAVOR)) {
                        listModel.removeElement(o);
                    }
                }
            } catch (Exception e) {
                logger.warning(e.toString());
            }
        }
    }

    private class StopRenderer implements ListCellRenderer<TradeRouteStop> {

        private final JPanel SELECTED_COMPONENT = new JPanel();
        private final JPanel NORMAL_COMPONENT = new JPanel();

        public StopRenderer() {
            NORMAL_COMPONENT.setLayout(new MigLayout("", "[80, center][]"));
            NORMAL_COMPONENT.setOpaque(false);
            SELECTED_COMPONENT.setLayout(new MigLayout("", "[80, center][]"));
            SELECTED_COMPONENT.setOpaque(false);
            SELECTED_COMPONENT.setUI((PanelUI)FreeColSelectedPanelUI
                .createUI(SELECTED_COMPONENT));
        }


        /**
         * {@inheritDoc}
         */
        @Override
        public Component getListCellRendererComponent(JList<? extends TradeRouteStop> list,
                                                      TradeRouteStop value,
                                                      int index,
                                                      boolean isSelected,
                                                      boolean hasFocus) {
            JPanel panel = (isSelected) ? SELECTED_COMPONENT
                : NORMAL_COMPONENT;
            panel.removeAll();
            panel.setForeground(list.getForeground());
            panel.setFont(list.getFont());
            Location location = value.getLocation();
            ImageLibrary lib = getImageLibrary();
            JLabel icon, name;

            if (location instanceof TradeLocation) {
                TradeLocation tl = (TradeLocation) location;
                if (tl.canBeInput() == true) {
                    name = tl.getNameAsJlabel();
                } else {
                    throw new IllegalStateException("Bogus location: " + location);
                }
            } else {
                throw new IllegalStateException("Bogus location: " + location);
            }

            if (location instanceof Europe) {
                Europe europe = (Europe) location;
                Image image = lib.getSmallerNationImage(europe.getOwner().getNation());
                icon = new JLabel(new ImageIcon(image));
            } else if (location instanceof Colony) {
                Colony colony = (Colony) location;
                icon = new JLabel(new ImageIcon(lib.getSmallerSettlementImage(colony)));
            } else {
                throw new IllegalStateException("Bogus location: " + location);
            }
            panel.add(icon, "spany");
            panel.add(name, "span, wrap");
            for (GoodsType cargo : value.getCargo()) {
                panel.add(new JLabel(new ImageIcon(
                    lib.getSmallerGoodsTypeImage(cargo))));
            }
            return panel;
        }
    }


    /**
     * The original route passed to this panel.  We are careful not to
     * modify it until we are sure all is well.
     */
    private final TradeRoute newRoute;

    /** A TransferHandler for the cargo labels. */
    private CargoHandler cargoHandler;

    /** Mouse listeners to use throughout. */
    private transient MouseListener dragListener, dropListener;

    /** Model to contain the current stops. */
    private DefaultListModel<TradeRouteStop> stopListModel;

    /** The list of stops to show. */
    private JList<TradeRouteStop> stopList;

    /** The user-editable name of the trade route. */
    private JTextField tradeRouteName;

    /** A box to select stops to add. */
    private JComboBox<String> destinationSelector;

    /** Toggle message display. */
    private JCheckBox messagesBox;

    /** A button to add stops with. */
    private JButton addStopButton;

    /** A button to remove stops with. */
    private JButton removeStopButton;

    /** The panel displaying the goods that could be transported. */
    private final TradeRouteGoodsPanel goodsPanel;

    /** The panel displaying the cargo at the selected stop. */
    private TradeRouteCargoPanel cargoPanel;


    /**
     * Create a panel to define trade route cargos.
     *
     * @param freeColClient The {@code FreeColClient} for the game.
     * @param newRoute The {@code TradeRoute} to operate on.
     */
    public TradeRouteInputPanel(FreeColClient freeColClient,
                                TradeRoute newRoute) {
        super(freeColClient, null,
              new MigLayout("wrap 4, fill", "[]20[fill]rel"));

        final Game game = freeColClient.getGame();
        final Player player = getMyPlayer();
        final TradeRoute tradeRoute = newRoute.copy(game);

        this.newRoute = newRoute;
        this.cargoHandler = new CargoHandler(this);
        this.dragListener = new DragListener(freeColClient, this);
        this.dropListener = new DropListener();

        this.stopListModel = new DefaultListModel<>();
        for (TradeRouteStop stop : tradeRoute.getStopList()) {
            this.stopListModel.addElement(stop);
        }

        this.stopList = new JList<>(this.stopListModel);
        this.stopList.setCellRenderer(new StopRenderer());
        this.stopList.setFixedCellHeight(48);
        this.stopList.setDragEnabled(true);
        this.stopList.setTransferHandler(new StopListHandler());
        this.stopList.addKeyListener(new KeyListener() {

                @Override
                public void keyTyped(KeyEvent e) {
                    if (e.getKeyChar() == KeyEvent.VK_DELETE) {
                        deleteCurrentlySelectedStops();
                    }
                }

                @Override
                public void keyPressed(KeyEvent e) {} // Ignore

                @Override
                public void keyReleased(KeyEvent e) {} // Ignore
            });
        this.stopList.addListSelectionListener(this);
        JScrollPane tradeRouteView = new JScrollPane(stopList);

        JLabel nameLabel = Utility.localizedLabel("tradeRouteInputPanel.nameLabel");
        this.tradeRouteName = new JTextField(tradeRoute.getName());

        JLabel destinationLabel
            = Utility.localizedLabel("tradeRouteInputPanel.destinationLabel");
        this.destinationSelector = new JComboBox<>();
        this.destinationSelector.setRenderer(new DestinationCellRenderer());
        StringTemplate template = StringTemplate.template("tradeRouteInputPanel.allColonies");
        this.destinationSelector.addItem(Messages.message(template));
        if (player.getEurope() != null) {
            this.destinationSelector.addItem(player.getEurope().getId());
        }
        for (Colony colony : player.getColonyList()) {
            this.destinationSelector.addItem(colony.getId());
        }

        this.messagesBox
            = new JCheckBox(Messages.message("tradeRouteInputPanel.silence"));
        this.messagesBox.setSelected(tradeRoute.isSilent());
        this.messagesBox.addActionListener((ActionEvent ae) -> {
                tradeRoute.setSilent(messagesBox.isSelected());
            });

        this.addStopButton = Utility.localizedButton("tradeRouteInputPanel.addStop");
        this.addStopButton.addActionListener((ActionEvent ae) -> {
                addSelectedStops();
            });

        this.removeStopButton = Utility.localizedButton("tradeRouteInputPanel.removeStop");
        this.removeStopButton.addActionListener((ActionEvent ae) -> {
                deleteCurrentlySelectedStops();
            });

        final List<GoodsType> gtl = getSpecification().getGoodsTypeList();
        this.goodsPanel = new TradeRouteGoodsPanel(transform(gtl,
                GoodsType::isStorable, gt -> buildCargoLabel(gt)));
        this.goodsPanel.setTransferHandler(this.cargoHandler);
        this.goodsPanel.setEnabled(false);
        this.goodsPanel.addMouseListener(this.dropListener);

        this.cargoPanel = new TradeRouteCargoPanel();
        this.cargoPanel.setTransferHandler(this.cargoHandler);
        this.cargoPanel.addMouseListener(this.dropListener);

        JButton cancelButton = Utility.localizedButton("cancel");
        cancelButton.setActionCommand(CANCEL);
        cancelButton.addActionListener(this);
        setCancelComponent(cancelButton);

        add(Utility.localizedHeader("tradeRouteInputPanel.editRoute", false),
            "span, align center");
        add(tradeRouteView, "span 1 5, grow");
        add(nameLabel);
        add(this.tradeRouteName, "span");
        add(destinationLabel);
        add(this.destinationSelector, "span");
        add(this.messagesBox);
        add(this.addStopButton);
        add(this.removeStopButton, "span");
        add(this.goodsPanel, "span");
        add(this.cargoPanel, "span, height 80:, growy");
        add(okButton, "newline 20, span, split 2, tag ok");
        add(cancelButton, "tag cancel");

        // update cargo panel if stop is selected
        if (this.stopListModel.getSize() > 0) {
            this.stopList.setSelectedIndex(0);
            updateCargoPanel(this.stopListModel.firstElement());
        }
        // update buttons according to selection
        updateButtons();

        getGUI().restoreSavedSize(this, getPreferredSize());
    }

    /**
     * Update the cargo panel to show a given stop.
     *
     * @param stop The {@code TradeRouteStop} to select.
     */
    private void updateCargoPanel(TradeRouteStop stop) {
        this.cargoPanel.setLabels((stop == null) ? null
            : transform(stop.getCargo(), GoodsType::isStorable,
                        gt -> buildCargoLabel(gt)));
    }

    /**
     * Import new goods at the selected stops.
     *
     * @param gt The {@code GoodsType} to import.
     */
    private void enableImport(GoodsType gt) {
        if (gt == null) return;
        this.cargoPanel.addLabel(buildCargoLabel(gt));
        for (int stopIndex : this.stopList.getSelectedIndices()) {
            TradeRouteStop stop = this.stopListModel.get(stopIndex);
            stop.addCargo(gt);
        }
        this.stopList.revalidate();
        this.stopList.repaint();
    }

    /**
     * Cancel import of goods at the selected stops.
     *
     * @param gt The {@code GoodsType} to stop importing.
     */
    private void cancelImport(GoodsType gt) {
        this.cargoPanel.removeType(gt);
        for (int stopIndex : this.stopList.getSelectedIndices()) {
            TradeRouteStop stop = this.stopListModel.get(stopIndex);
            List<GoodsType> cargo = new ArrayList<>(stop.getCargo());
            if (removeInPlace(cargo, matchKey(gt))) {
                stop.setCargo(cargo);
            }
        }
        this.stopList.revalidate();
        this.stopList.repaint();
        this.cargoPanel.revalidate();
        this.cargoPanel.repaint();
    }

    /**
     * Add any stops selected in the destination selector.
     */
    private void addSelectedStops() {
        int startIndex = -1;
        int endIndex = -1;
        int sel = this.destinationSelector.getSelectedIndex();
        if (sel == 0) { // All colonies + Europe
            startIndex = 1;
            endIndex = this.destinationSelector.getItemCount();
        } else { // just one place
            startIndex = sel;
            endIndex = startIndex+1;
        }
        List<GoodsType> cargo = transform(cargoPanel.getComponents(),
            c -> c instanceof TradeRouteCargoLabel,
            c -> ((TradeRouteCargoLabel)c).getType());
        int maxIndex = this.stopList.getMaxSelectionIndex();
        for (int i = startIndex; i < endIndex; i++) {
            String id = this.destinationSelector.getItemAt(i);
            FreeColGameObject fcgo = getGame().getFreeColGameObject(id);
            if (fcgo instanceof Location) {
                TradeRouteStop stop
                    = new TradeRouteStop(getGame(), (Location)fcgo);
                stop.setCargo(cargo);
                if (maxIndex < 0) {
                    this.stopListModel.addElement(stop);
                } else {
                    maxIndex++;
                    this.stopListModel.add(maxIndex, stop);
                }
            }
        }
    }

    /**
     * Convenience function to build a new {@code TradeRouteCargoLabel}.
     *
     * @param gt The {@code GoodsType} for the label.
     * @return A {@code TradeRouteCargoLabel} for the goods type.
     */
    private TradeRouteCargoLabel buildCargoLabel(GoodsType gt) {
        final ImageLibrary lib = getImageLibrary();
        ImageIcon icon = new ImageIcon(lib.getScaledGoodsTypeImage(gt));
        TradeRouteCargoLabel label = new TradeRouteCargoLabel(icon, gt);
        label.setTransferHandler(this.cargoHandler);
        label.addMouseListener(this.dragListener);
        return label;
    }

    /**
     * Delete any stops currently selected in the stop list.
     */
    private void deleteCurrentlySelectedStops() {
        int count = 0;
        int lastIndex = 0;
        for (int index : this.stopList.getSelectedIndices()) {
            this.stopListModel.remove(index - count);
            count++;
            lastIndex = index;
        }

        // If the remaining list is non-empty, make sure that the
        // element beneath the last of the previously selected is
        // selected (ie, delete one of many, the one -under- the
        // deleted is selected) the user can then click in the list
        // once, and continue deleting without having to click in the
        // list again.
        if (this.stopListModel.getSize() > 0) {
            this.stopList.setSelectedIndex(lastIndex - count + 1);
        }
    }


    /**
     * Make sure the original route is invalid and remove this panel.
     *
     * Public so that this panel can be signalled to close if the parent
     * TradeRoutePanel is closed.
     */
    public void cancelTradeRoute() {
        this.newRoute.setName(null);
        getGUI().removeComponent(this);
    }

    /**
     * Enables the remove stop button if a stop is selected and disables it
     * otherwise.
     */
    private void updateButtons() {
        this.addStopButton.setEnabled(this.stopListModel.getSize() 
            < this.destinationSelector.getItemCount() - 1);
        this.removeStopButton.setEnabled(this.stopList.getSelectedIndices()
            .length > 0);
    }

    /**
     * Check that the trade route is valid.
     *
     * @return True if the trade route is valid.
     */
    private boolean verifyNewTradeRoute() {
        // Update the trade route with the current settings
        this.newRoute.setName(tradeRouteName.getText());
        this.newRoute.clearStops();
        for (int index = 0; index < this.stopListModel.getSize(); index++) {
            this.newRoute.addStop(this.stopListModel.get(index));
        }
        this.newRoute.setSilent(this.messagesBox.isSelected());

        StringTemplate err = this.newRoute.verify();
        if (err != null) {
            getGUI().showInformationPanel(err);
            this.newRoute.setName(null); // Mark as unacceptable
            return false;
        }
        return true;
    }


    // Interface ActionListener

    /**
     * {@inheritDoc}
     */
    @Override
    public void actionPerformed(ActionEvent ae) {
        final String command = ae.getActionCommand();
        if (command == null) return;
        switch (command) {
        case OK:
            if (!verifyNewTradeRoute()) return;
            // Return to TradeRoutePanel, which will add the route
            // if needed, and it is valid.
            super.actionPerformed(ae);
            break;
        case CANCEL:
            cancelTradeRoute();
            break;
        default:
            super.actionPerformed(ae);
            break;
        }
    }


    // Interface ListSelectionListener

    /**
     * {@inheritDoc}
     */
    @Override
    public void valueChanged(ListSelectionEvent e) {
        if (e.getValueIsAdjusting()) return;
        int[] idx = this.stopList.getSelectedIndices();
        if (idx.length > 0) {
            updateCargoPanel(this.stopListModel.get(idx[0]));
            this.goodsPanel.setEnabled(true);
        } else {
            this.goodsPanel.setEnabled(false);
        }
        updateButtons();
    }


    // Override Component

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeNotify() {
        this.cargoHandler = null;
        this.dragListener = null;
        this.dropListener = null;
        this.stopListModel.clear();
        this.stopListModel = null;
        this.stopList = null;
        this.tradeRouteName = null;
        this.destinationSelector = null;
        this.messagesBox = null;
        this.addStopButton = null;
        this.removeStopButton = null;
        this.cargoPanel = null;

        super.removeNotify();
    }
}