/**
 *  Copyright (C) 2002-2022   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.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import javax.swing.AbstractCellEditor;
import javax.swing.DefaultCellEditor;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JTable;
import javax.swing.ListCellRenderer;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.control.PreGameController;
import net.sf.freecol.client.gui.GUI;
import net.sf.freecol.client.gui.action.ColopediaAction.PanelType;
import net.sf.freecol.client.gui.plaf.FreeColComboBoxRenderer;
import net.sf.freecol.common.i18n.Messages;
import net.sf.freecol.common.model.EuropeanNationType;
import net.sf.freecol.common.model.Nation;
import net.sf.freecol.common.model.NationOptions;
import net.sf.freecol.common.model.NationOptions.Advantages;
import net.sf.freecol.common.model.NationOptions.NationState;
import net.sf.freecol.common.model.NationType;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.StringTemplate;
import static net.sf.freecol.common.util.CollectionUtils.*;


/**
 * The table of players.
 */
public final class PlayersTable extends JTable {

    /**
     * A table cell editor that can be used to select a nation.
     */
    private static class AdvantageCellEditor extends DefaultCellEditor {

        private final JComboBox<EuropeanNationType> box;

        /**
         * Internal constructor.
         *
         * @param box The {@code JComboBox} to edit.
         */
        private AdvantageCellEditor(JComboBox<EuropeanNationType> box) {
            super(box);

            this.box = box;
        }

        /**
         * A standard constructor.
         *
         * @param nationTypes The list of {@code EuropeanNationType}s.
         */
        public AdvantageCellEditor(List<EuropeanNationType> nationTypes) {
            this(new JComboBox<>(nationTypes
                    .toArray(new EuropeanNationType[0])));

            this.box.setRenderer(new FreeColComboBoxRenderer<EuropeanNationType>());
        }


        // Implement DefaultCellEditor

        /**
         * {@inheritDoc}
         */
        @Override
        public Object getCellEditorValue() {
            return ((JComboBox)getComponent()).getSelectedItem();
        }
    }

    private static class AdvantageCellRenderer extends JLabel
        implements TableCellRenderer {

        /** The national advantages type. */
        private final Advantages advantages;

 
        /**
         * The default constructor.
         *
         * @param advantages The type of national {@code Advantages}.
         */
        public AdvantageCellRenderer(Advantages advantages) {
            this.advantages = advantages;
        }


        // Implement TableCellRenderer

        /**
         * {@inheritDoc}
         */
        @Override
        public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus,
            int row, int column) {
            final Player player = (Player)table.getValueAt(row,
                PlayersTable.PLAYER_COLUMN);
            final NationType nationType = ((Nation)table.getValueAt(row,
                    PlayersTable.NATION_COLUMN)).getType();
            JLabel label;
            switch (advantages) {
            case SELECTABLE:
                return Utility.localizedLabel(Messages.nameKey((player == null)
                        ? nationType
                        : player.getNationType()));
            case FIXED:
                label = Utility.localizedLabel(Messages.nameKey(nationType));
                break;
            case NONE:
            default:
                label = Utility.localizedLabel("none");
                break;
            }
            label.setForeground((player != null && player.isReady())
                ? Color.GRAY
                : table.getForeground());
            label.setBackground(table.getBackground());
            Utility.localizeToolTip(this, StringTemplate
                .key(advantages.getShortDescriptionKey()));
            return label;
        }
    }

    private static class AvailableCellRenderer extends JLabel
        implements TableCellRenderer {

        private final JComboBox<NationState> box
            = new JComboBox<>(NationState.values());


        /**
         * The default constructor.
         */
        public AvailableCellRenderer() {
            box.setRenderer(new NationStateRenderer());
        }


        // Implement TableCellRenderer

        /**
         * {@inheritDoc}
         */
        @Override
        public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus,
            int row, int column) {
            if (value != null) { // FIXME: Why does this happen?
                box.setSelectedItem(value);
                final NationState nationState = (NationState)value;
                Utility.localizeToolTip(box, StringTemplate
                    .key(nationState.getShortDescriptionKey()));
            }
            return box;
        }
    }

    private final class AvailableCellEditor extends AbstractCellEditor
        implements TableCellEditor {

        private final JComboBox<NationState> aiStateBox
            = new JComboBox<>(new NationState[] {
                    NationState.AI_ONLY,
                    NationState.NOT_AVAILABLE
                });
        private final JComboBox<NationState> allStateBox
            = new JComboBox<>(NationState.values());
        private JComboBox<NationState> activeBox = null;


        public AvailableCellEditor() {
            ActionListener listener = (ActionEvent ae) -> {
                stopCellEditing();
            };
            aiStateBox.setRenderer(new NationStateRenderer());
            aiStateBox.addActionListener(listener);
            allStateBox.setRenderer(new NationStateRenderer());
            allStateBox.addActionListener(listener);
        }

        private JComboBox<NationState> getActiveBox(int row) {
            NationType nationType = ((Nation) getValueAt(row, NATION_COLUMN))
                .getType();
            this.activeBox = (nationType instanceof EuropeanNationType)
                ? this.allStateBox
                : this.aiStateBox;
            return this.activeBox;
        }

        // Implement AbstractCellEditor

        /**
         * {@inheritDoc}
         */
        @Override
        public Component getTableCellEditorComponent(JTable table,
            Object value, boolean isSelected, int row, int column) {
            return getActiveBox(row);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Object getCellEditorValue() {
            return (this.activeBox == null) ? null
                : this.activeBox.getSelectedItem();
        }
    }

    private static class HeaderListener extends MouseAdapter {

        private final JTableHeader header;

        private final HeaderRenderer renderer;


        public HeaderListener(JTableHeader header, HeaderRenderer renderer) {
            this.header = header;
            this.renderer = renderer;
        }

        @Override
        public void mousePressed(MouseEvent e) {
            int col = header.columnAtPoint(e.getPoint());
            renderer.setPressedColumn(col);
            header.repaint();
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            renderer.setPressedColumn(HeaderRenderer.NO_COLUMN);
            header.repaint();
        }
    }

    private static class HeaderRenderer implements TableCellRenderer {

        private static final int NO_COLUMN = -1;
        private int pressedColumn = NO_COLUMN;
        private final Component[] components;

        public HeaderRenderer(Component... components) {
            this.components = components;
        }


        // Implement TableCellEditor

        /**
         * {@inheritDoc}
         */
        @Override
        public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus,
            int row, int column) {
            if (components[column] instanceof JButton) {
                boolean isPressed = (column == pressedColumn);
                ((JButton)components[column]).getModel().setPressed(isPressed);
                ((JButton)components[column]).getModel().setArmed(isPressed);
            }
            return components[column];
        }

        public void setPressedColumn(int column) {
            pressedColumn = column;
        }
    }

    private class NationCellRenderer extends JLabel
        implements TableCellRenderer {

        // Implement TableCellEditor

        /**
         * {@inheritDoc}
         */
        @Override
        public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus,
            int row, int column) {
            Nation nation = (Nation) value;
            setText(Messages.message(StringTemplate.template("countryName")
                    .add("%nation%", Messages.nameKey(nation.getId()))));
            setIcon(new ImageIcon(gui.getFixedImageLibrary()
                    .getUnscaledSmallerNationImage(nation)));
            return this;
        }
    }

    private static class NationStateRenderer extends JLabel
        implements ListCellRenderer<NationState> {

        // Implement ListCellEditor<NationState>

        /**
         * {@inheritDoc}
         */
        @Override
        public Component getListCellRendererComponent(JList<? extends NationState> list,
                                                      NationState value,
                                                      int index,
                                                      boolean isSelected,
                                                      boolean cellHasFocus) {
            setText(Messages.getName(value));
            return this;
        }
    }

    private static class PlayerCellRenderer implements TableCellRenderer {

        private final JLabel label = new JLabel();
        private final JButton button = Utility.localizedButton("select");


        public PlayerCellRenderer() {
            label.setHorizontalAlignment(JLabel.CENTER);
            Utility.padBorder(button, 5, 10, 5, 10);
        }


        // Implement TableCellRenderer

        /**
         * {@inheritDoc}
         */
        @Override
        public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus,
            int row, int column) {
            Player player = (Player)value;
            if (player == null) {
                NationType nationType
                    = (NationType)table.getValueAt(row, ADVANTAGE_COLUMN);
                if (nationType instanceof EuropeanNationType) {
                    NationState nationState = (NationState)table
                        .getValueAt(row, AVAILABILITY_COLUMN);
                    if (nationState == NationState.AVAILABLE) {
                        return button;
                    }
                }
                Nation nation = (Nation) table.getValueAt(row, NATION_COLUMN);
                label.setText(nation.getRulerName());
            } else {
                label.setText(player.getName());
            }
            return label;
        }
    }

    private static final class PlayerCellEditor extends AbstractCellEditor
        implements TableCellEditor {

        private final JButton button = Utility.localizedButton("select");


        public PlayerCellEditor() {
            button.addActionListener((ActionEvent ae) -> {
                    fireEditingStopped();
                });
        }


        /**
         * {@inheritDoc}
         */
        @Override
        public Component getTableCellEditorComponent(JTable table,
            Object value, boolean isSelected, int row, int column) {
            return button;
        }

        @Override
        public Object getCellEditorValue() {
            return Boolean.TRUE;
        }
    }

    /**
     * The TableModel for the players table.
     */
    private static class PlayersTableModel extends AbstractTableModel {

        private final PreGameController preGameController;

        private final NationOptions nationOptions;

        private final Player thisPlayer;

        private final List<Nation> nations;

        private final Map<Nation, Player> nationMap;


        /**
         * Create a new PlayersTableModel.
         *
         * @param preGameController The {@code PreGameController}
         *     to use notify of updates.
         * @param nationOptions The current {@code NationOptions}.
         * @param thisPlayer The {@code Player} that owns the client.
         */
        public PlayersTableModel(PreGameController preGameController,
                                 NationOptions nationOptions,
                                 Player thisPlayer) {
            final Specification spec = thisPlayer.getSpecification();
            this.preGameController = preGameController;
            this.nationOptions = nationOptions;
            this.thisPlayer = thisPlayer;
            final Predicate<Nation> nationPred = n -> !n.isUnknownEnemy()
                && nationOptions.getNations().get(n) != null;
            this.nations = transform(spec.getNations(), nationPred);
            this.nationMap = new HashMap<>(this.nations.size());
            for (Nation n : this.nations) this.nationMap.put(n, null);
            this.nationMap.put(thisPlayer.getNation(), thisPlayer);
        }


        /**
         * Update the nation map following any change.
         */
        public void update() {
            for (Nation n : this.nations) nationMap.put(n, null);
            for (Player p : thisPlayer.getGame().getLivePlayerList()) {
                nationMap.put(p.getNation(), p);
            }
            fireTableDataChanged();
        }

        /**
         * Gets the class of the objects in the given column.
         *
         * @param column The column to return the class of.
         * @return The {@code Class} of the objects in the given column.
         */
        @Override
        public Class<?> getColumnClass(int column) {
            switch (column) {
            case NATION_COLUMN:
                return Nation.class;
            case AVAILABILITY_COLUMN:
                return NationOptions.NationState.class;
            case ADVANTAGE_COLUMN:
                return NationType.class;
            case COLOR_COLUMN:
                return Color.class;
            case PLAYER_COLUMN:
                return Player.class;
            }
            return String.class;
        }

        /**
         * Get the number of columns in this table.
         *
         * @return The number of columns.
         */
        @Override
        public int getColumnCount() {
            return columnNames.length;
        }

        /**
         * Get the name of a column.
         *
         * @param column The column number to look up.
         * @return The name of the specified column.
         */
        @Override
        public String getColumnName(int column) {
            return columnNames[column];
        }

        /**
         * Get the number of rows in this table.
         *
         * @return The number of rows.
         */
        @Override
        public int getRowCount() {
            return nations.size();
        }

        /**
         * Get the value at the requested location.
         *
         * @param row The requested row.
         * @param column The requested column.
         * @return The value at the requested location.
         */
        @Override
        public Object getValueAt(int row, int column) {
            if (row >= 0 && row < getRowCount()
                && column >= 0 && column < getColumnCount()) {
                Nation nation = nations.get(row);
                switch (column) {
                case NATION_COLUMN:
                    return nation;
                case AVAILABILITY_COLUMN:
                    return nationOptions.getNationState(nation);
                case ADVANTAGE_COLUMN:
                    return (nationMap.get(nation) == null) ? nation.getType()
                        : nationMap.get(nation).getNationType();
                case COLOR_COLUMN:
                    return nation.getColor();
                case PLAYER_COLUMN:
                    return nationMap.get(nation);
                }
            }
            return null;
        }

        /**
         * Is a cell editable?
         *
         * @param row The specified row.
         * @param column The specified column.
         * @return True if the specified cell is editable.
         */
        @Override
        public boolean isCellEditable(int row, int column) {
            if (row >= 0 && row < getRowCount()) {
                Nation nation = nations.get(row);
                boolean ownRow = thisPlayer == nationMap.get(nation)
                    && !thisPlayer.isReady();
                switch (column) {
                case AVAILABILITY_COLUMN:
                    return !ownRow && thisPlayer.isAdmin();
                case ADVANTAGE_COLUMN:
                    return nation.getType() instanceof EuropeanNationType
                        && ownRow;
                case COLOR_COLUMN:
                    // Allow a player to change all the colors. 
                    // This is an accessibility issue for users with a
                    // colour vision deficiency.  Better to support them
                    // and just admit that if someone wants to be a pain
                    // and set all the colours the same, they can.
                    return nation.getType() instanceof EuropeanNationType;
                case PLAYER_COLUMN:
                    return nation.getType() instanceof EuropeanNationType
                        && nationMap.get(nation) == null;
                }
            }
            return false;
        }

        /**
         * Sets the value at the specified location.
         *
         * @param value The new value.
         * @param row The specified row.
         * @param column The specified column.
         */
        @Override
        public void setValueAt(Object value, int row, int column) {
            if (row >= 0 && row < getRowCount()
                && column > 0 && column < getColumnCount()) {
                // Column 0 can't be updated.
                Nation nation = nations.get(row);
                switch (column) {
                case ADVANTAGE_COLUMN:
                    preGameController.setNationType((NationType)value);
                    update();
                    break;
                case AVAILABILITY_COLUMN:
                    preGameController.setAvailable(nations.get(row),
                        (NationState)value);
                    update();
                    break;
                case COLOR_COLUMN:
                    preGameController.setColor(nation, (Color)value);
                    update();
                    break;
                case PLAYER_COLUMN:
                    if (nationOptions.getNationState(nation)
                        == NationState.AVAILABLE) {
                        preGameController.setNation(nation);
                        preGameController.setNationType(nation.getType());
                        update();
                    }
                    break;
                default:
                    break;
                }
                fireTableCellUpdated(row, column);
            }
        }
    }


    public static final int NATION_COLUMN = 0,
        AVAILABILITY_COLUMN = 1,
        ADVANTAGE_COLUMN = 2,
        COLOR_COLUMN = 3,
        PLAYER_COLUMN = 4;

    private static final String[] columnNames = {
        Messages.message("nation"),
        Messages.message("playersTable.availability"),
        Messages.message("playersTable.advantage"),
        Messages.message("color"),
        Messages.message("player")
    };

    /** A link to the gui. */
    private final GUI gui;


    /**
     * Creates a players table.
     *
     * @param freeColClient The {@code FreeColClient} for the game.
     * @param nationOptions The {@code NationOptions} for this game.
     * @param myPlayer The client {@code Player}.
     */
    public PlayersTable(final FreeColClient freeColClient,
                        NationOptions nationOptions, Player myPlayer) {
        super();

        gui = freeColClient.getGUI();
        final Specification spec = freeColClient.getGame().getSpecification();

        setModel(new PlayersTableModel(freeColClient.getPreGameController(),
                 nationOptions, myPlayer));
        setRowHeight(47);

        JButton nationButton = Utility.localizedButton("nation");
        nationButton.addActionListener((ActionEvent ae) -> {
                gui.showColopediaPanel(PanelType.NATIONS.getKey());
            });

        JLabel availabilityLabel = Utility.localizedLabel("playersTable.availability");
        JButton advantageButton = Utility.localizedButton("playersTable.advantage");
        advantageButton.addActionListener((ActionEvent ae) -> {
                gui.showColopediaPanel(PanelType.NATION_TYPES.getKey());
            });

        JLabel colorLabel = Utility.localizedLabel("color");
        JLabel playerLabel = Utility.localizedLabel("player");

        DefaultTableCellRenderer dtcr = new DefaultTableCellRenderer();
        dtcr.setOpaque(false);

        HeaderRenderer renderer = new HeaderRenderer(nationButton,
            availabilityLabel, advantageButton, colorLabel, playerLabel);
        JTableHeader header = getTableHeader();
        header.addMouseListener(new HeaderListener(header, renderer));

        final TableColumnModel tcm = getColumnModel();
        
        TableColumn nationColumn = tcm.getColumn(NATION_COLUMN);
        nationColumn.setCellRenderer(new NationCellRenderer());
        nationColumn.setHeaderRenderer(renderer);
        nationColumn.setPreferredWidth(2 * tcm.getTotalColumnWidth()
            / tcm.getColumnCount());

        TableColumn availableColumn = tcm.getColumn(AVAILABILITY_COLUMN);
        availableColumn.setCellRenderer(new AvailableCellRenderer());
        availableColumn.setCellEditor(new AvailableCellEditor());

        TableColumn advantagesColumn = tcm.getColumn(ADVANTAGE_COLUMN);
        switch (nationOptions.getNationalAdvantages()) {
        case SELECTABLE:
            advantagesColumn.setCellEditor(new AdvantageCellEditor(spec
                    .getEuropeanNationTypes()));
            break;
        case FIXED:
            break; // Do nothing
        case NONE:
            spec.clearEuropeanNationalAdvantages();
            break;
        default:
            break;
        }
        advantagesColumn.setCellRenderer(new AdvantageCellRenderer(nationOptions.getNationalAdvantages()));
        advantagesColumn.setHeaderRenderer(renderer);

        TableColumn colorsColumn = tcm.getColumn(COLOR_COLUMN);
        colorsColumn.setCellRenderer(new ColorCellRenderer(true));
        colorsColumn.setCellEditor(new ColorCellEditor(freeColClient));

        TableColumn playerColumn = tcm.getColumn(PLAYER_COLUMN);
        playerColumn.setCellEditor(new PlayerCellEditor());
        playerColumn.setCellRenderer(new PlayerCellRenderer());
    }

    public void update() {
        ((PlayersTableModel)getModel()).update();
    }
}
