/*
 * TaxaPanel.java
 *
 * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
 *
 * This file is part of BEAST.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership and licensing.
 *
 * BEAST is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 *  BEAST 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with BEAST; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

package dr.app.oldbeauti;

import dr.evolution.util.Taxa;
import dr.evolution.util.Taxon;
import dr.evolution.util.TaxonList;
import jam.framework.Exportable;
import jam.panels.ActionPanel;
import jam.table.TableRenderer;
import jam.util.IconUtils;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.BorderUIResource;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author			Andrew Rambaut
 * @author			Alexei Drummond
 * @version			$Id: TaxaPanel.java,v 1.1 2006/09/05 13:29:34 rambaut Exp $
 */
public class TaxaPanel extends JPanel implements Exportable {


    /**
     *
     */
    private static final long serialVersionUID = -3138832889782090814L;

    private final String TAXON_SET_DEFAULT = "taxon set...";

    BeautiFrame frame = null;

    BeautiOptions options = null;

    private TaxonList taxa = null;
    private JTable taxonSetsTable = null;
    private TaxonSetsTableModel taxonSetsTableModel = null;

    private JPanel taxonSetEditingPanel = null;

    private Taxa currentTaxonSet = null;

    private List<Taxon> includedTaxa = new ArrayList<Taxon>();
    private List<Taxon> excludedTaxa = new ArrayList<Taxon>();

    private JTable excludedTaxaTable = null;
    private TaxaTableModel excludedTaxaTableModel = null;
    private JComboBox excludedTaxonSetsComboBox = null;
    private boolean excludedSelectionChanging = false;

    private JTable includedTaxaTable = null;
    private TaxaTableModel includedTaxaTableModel = null;
    private JComboBox includedTaxonSetsComboBox = null;
    private boolean includedSelectionChanging = false;

    private static int taxonSetCount = 0;

    public TaxaPanel(BeautiFrame parent) {

        this.frame = parent;

        Icon includeIcon = null, excludeIcon = null;
        try {
            includeIcon = new ImageIcon(IconUtils.getImage(this.getClass(), "images/include.png"));
            excludeIcon = new ImageIcon(IconUtils.getImage(this.getClass(), "images/exclude.png"));
        } catch (Exception e) {
            // do nothing
        }

        // Taxon Sets
        taxonSetsTableModel = new TaxonSetsTableModel();
        taxonSetsTable = new JTable(taxonSetsTableModel);
        final TableColumnModel model = taxonSetsTable.getColumnModel();
        final TableColumn tableColumn0 = model.getColumn(0);
        tableColumn0.setCellRenderer(new TableRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
        tableColumn0.setMinWidth(20);

        //final TableColumn tableColumn1 = model.getColumn(1);

        taxonSetsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent evt) { taxonSetsTableSelectionChanged(); }
        });

        JScrollPane scrollPane1 = new JScrollPane(taxonSetsTable,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        ActionPanel actionPanel1 = new ActionPanel(false);
        actionPanel1.setAddAction(addTaxonSetAction);
        actionPanel1.setRemoveAction(removeTaxonSetAction);

        addTaxonSetAction.setEnabled(false);
        removeTaxonSetAction.setEnabled(false);

        JPanel controlPanel1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
        controlPanel1.add(actionPanel1);

        // Excluded Taxon List
        excludedTaxaTableModel = new TaxaTableModel(false);
        excludedTaxaTable = new JTable(excludedTaxaTableModel);

        excludedTaxaTable.getColumnModel().getColumn(0).setCellRenderer(
                new TableRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
        excludedTaxaTable.getColumnModel().getColumn(0).setMinWidth(20);

        excludedTaxaTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent evt) { excludedTaxaTableSelectionChanged(); }
        });

        JScrollPane scrollPane2 = new JScrollPane(excludedTaxaTable,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        Box panel1 = new Box(BoxLayout.X_AXIS);
        panel1.add(new JLabel("Select: "));
        panel1.setOpaque(false);
        excludedTaxonSetsComboBox = new JComboBox(new String[] { TAXON_SET_DEFAULT });
        excludedTaxonSetsComboBox.setOpaque(false);
        panel1.add(excludedTaxonSetsComboBox);

        JPanel buttonPanel = createAddRemoveButtonPanel(includeTaxonAction, includeIcon, "Include selected taxa in the taxon set",
                excludeTaxonAction, excludeIcon, "Exclude selected taxa from the taxon set",
                javax.swing.BoxLayout.Y_AXIS);

        // Included Taxon List
        includedTaxaTableModel = new TaxaTableModel(true);
        includedTaxaTable = new JTable(includedTaxaTableModel);

        includedTaxaTable.getColumnModel().getColumn(0).setCellRenderer(
                new TableRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
        includedTaxaTable.getColumnModel().getColumn(0).setMinWidth(20);

        includedTaxaTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent evt) { includedTaxaTableSelectionChanged(); }
        });
        includedTaxaTable.doLayout();

        JScrollPane scrollPane3 = new JScrollPane(includedTaxaTable,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        Box panel2 = new Box(BoxLayout.X_AXIS);
        panel2.add(new JLabel("Select: "));
        panel2.setOpaque(false);
        includedTaxonSetsComboBox = new JComboBox(new String[] { TAXON_SET_DEFAULT });
        includedTaxonSetsComboBox.setOpaque(false);
        panel2.add(includedTaxonSetsComboBox);

        taxonSetEditingPanel = new JPanel();
        taxonSetEditingPanel.setBorder(BorderFactory.createTitledBorder("Taxon Set: none selected"));
        taxonSetEditingPanel.setOpaque(false);
        taxonSetEditingPanel.setLayout(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();

        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 0.5;
        c.weighty = 1;
        c.fill = GridBagConstraints.BOTH;
        c.anchor = GridBagConstraints.CENTER;
        c.insets = new Insets(12,12,4,0);
        taxonSetEditingPanel.add(scrollPane2, c);

        c.gridx = 0;
        c.gridy = 1;
        c.weightx = 0.5;
        c.weighty = 0;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.anchor = GridBagConstraints.CENTER;
        c.insets = new Insets(0,12,12,0);
        taxonSetEditingPanel.add(panel1, c);

        c.gridx = 1;
        c.gridy = 0;
        c.weightx = 0;
        c.weighty = 1;
        c.gridheight = 2;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.CENTER;
        c.insets = new Insets(12,2,12,4);
        taxonSetEditingPanel.add(buttonPanel, c);

        c.gridx = 2;
        c.gridy = 0;
        c.weightx = 0.5;
        c.weighty = 1;
        c.gridheight = 1;
        c.fill = GridBagConstraints.BOTH;
        c.anchor = GridBagConstraints.CENTER;
        c.insets = new Insets(12,0,4,12);
        taxonSetEditingPanel.add(scrollPane3, c);

        c.gridx = 2;
        c.gridy = 1;
        c.weightx = 0.5;
        c.weighty = 0;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.anchor = GridBagConstraints.CENTER;
        c.insets = new Insets(0,0,12,12);
        taxonSetEditingPanel.add(panel2, c);

        JPanel panel3 = new JPanel();
        panel3.setOpaque(false);
        panel3.setLayout(new GridBagLayout());
        c = new GridBagConstraints();

        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 0.4;
        c.weighty = 1;
        c.fill = GridBagConstraints.BOTH;
        c.anchor = GridBagConstraints.CENTER;
        c.insets = new Insets(0,0,2,12);
        panel3.add(scrollPane1, c);

        c.gridx = 0;
        c.gridy = 1;
        c.weightx = 0;
        c.weighty = 0;
        c.fill = GridBagConstraints.NONE;
        c.anchor = GridBagConstraints.WEST;
        c.insets = new Insets(2,0,0,12);
        panel3.add(actionPanel1, c);

        c.gridx = 1;
        c.gridy = 0;
        c.weightx = 0.6;
        c.weighty = 1;
        c.fill = GridBagConstraints.BOTH;
        c.anchor = GridBagConstraints.CENTER;
        c.insets = new Insets(0,0,0,0);
        panel3.add(taxonSetEditingPanel, c);

        setOpaque(false);
        setBorder(new BorderUIResource.EmptyBorderUIResource(new Insets(12, 12, 12, 12)));
        setLayout(new BorderLayout(0,0));
        add(panel3, BorderLayout.CENTER);

//		taxonSetsTable.addMouseListener(new MouseAdapter() {
//			public void mouseClicked(MouseEvent e) {
//				if (e.getClickCount() == 2) {
//					JTable target = (JTable)e.getSource();
//					int row = target.getSelectedRow();
//					taxonSetsTableDoubleClicked(row);
//				}
//			}
//		});

        includedTaxaTable.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    includeSelectedTaxa();
                }
            }
        });
        excludedTaxaTable.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    excludeSelectedTaxa();
                }
            }
        });

        includedTaxaTable.addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent focusEvent) {
                excludedTaxaTable.clearSelection();
            }
        });
        excludedTaxaTable.addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent focusEvent) {
                includedTaxaTable.clearSelection();
            }
        });

        includedTaxaTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                if (!includedSelectionChanging && includedTaxonSetsComboBox.getSelectedIndex() != 0) {
                    includedTaxonSetsComboBox.setSelectedIndex(0);
                }
            }
        });
        includedTaxonSetsComboBox.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                includedSelectionChanging = true;
                includedTaxaTable.clearSelection();
                if (includedTaxonSetsComboBox.getSelectedIndex() > 0) {
                    Taxa taxa = (Taxa)includedTaxonSetsComboBox.getSelectedItem();
                    for (int i =0; i < taxa.getTaxonCount(); i++) {
                        Taxon taxon = taxa.getTaxon(i);
                        int index = includedTaxa.indexOf(taxon);
                        includedTaxaTable.getSelectionModel().addSelectionInterval(index, index);

                    }
                }
                includedSelectionChanging = false;
            }
        });

        excludedTaxaTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                if (!excludedSelectionChanging && excludedTaxonSetsComboBox.getSelectedIndex() != 0) {
                    excludedTaxonSetsComboBox.setSelectedIndex(0);
                }
            }
        });
        excludedTaxonSetsComboBox.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                excludedSelectionChanging = true;
                excludedTaxaTable.clearSelection();
                if (excludedTaxonSetsComboBox.getSelectedIndex() > 0) {
                    Taxa taxa = (Taxa)excludedTaxonSetsComboBox.getSelectedItem();
                    for (int i =0; i < taxa.getTaxonCount(); i++) {
                        Taxon taxon = taxa.getTaxon(i);
                        int index = excludedTaxa.indexOf(taxon);
                        excludedTaxaTable.getSelectionModel().addSelectionInterval(index, index);

                    }
                }
                excludedSelectionChanging = false;
            }
        });

        taxonSetsTable.doLayout();
        includedTaxaTable.doLayout();
        excludedTaxaTable.doLayout();
    }

    private void taxonSetChanged() {
        currentTaxonSet.removeAllTaxa();
        for (Taxon anIncludedTaxa : includedTaxa) {
            currentTaxonSet.addTaxon(anIncludedTaxa);
        }

        setupTaxonSetsComboBoxes();

        if (options.taxonSetsMono.get(currentTaxonSet) != null &&
                options.taxonSetsMono.get(currentTaxonSet) &&
                !checkCompatibility(currentTaxonSet)) {
            options.taxonSetsMono.put(currentTaxonSet, Boolean.FALSE);
        }

        frame.taxonSetsChanged();
    }

    public void setOptions(BeautiOptions options) {

        this.options = options;

        taxa = options.alignment;
        if (taxa == null) {
            addTaxonSetAction.setEnabled(false);
            removeTaxonSetAction.setEnabled(false);
        } else {
            addTaxonSetAction.setEnabled(true);
        }

        taxonSetsTableSelectionChanged();
        taxonSetsTableModel.fireTableDataChanged();
    }

    public void getOptions(BeautiOptions options) {
//		options.datesUnits = unitsCombo.getSelectedIndex();
//		options.datesDirection = directionCombo.getSelectedIndex();
//		options.translation = translationCombo.getSelectedIndex();
    }

    public JComponent getExportableComponent() {
        return taxonSetsTable;
    }

    private void taxonSetsTableSelectionChanged() {
        int[] rows = taxonSetsTable.getSelectedRows();
        if (rows.length == 0) {
            removeTaxonSetAction.setEnabled(false);
        } else if (rows.length == 1) {
            currentTaxonSet = options.taxonSets.get(rows[0]);
            setCurrentTaxonSet(currentTaxonSet);
            removeTaxonSetAction.setEnabled(true);
        } else {
            setCurrentTaxonSet(null);
            removeTaxonSetAction.setEnabled(true);
        }
    }

//	private void taxonSetsTableDoubleClicked(int row) {
//		currentTaxonSet = (Taxa)taxonSets.get(row);
//
//		Collections.sort(taxonSets);
//		taxonSetsTableModel.fireTableDataChanged();
//
//		setCurrentTaxonSet(currentTaxonSet);
//
//		int sel = taxonSets.indexOf(currentTaxonSet);
//		taxonSetsTable.setRowSelectionInterval(sel, sel);
//	}

    Action addTaxonSetAction = new AbstractAction("+") {

        /**
         *
         */
        private static final long serialVersionUID = 20273987098143413L;

        public void actionPerformed(ActionEvent ae) {
            taxonSetCount ++;
            currentTaxonSet = new Taxa("untitled" + taxonSetCount);

            options.taxonSets.add(currentTaxonSet);
            Collections.sort(options.taxonSets);

            options.taxonSetsMono.put(currentTaxonSet, Boolean.FALSE);

            taxonSetsTableModel.fireTableDataChanged();

            int sel = options.taxonSets.indexOf(currentTaxonSet);
            taxonSetsTable.setRowSelectionInterval(sel, sel);

            taxonSetChanged();
        }
    };

    Action removeTaxonSetAction = new AbstractAction("-") {

        /**
         *
         */
        private static final long serialVersionUID = 6077578872870122265L;

        public void actionPerformed(ActionEvent ae) {
            int row = taxonSetsTable.getSelectedRow();
            if (row != -1) {
                Taxa taxa = options.taxonSets.remove(row);
                options.taxonSetsMono.remove(taxa);
            }
            taxonSetChanged();

            taxonSetsTableModel.fireTableDataChanged();

            if (row >= options.taxonSets.size()) {
                row = options.taxonSets.size() - 1;
            }
            if (row >= 0) {
                taxonSetsTable.setRowSelectionInterval(row, row);
            } else {
                setCurrentTaxonSet(null);
            }
        }
    };

    private void setCurrentTaxonSet(Taxa taxonSet) {

        this.currentTaxonSet = taxonSet;

        includedTaxa.clear();
        excludedTaxa.clear();

        if (currentTaxonSet != null) {
            for (int i = 0; i < taxonSet.getTaxonCount(); i++) {
                includedTaxa.add(taxonSet.getTaxon(i));
            }
            Collections.sort(includedTaxa);

            for (int i = 0; i < taxa.getTaxonCount(); i++) {
                excludedTaxa.add(taxa.getTaxon(i));
            }
            excludedTaxa.removeAll(includedTaxa);
            Collections.sort(excludedTaxa);
        }

        setTaxonSetTitle();

        setupTaxonSetsComboBoxes();

        includedTaxaTableModel.fireTableDataChanged();
        excludedTaxaTableModel.fireTableDataChanged();
    }

    private void setTaxonSetTitle() {

        if (currentTaxonSet == null) {
            taxonSetEditingPanel.setBorder(BorderFactory.createTitledBorder(""));
            taxonSetEditingPanel.setEnabled(false);
        } else {
            taxonSetEditingPanel.setEnabled(true);
            taxonSetEditingPanel.setBorder(BorderFactory.createTitledBorder("Taxon Set: " + currentTaxonSet.getId()));
        }
    }


    private void setupTaxonSetsComboBoxes() {
        setupTaxonSetsComboBox(excludedTaxonSetsComboBox, excludedTaxa);
        excludedTaxonSetsComboBox.setSelectedIndex(0);
        setupTaxonSetsComboBox(includedTaxonSetsComboBox, includedTaxa);
        includedTaxonSetsComboBox.setSelectedIndex(0);
    }

    private void setupTaxonSetsComboBox(JComboBox comboBox, List availableTaxa) {
        comboBox.removeAllItems();

        comboBox.addItem(TAXON_SET_DEFAULT);
        for (Taxa taxa : options.taxonSets) {
            if (taxa != currentTaxonSet) {
                if (isCompatible(taxa, availableTaxa)) {
                    comboBox.addItem(taxa);
                }
            }
        }
    }

    /**
     * Returns true if taxa are all found in availableTaxa
     * @param taxa
     * @param availableTaxa
     * @return true if the taxa are all found in availableTaxa
     */
    private boolean isCompatible(Taxa taxa, List availableTaxa) {

        for (int i = 0; i < taxa.getTaxonCount(); i++) {
            Taxon taxon = taxa.getTaxon(i);
            if (!availableTaxa.contains(taxon)) {
                return false;
            }
        }
        return true;
    }

    private boolean checkCompatibility(Taxa taxa) {
        for (Taxa taxa2 : options.taxonSets) {
            if (taxa2 != taxa && options.taxonSetsMono.get(taxa2)) {
                if (taxa.containsAny(taxa2) && !taxa.containsAll(taxa2) && !taxa2.containsAll(taxa)) {
                    JOptionPane.showMessageDialog(frame,
                            "You cannot enforce monophyly on this taxon set \n" +
                                    "because it is not compatible with another taxon \n" +
                                    "set, " + taxa2.getId() + ", for which monophyly is\n" +
                                    "enforced.",
                            "Warning",
                            JOptionPane.WARNING_MESSAGE);
                    return false;
                }
            }
        }
        return true;
    }

    class TaxonSetsTableModel extends AbstractTableModel {

        /**
         *
         */
        private static final long serialVersionUID = 3318461381525023153L;

        public TaxonSetsTableModel() {
        }

        public int getColumnCount() {
            return 2;
        }

        public int getRowCount() {
            if (options == null) return 0;
            return options.taxonSets.size();
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            Taxa taxonSet = options.taxonSets.get(rowIndex);
            switch(columnIndex) {
                case 0: return taxonSet.getId();
                case 1: return options.taxonSetsMono.get(taxonSet);
            }
            return null;
        }

        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
            Taxa taxonSet = options.taxonSets.get(rowIndex);
            switch(columnIndex) {
                case 0: {
                    taxonSet.setId(aValue.toString());
                    setTaxonSetTitle();
                    break;
                }
                case 1: {
                    if ((Boolean)aValue) {
                        Taxa taxa = options.taxonSets.get(rowIndex);
                        if (checkCompatibility(taxa)) {
                            options.taxonSetsMono.put(taxonSet, (Boolean)aValue);
                        }
                    } else {
                        options.taxonSetsMono.put(taxonSet, (Boolean)aValue);
                    }
                    break;
                }
            }
        }

        public boolean isCellEditable(int row, int col) {
            return true;
        }

        public String getColumnName(int column) {
            switch(column) {
                case 0: return "Taxon Sets";
                case 1: return "Monophyletic?";
            }
            return null;
        }

        public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }
    }

    private JPanel createAddRemoveButtonPanel(Action addAction, Icon addIcon, String addToolTip,
                                              Action removeAction, Icon removeIcon, String removeToolTip, int axis) {

        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new BoxLayout(buttonPanel, axis));
        buttonPanel.setOpaque(false);
        JButton addButton = new JButton(addAction);
        if (addIcon != null) {
            addButton.setIcon(addIcon);
            addButton.setText(null);
        }
        addButton.setToolTipText(addToolTip);
        addButton.putClientProperty("JButton.buttonType", "toolbar");
        addButton.setOpaque(false);
        addAction.setEnabled(false);

        JButton removeButton = new JButton(removeAction);
        if (removeIcon != null) {
            removeButton.setIcon(removeIcon);
            removeButton.setText(null);
        }
        removeButton.setToolTipText(removeToolTip);
        removeButton.putClientProperty("JButton.buttonType", "toolbar");
        removeButton.setOpaque(false);
        removeAction.setEnabled(false);

        buttonPanel.add(addButton);
        buttonPanel.add(new JToolBar.Separator(new Dimension(6,6)));
        buttonPanel.add(removeButton);

        return buttonPanel;
    }

    private void excludedTaxaTableSelectionChanged() {
        if (excludedTaxaTable.getSelectedRowCount() == 0) {
            includeTaxonAction.setEnabled(false);
        } else {
            includeTaxonAction.setEnabled(true);
        }
    }

    private void includedTaxaTableSelectionChanged() {
        if (includedTaxaTable.getSelectedRowCount() == 0) {
            excludeTaxonAction.setEnabled(false);
        } else {
            excludeTaxonAction.setEnabled(true);
        }
    }

    private void includeSelectedTaxa() {
        int[] rows = excludedTaxaTable.getSelectedRows();

        List<Taxon> transfer = new ArrayList<Taxon>();

        for (int r : rows) {
            transfer.add(excludedTaxa.get(r));
        }

        includedTaxa.addAll(transfer);
        Collections.sort(includedTaxa);

        excludedTaxa.removeAll(includedTaxa);

        includedTaxaTableModel.fireTableDataChanged();
        excludedTaxaTableModel.fireTableDataChanged();

        includedTaxaTable.getSelectionModel().clearSelection();
        for (Taxon taxon : transfer) {
            int row = includedTaxa.indexOf(taxon);
            includedTaxaTable.getSelectionModel().addSelectionInterval(row, row);
        }

        taxonSetChanged();
    }

    private void excludeSelectedTaxa() {
        int[] rows = includedTaxaTable.getSelectedRows();

        List<Taxon> transfer = new ArrayList<Taxon>();

        for (int r : rows) {
            transfer.add(includedTaxa.get(r));
        }

        excludedTaxa.addAll(transfer);
        Collections.sort(excludedTaxa);

        includedTaxa.removeAll(excludedTaxa);

        includedTaxaTableModel.fireTableDataChanged();
        excludedTaxaTableModel.fireTableDataChanged();

        excludedTaxaTable.getSelectionModel().clearSelection();
        for (Taxon taxon : transfer) {
            int row = excludedTaxa.indexOf(taxon);
            excludedTaxaTable.getSelectionModel().addSelectionInterval(row, row);
        }

        taxonSetChanged();
    }

    Action includeTaxonAction = new AbstractAction("->") {
        /**
         *
         */
        private static final long serialVersionUID = 7510299673661594128L;

        public void actionPerformed(ActionEvent ae) {
            includeSelectedTaxa();
        }
    };

    Action excludeTaxonAction = new AbstractAction("<-") {

        /**
         *
         */
        private static final long serialVersionUID = 449692708602410206L;

        public void actionPerformed(ActionEvent ae) {
            excludeSelectedTaxa();
        }
    };

    class TaxaTableModel extends AbstractTableModel {

        /**
         *
         */
        private static final long serialVersionUID = -8027482229525938010L;
        boolean included;

        public TaxaTableModel(boolean included) {
            this.included = included;
        }

        public int getColumnCount() {
            return 1;
        }

        public int getRowCount() {
            if (currentTaxonSet == null) return 0;

            if (included) {
                return includedTaxa.size();
            } else {
                return excludedTaxa.size();
            }
        }

        public Object getValueAt(int row, int col) {

            if (included) {
                return includedTaxa.get(row).getId();
            } else {
                return excludedTaxa.get(row).getId();
            }
        }

        public boolean isCellEditable(int row, int col) {
            return false;
        }

        public String getColumnName(int column) {
            if (included) return "Included Taxa";
            else return "Excluded Taxa";
        }

        public Class getColumnClass(int c) {return getValueAt(0, c).getClass();}
    }

}
