/*
 * BeautiFrame.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
 */

/*
 * BeautiFrame.java
 *
 * (c) 2002-2005 BEAST Development Core Team
 *
 * This package may be distributed under the
 * Lesser Gnu Public Licence (LGPL)
 */
package dr.app.beauti;

import dr.app.beauti.ancestralStatesPanel.AncestralStatesPanel;
import dr.app.beauti.clockModelsPanel.OldClockModelsPanel;
import dr.app.beauti.components.ComponentFactory;
import dr.app.beauti.components.ancestralstates.AncestralStatesComponentFactory;
import dr.app.beauti.components.continuous.ContinuousComponentFactory;
import dr.app.beauti.components.discrete.DiscreteTraitsComponentFactory;
import dr.app.beauti.components.dollo.DolloComponentFactory;
import dr.app.beauti.components.hpm.HierarchicalModelComponentFactory;
import dr.app.beauti.components.linkedparameters.LinkedParameterComponentFactory;
import dr.app.beauti.components.marginalLikelihoodEstimation.MarginalLikelihoodEstimationComponentFactory;
import dr.app.beauti.components.sequenceerror.SequenceErrorModelComponentFactory;
import dr.app.beauti.components.tipdatesampling.TipDateSamplingComponentFactory;
import dr.app.beauti.datapanel.DataPanel;
import dr.app.beauti.generator.BeastGenerator;
import dr.app.beauti.generator.Generator;
import dr.app.beauti.mcmcpanel.MCMCPanel;
import dr.app.beauti.operatorspanel.OperatorsPanel;
import dr.app.beauti.options.BeautiOptions;
import dr.app.beauti.options.PartitionTreePrior;
import dr.app.beauti.options.STARBEASTOptions;
import dr.app.beauti.options.TraitData;
import dr.app.beauti.priorsPanel.DefaultPriorTableDialog;
import dr.app.beauti.priorsPanel.PriorsPanel;
import dr.app.beauti.siteModelsPanel.SiteModelsPanel;
import dr.app.beauti.taxonsetspanel.SpeciesSetPanel;
import dr.app.beauti.taxonsetspanel.TaxonSetPanel;
import dr.app.beauti.tipdatepanel.TipDatesPanel;
import dr.app.beauti.traitspanel.TraitsPanel;
import dr.app.beauti.treespanel.TreesPanel;
import dr.app.beauti.util.BEAUTiImporter;
import dr.app.beauti.util.TextUtil;
import dr.app.gui.FileDrop;
import dr.app.util.OSType;
import dr.app.util.Utils;
import dr.evolution.io.Importer.ImportException;
import dr.evolution.io.NexusImporter.MissingBlockException;
import jam.framework.DocumentFrame;
import jam.framework.Exportable;
import jam.util.IconUtils;
import org.jdom.JDOMException;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.plaf.BorderUIResource;
import java.awt.*;
import java.awt.event.HierarchyBoundsListener;
import java.awt.event.HierarchyEvent;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Andrew Rambaut
 * @author Alexei Drummond
 * @version $Id: BeautiFrame.java,v 1.22 2006/09/09 16:07:06 rambaut Exp $
 */
public class BeautiFrame extends DocumentFrame {

    private static final long serialVersionUID = 2114148696789612509L;

    public final static String DATA_PARTITIONS = "Partitions";
    public final static String TAXON_SETS = "Taxa";
    public final static String TIP_DATES = "Tips";
    public final static String TRAITS = "Traits";
    public final static String SITE_MODELS = "Sites";
    public final static String CLOCK_MODELS = "Clocks";
    public final static String TREES = "Trees";
    public final static String ANCESTRAL_STATES = "States";
    public final static String PRIORS = "Priors";
    public final static String OPERATORS = "Operators";
    public final static String MCMC = "MCMC";

    private BeautiOptions options;
    private BeastGenerator generator;
    private final ComponentFactory[] components;

    public final JTabbedPane tabbedPane = new JTabbedPane();
    public final JLabel statusLabel = new JLabel();

    private DataPanel dataPanel;
    private TipDatesPanel tipDatesPanel;
    private TraitsPanel traitsPanel;
    private TaxonSetPanel taxonSetPanel;
    private SpeciesSetPanel speciesSetPanel;
    private SiteModelsPanel siteModelsPanel;
    private AncestralStatesPanel ancestralStatesPanel;
    private OldClockModelsPanel clockModelsPanel;
    private TreesPanel treesPanel;
    private PriorsPanel priorsPanel;
    private OperatorsPanel operatorsPanel;
    private MCMCPanel mcmcPanel;

    private BeautiPanel currentPanel;

    private Map<String, FileDialog> fileDialogs = new HashMap<String, FileDialog>();
    private Map<String, JFileChooser> fileChoosers = new HashMap<String, JFileChooser>();

    final Icon gearIcon = IconUtils.getIcon(this.getClass(), "images/gear.png");

    public BeautiFrame(String title) {
        super();

        setTitle(title);

        // Prevent the application to close in requestClose()
        // after a user cancel or a failure in beast file generation
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

//        getOpenAction().setEnabled(false);
//        getSaveAction().setEnabled(false);

        getFindAction().setEnabled(false);

        getZoomWindowAction().setEnabled(false);

        components = new ComponentFactory[] {
                AncestralStatesComponentFactory.INSTANCE,
                ContinuousComponentFactory.INSTANCE,
                DiscreteTraitsComponentFactory.INSTANCE,
//                DnDsComponentFactory.INSTANCE,
                DolloComponentFactory.INSTANCE,
                LinkedParameterComponentFactory.INSTANCE,
                HierarchicalModelComponentFactory.INSTANCE,
                MarginalLikelihoodEstimationComponentFactory.INSTANCE,
                SequenceErrorModelComponentFactory.INSTANCE,
                TipDateSamplingComponentFactory.INSTANCE
        };

        options = new BeautiOptions(components);
        generator = new BeastGenerator(options, components);

        this.getContentPane().addHierarchyBoundsListener(new HierarchyBoundsListener() {
            public void ancestorMoved(HierarchyEvent e) {
            }

            public void ancestorResized(HierarchyEvent e) {
                setStatusMessage();
            }
        });
    }

    public void initializeComponents() {

        dataPanel = new DataPanel(this, getImportAction(), getDeleteAction()/*, getImportTraitsAction()*/);
        tipDatesPanel = new TipDatesPanel(this);
        traitsPanel = new TraitsPanel(this, dataPanel, getImportTraitsAction());
        taxonSetPanel = new TaxonSetPanel(this);
        speciesSetPanel = new SpeciesSetPanel(this);
        siteModelsPanel = new SiteModelsPanel(this, getDeleteAction());
        ancestralStatesPanel = new AncestralStatesPanel(this);
        clockModelsPanel = new OldClockModelsPanel(this);
//        oldTreesPanel = new OldTreesPanel(this);
        treesPanel = new TreesPanel(this, getDeleteAction());
//        speciesTreesPanel = new SpeciesTreesPanel(this);
        priorsPanel = new PriorsPanel(this, false);
        operatorsPanel = new OperatorsPanel(this);
        mcmcPanel = new MCMCPanel(this);

        int index = 0;
        tabbedPane.addTab(DATA_PARTITIONS, dataPanel);
        tabbedPane.setToolTipTextAt(index++, "<html>" +
                "Import sequence alignments, organize data partitions,<br>" +
                "link models between partitions and select *BEAST</html>");

        tabbedPane.addTab(TAXON_SETS, taxonSetPanel);
        tabbedPane.setToolTipTextAt(index++, "<html>" +
                "Create and edit sets of taxa which can be used to <br>" +
                "define times of most recent common ancestors and <br>" +
                "to keep groups monophyletic.</html>");
//        tabbedPane.addTab("Species Sets", speciesSetPanel);
        tabbedPane.addTab(TIP_DATES, tipDatesPanel);
        tabbedPane.setToolTipTextAt(index++, "<html>" +
                "Specify sampling dates of tips for use in temporal <br>" +
                "analyses of measurably evolving populations.</html>");
        tabbedPane.addTab(TRAITS, traitsPanel);
        tabbedPane.setToolTipTextAt(index++, "<html>" +
                "Import and organize continuous and discrete traits <br>" +
                "for taxa, convert them into data partitions for evolutionary<br>" +
                "analysis.</html>");
        tabbedPane.addTab(SITE_MODELS, siteModelsPanel);
        tabbedPane.setToolTipTextAt(index++, "<html>" +
                "Select evolutionary models to be used for each data <br>" +
                "partition including substitution models, codon partitioning<br>" +
                "and trait evolution models.</html>");
        tabbedPane.addTab(CLOCK_MODELS, clockModelsPanel);
        tabbedPane.setToolTipTextAt(index++, "<html>" +
                "Select relaxed molecular clock models to be used across <br>" +
                "the tree. Specify sampling of rates.</html>");
        tabbedPane.addTab(TREES, treesPanel);
        tabbedPane.setToolTipTextAt(index++, "<html>" +
                "Select the priors on trees including coalescent models<br>" +
                "birth-death speciation models and the *BEAST gene tree,<br>" +
                "species tree options.</html>");

        tabbedPane.addTab(ANCESTRAL_STATES, ancestralStatesPanel);
        tabbedPane.setToolTipTextAt(index++, "<html>" +
                "Select options for sampling ancestral states at specific<br>" +
                "or all common ancestors, models of counting state changes<br>" +
                "and models of sequencing error for data partitions.</html>");

        tabbedPane.addTab(PRIORS, priorsPanel);
        tabbedPane.setToolTipTextAt(index++, "<html>" +
                "Specify prior probability distributions on each and every<br>" +
                "parameter of the current model.</html>");
        tabbedPane.addTab(OPERATORS, operatorsPanel);
        tabbedPane.setToolTipTextAt(index++, "<html>" +
                "Select and adjust the menu of operators that will be used<br>" +
                "to propose changes to the parameters. Switch off operators<br>" +
                "on certain parameters to fix them to initial values.</html>");
        tabbedPane.addTab(MCMC, mcmcPanel);
        tabbedPane.setToolTipTextAt(index++, "<html>" +
                "Specify the details of MCMC sampling. This includes chain<br>" +
                "length, sampling frequencies, log file names and more.</html>");

        for (int i = 1; i < tabbedPane.getTabCount(); i++) {
            tabbedPane.setEnabledAt(i, false);
        }

        currentPanel = (BeautiPanel) tabbedPane.getSelectedComponent();

        tabbedPane.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                BeautiPanel selectedPanel = (BeautiPanel) tabbedPane.getSelectedComponent();
                if (selectedPanel == dataPanel) {
                    dataPanel.selectionChanged();
                } else {
                    getDeleteAction().setEnabled(false);
                }
                currentPanel.getOptions(options);
                setAllOptions();
                currentPanel = selectedPanel;
            }
        });

        JPanel basePanel = new JPanel(new BorderLayout(6, 6));
        basePanel.setBorder(new BorderUIResource.EmptyBorderUIResource(new java.awt.Insets(12, 12, 12, 12)));
//        basePanel.setPreferredSize(new java.awt.Dimension(800, 600));

        getExportAction().setEnabled(false);
        JButton generateButton = new JButton(getExportAction());
        generateButton.putClientProperty("JButton.buttonType", "roundRect");

        JPanel panel2 = new JPanel(new BorderLayout(6, 6));
        panel2.add(statusLabel, BorderLayout.WEST);
        panel2.add(generateButton, BorderLayout.EAST);
        panel2.setMinimumSize(new java.awt.Dimension(10, 10));

        basePanel.add(tabbedPane, BorderLayout.CENTER);
        basePanel.add(panel2, BorderLayout.SOUTH);

        add(basePanel, BorderLayout.CENTER);

        Toolkit tk = Toolkit.getDefaultToolkit();
        Dimension d = tk.getScreenSize();
//        System.out.println("Screen width = " + d.width);
//        System.out.println("Screen height = " + d.height);

        if (d.width < 1000 || d.height < 700) {
            setSize(new java.awt.Dimension(700, 550));
        } else {
            setSize(new java.awt.Dimension(1024, 768));
        }

        if (OSType.isMac()) {
            setMinimumSize(new java.awt.Dimension(640, 480));
        }

        setAllOptions();

        Color focusColor = UIManager.getColor("Focus.color");
        Border focusBorder = BorderFactory.createMatteBorder(2, 2, 2, 2, focusColor);
        dataPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
        new FileDrop(null, dataPanel, focusBorder, new FileDrop.Listener() {
            public void filesDropped(java.io.File[] files) {
                importFiles(files);
            }   // end filesDropped
        }); // end FileDrop.Listener


    }

    /**
     * set all the options for all panels
     */
    public void setAllOptions() {
        try {
            dataPanel.setOptions(options);
            tipDatesPanel.setOptions(options);
            traitsPanel.setOptions(options);
            if (options.useStarBEAST) {
                speciesSetPanel.setOptions(options);
            } else {
                taxonSetPanel.setOptions(options);
            }
            siteModelsPanel.setOptions(options);
            clockModelsPanel.setOptions(options);
            treesPanel.setOptions(options);
            ancestralStatesPanel.setOptions(options);
            priorsPanel.setOptions(options);
            operatorsPanel.setOptions(options);
            mcmcPanel.setOptions(options);

            setStatusMessage();
        } catch (IllegalArgumentException iae) {
            iae.printStackTrace(System.err);
            JOptionPane.showMessageDialog(this, iae.getMessage(),
                    "Illegal Argument Exception", JOptionPane.ERROR_MESSAGE);
        }

        // enable/disable the other tabs and generate option depending on whether any
        // data has been loaded.
        boolean enabled = options.getDataPartitions().size() > 0;
        for (int i = 1; i < tabbedPane.getTabCount(); i++) {
            tabbedPane.setEnabledAt(i, enabled);
        }
        getExportAction().setEnabled(enabled);
    }

    /**
     * get all the options for all panels
     */
    private void getAllOptions() {
        try {
            dataPanel.getOptions(options);
            tipDatesPanel.getOptions(options);
            traitsPanel.getOptions(options);
            if (options.useStarBEAST) {
                speciesSetPanel.getOptions(options);
            } else {
                taxonSetPanel.getOptions(options);
            }
            siteModelsPanel.getOptions(options);
            clockModelsPanel.getOptions(options);
            treesPanel.getOptions(options);
            ancestralStatesPanel.getOptions(options);
            priorsPanel.getOptions(options);
            operatorsPanel.getOptions(options);
            mcmcPanel.getOptions(options);
        } catch (IllegalArgumentException iae) {
            iae.printStackTrace(System.err);
            JOptionPane.showMessageDialog(this, iae.getMessage(),
                    "Illegal Argument Exception", JOptionPane.ERROR_MESSAGE);
        }
    }

    public void doSelectAll() {
        if (currentPanel == dataPanel) {
            dataPanel.selectAll();
        }
    }

    public final void dataSelectionChanged(boolean isSelected) {
        getDeleteAction().setEnabled(isSelected);
    }

    public final void modelSelectionChanged(boolean isSelected) {
        getDeleteAction().setEnabled(isSelected);
    }

    public void doDelete() {
        if (tabbedPane.getSelectedComponent() == dataPanel) {
            dataPanel.removeSelection();
//        } else if (tabbedPane.getSelectedComponent() == modelsPanel) {
//            modelsPanel.delete();
//        } else if (tabbedPane.getSelectedComponent() == treesPanel) {
//        	treesPanel.delete();
        } else {
            throw new RuntimeException("Delete should only be accessable from the Data and Models panels");
        }

        setStatusMessage();
    }

    public boolean requestClose() {
        if (isDirty() && options.hasData() && isVisible()) {
            int option = JOptionPane.showConfirmDialog(this,
                    "You have made changes but have not generated\n" +
                            "a BEAST XML file. Do you wish to generate\n" +
                            "before closing this window?",
                    "Unused changes",
                    JOptionPane.YES_NO_CANCEL_OPTION,
                    JOptionPane.WARNING_MESSAGE);

            if (option == JOptionPane.YES_OPTION) {
                return doGenerate();
            } else if (option == JOptionPane.CANCEL_OPTION || option == JOptionPane.DEFAULT_OPTION) {
                return false;
            }
            return true;
        }
        return true;
    }

    public void doApplyTemplate() {
        FileDialog dialog = new FileDialog(this,
                "Apply Template",
                FileDialog.LOAD);
        dialog.setVisible(true);
        if (dialog.getFile() != null) {
            File file = new File(dialog.getDirectory(), dialog.getFile());
            try {
                readFromFile(file);
            } catch (FileNotFoundException fnfe) {
                JOptionPane.showMessageDialog(this, "Unable to open template file: File not found",
                        "Unable to open file",
                        JOptionPane.ERROR_MESSAGE);
            } catch (IOException ioe) {
                JOptionPane.showMessageDialog(this, "Unable to read template file: " + ioe.getMessage(),
                        "Unable to read file",
                        JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    protected boolean readFromFile(File file) throws IOException {
        FileInputStream fileIn =
                new FileInputStream(file);
        try {
            ObjectInputStream in = new ObjectInputStream(fileIn);
            options = (BeautiOptions) in.readObject();
            in.close();
        } catch (IOException ioe) {
            JOptionPane.showMessageDialog(this,
                    "Unable to read BEAUti file. BEAUti can only read files\n" +
                            "created by 'Saving' within BEAUti. It cannot read BEAST\n" +
                            "XML files. To read data within BEAST XML files, use\n" +
                            "the 'Import' option.",
                    "Unable to read file",
                    JOptionPane.ERROR_MESSAGE);
        } catch (ClassNotFoundException cnfe) {
            JOptionPane.showMessageDialog(this, "Unable to read BEAUti file: " + cnfe.getMessage(),
                    "Unable to read file",
                    JOptionPane.ERROR_MESSAGE);
            cnfe.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            return false;
        }
        fileIn.close();

        options.registerComponents(components);
        generator = new BeastGenerator(options, components);

        return true;
    }

    public String getDefaultFileName() {
        return options.fileNameStem + ".beauti";
    }

    protected boolean writeToFile(File file) throws IOException {
        OutputStream fileOut = new FileOutputStream(file);
        ObjectOutputStream out =
                new ObjectOutputStream(fileOut);
        out.writeObject(options);
        out.close();
        fileOut.close();
        return true;
    }

    public final void doImport() {
        File[] files = selectImportFiles("Import Aligment...", true, new FileNameExtensionFilter[] {
                new FileNameExtensionFilter( "Microsatellite (tab-delimited *.txt) Files", "txt"),
                new FileNameExtensionFilter(
                        "NEXUS, BEAST or FASTA Files", "nex", "nexus", "nx", "xml", "beast", "fa", "fasta", "afa")});
        // new FileNameExtensionFilter( "Microsatellite (tab-delimited *.txt) Files", "txt");
        if (files != null && files.length != 0) {
            importFiles(files);
            tabbedPane.setSelectedComponent(dataPanel);
        }
    }

    private void importFiles(File[] files) {
        for (File file : files) {
            if (file == null || file.getName().equals("")) {
                JOptionPane.showMessageDialog(this, "Invalid file name",
                        "Invalid file name", JOptionPane.ERROR_MESSAGE);
            } else {
                try {
                    BEAUTiImporter beautiImporter = new BEAUTiImporter(this, options);
                    beautiImporter.importFromFile(file);

                    setDirty();
//                    } catch (FileNotFoundException fnfe) {
//                        JOptionPane.showMessageDialog(this, "Unable to open file: File not found",
//                                "Unable to open file", JOptionPane.ERROR_MESSAGE);
                } catch (IOException ioe) {
                    JOptionPane.showMessageDialog(this, "File I/O Error unable to read file: " + ioe.getMessage(),
                            "Unable to read file", JOptionPane.ERROR_MESSAGE);
                    ioe.printStackTrace();
                    // there may be other files in the list so don't return
//                    return;

                } catch (MissingBlockException ex) {
                    JOptionPane.showMessageDialog(this, "TAXON, DATA or CHARACTERS block is missing in Nexus file: " + ex,
                            "Missing Block in Nexus File",
                            JOptionPane.ERROR_MESSAGE);
                    ex.printStackTrace();

                } catch (ImportException ime) {
                    JOptionPane.showMessageDialog(this, "Error parsing imported file: " + ime,
                            "Error reading file",
                            JOptionPane.ERROR_MESSAGE);
                    ime.printStackTrace();
                } catch (JDOMException jde) {
                    JOptionPane.showMessageDialog(this, "Error parsing imported file: " + jde,
                            "Error reading file",
                            JOptionPane.ERROR_MESSAGE);
                    jde.printStackTrace();
                }
            }
        }

        if (!options.hasIdenticalTaxa()) {
            setAllOptions(); // need this to refresh panels otherwise it will throw exception
            dataPanel.selectAll();
            dataPanel.unlinkTrees();
        }

        setAllOptions();

    }

    public final boolean doImportTraits() {
        if (options.taxonList != null) { // validation of check empty taxonList
            File[] files = selectImportFiles("Import Traits File...", false, new FileNameExtensionFilter[] {
                    new FileNameExtensionFilter("Tab-delimited text files", "txt", "tab", "dat") });

            if (files != null && files.length != 0) {
                try {
                    BEAUTiImporter beautiImporter = new BEAUTiImporter(this, options);
                    beautiImporter.importTraits(files[0]);
                } catch (FileNotFoundException fnfe) {
                    JOptionPane.showMessageDialog(this, "Unable to open file: File not found",
                            "Unable to open file",
                            JOptionPane.ERROR_MESSAGE);
                    return false;
                } catch (IOException ioe) {
                    JOptionPane.showMessageDialog(this, "Unable to read file: " + ioe.getMessage(),
                            "Unable to read file",
                            JOptionPane.ERROR_MESSAGE);
                    return false;
                } catch (Exception ex) {
                    ex.printStackTrace(System.err);
                    JOptionPane.showMessageDialog(this, "Fatal exception: " + ex,
                            "Error reading file",
                            JOptionPane.ERROR_MESSAGE);
                    ex.printStackTrace();
                    return false;
                }
            } else {
                return false;
            }

            traitsPanel.fireTraitsChanged();
            setAllOptions();

            tabbedPane.setSelectedComponent(traitsPanel);
            return true;

        } else {
            JOptionPane.showMessageDialog(this, "No taxa loaded yet, please import Alignment file.",
                    "No taxa loaded", JOptionPane.ERROR_MESSAGE);
            return false;
        }
    }

    public boolean validateTraitName(String traitName) {
        // check that the name is valid
        if (traitName.trim().length() == 0) {
            Toolkit.getDefaultToolkit().beep();
            return false;
        }

        // disallow a trait called 'date'
        if (traitName.equalsIgnoreCase("date")) {
            JOptionPane.showMessageDialog(this,
                    "This trait name has a special meaning. Use the 'Tip Date' panel\n" +
                            " to set dates for taxa.",
                    "Reserved trait name",
                    JOptionPane.WARNING_MESSAGE);

            return false;
        }

        if (options.useStarBEAST && traitName.equalsIgnoreCase(TraitData.TRAIT_SPECIES)) {
            JOptionPane.showMessageDialog(this,
                    "This trait name is already in used to denote species\n" +
                            "for *BEAST. Please select a different name.",
                    "Reserved trait name",
                    JOptionPane.WARNING_MESSAGE);

            return false;
        }

        // check that the trait name doesn't exist
        if (options.traitExists(traitName)) {
            int option = JOptionPane.showConfirmDialog(this,
                    "A trait of this name already exists. Do you wish to replace\n" +
                            "it with this new trait? This may result in the loss or change\n" +
                            "in trait values for the taxa.",
                    "Overwrite trait?",
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.WARNING_MESSAGE);

            if (option == JOptionPane.NO_OPTION) {
                return false;
            }
        }

        return true;
    }

    /**
     * Attempts to set up starBEAST - returns true if successful
     * @param useStarBEAST
     * @return
     */
    public boolean setupStarBEAST(boolean useStarBEAST) {
        if (useStarBEAST) {
            if (!options.traitExists(TraitData.TRAIT_SPECIES)) {
                if (!traitsPanel.addTrait(
                        "<html><p>" +
                                "StarBEAST requires a trait to give species designations<br>" +
                                "for each taxon. Create or import a discrete trait<br>" +
                                "labelled 'species'.</p></html>",
                        TraitData.TRAIT_SPECIES,
                        true /* isSpeciesTrait */
                )) {
                    return false;
                }
            } else if (options.getTraitPartitions(options.getTrait(TraitData.TRAIT_SPECIES)).size() > 0) {
                int option = JOptionPane.showConfirmDialog(this,
                        "The trait named '" + TraitData.TRAIT_SPECIES + "', used to denote species in *BEAST, is\n" +
                                "already in use as a data partition. Do you wish to continue?",
                        "Species trait already in use",
                        JOptionPane.YES_NO_OPTION,
                        JOptionPane.WARNING_MESSAGE);
                if (option == JOptionPane.NO_OPTION) {
                    return false;
                }

            }

            dataPanel.selectAll();
            dataPanel.unlinkAll();

            options.starBEASTOptions = new STARBEASTOptions(options);
            options.fileNameStem = "StarBEASTLog";

            tabbedPane.removeTabAt(1);
            tabbedPane.insertTab("Species Sets", null, speciesSetPanel, null, 1);

        } else { // remove species
            options.fileNameStem = MCMCPanel.DEFAULT_FILE_NAME_STEM;

            tabbedPane.removeTabAt(1);
            tabbedPane.insertTab("Taxon Sets", null, taxonSetPanel, null, 1);
        }

        options.useStarBEAST = useStarBEAST;

        treesPanel.updatePriorPanelForSpeciesAnalysis();

        setStatusMessage();

        return true;
    }

    public void updateDiscreteTraitAnalysis() {
        setStatusMessage();
    }

    public void setupEBSP() {
        dataPanel.selectAll();

        dataPanel.unlinkAll();

        setAllOptions();
    }

    public PartitionTreePrior getCurrentPartitionTreePrior() {
        treesPanel.setOptions(options); // need this to refresh the currentTreeModel
        return treesPanel.currentTreeModel.getPartitionTreePrior();
    }

    public void setStatusMessage() {
        int width = this.getWidth() - 260; // minus generate button size
        if (width < 100) width = 100; // prevent too narrow
        String tw = TextUtil.wrapText(options.statusMessage(), statusLabel, width);
//        System.out.println(this.getWidth() + "   " + tw);
        statusLabel.setText(tw);
    }

    public final boolean doGenerate() {

        try {
            generator.checkOptions();
        } catch (Generator.GeneratorException ge) {
            ge.printStackTrace(System.err);
            JOptionPane.showMessageDialog(this, ge.getMessage(), "Invalid BEAUti setting : ",
                    JOptionPane.ERROR_MESSAGE);
            if (ge.getSwitchToPanel() != null) {
                switchToPanel(ge.getSwitchToPanel());
            }
            return false;
        }

        DefaultPriorTableDialog defaultPriorDialog = new DefaultPriorTableDialog(this);
        if (!defaultPriorDialog.showDialog(options)) {
            return false;
        }

        File file = selectExportFile("Generate BEAST XML File...", new FileNameExtensionFilter("BEAST XML File", "xml", "beast"));

        if (file != null) {
            try {
                getAllOptions();
                generator.generateXML(file);

            } catch (IOException ioe) {
                ioe.printStackTrace(System.err);
                JOptionPane.showMessageDialog(this, "Unable to generate file due to I/O issue: " + ioe.getMessage(),
                        "Unable to generate file", JOptionPane.ERROR_MESSAGE);
                return false;
            } catch (Generator.GeneratorException e) {
                e.printStackTrace(System.err);
                JOptionPane.showMessageDialog(this, "The BEAST XML is incomplete because :\n" + e.getMessage(),
                        "The BEAST XML is incomplete", JOptionPane.ERROR_MESSAGE);
                return false;
            } catch (Exception e) {
                e.printStackTrace(System.err);
                JOptionPane.showMessageDialog(this, "Unable to generate file: " + e.getMessage(),
                        "Unable to generate file", JOptionPane.ERROR_MESSAGE);
                return false;
            }

            clearDirty();
            return true;
        }

        return false;
    }

    /**
     * Use the native file dialog on the Mac because the Swing one is bad. On linux, the native
     * one is bad. No preference on Windows.
     * @param title
     * @return
     */
    private File[] selectImportFiles(final String title, boolean multipleSelection, FileNameExtensionFilter[] fileNameExtensionFilters) {
        if (Boolean.parseBoolean(System.getProperty("use.native.choosers", Boolean.toString(OSType.isMac())))) {
            FileDialog importDialog = fileDialogs.get(title);
            if (importDialog == null) {
                importDialog = new FileDialog(this, title, FileDialog.LOAD);
                fileDialogs.put(title, importDialog);
            }

            importDialog.setVisible(true);
            if (importDialog.getFile() != null) {
                return new File[] { new File(importDialog.getDirectory(), importDialog.getFile()) };
            }
        } else {
            JFileChooser importChooser = fileChoosers.get(title);
            if (importChooser == null) {
                importChooser = new JFileChooser(Utils.getCWD());

                importChooser.setMultiSelectionEnabled(multipleSelection);
                for (FileNameExtensionFilter fileNameExtensionFilter : fileNameExtensionFilters) {
                    importChooser.setFileFilter(fileNameExtensionFilter);
                }
                importChooser.setDialogTitle(title);

                fileChoosers.put(title, importChooser);
            }

            int returnVal = importChooser.showOpenDialog(this);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                if (importChooser.isMultiSelectionEnabled()) {
                    return importChooser.getSelectedFiles();
                } else {
                    return new File[] { importChooser.getSelectedFile() };
                }
            }
        }

        return null;
    }

    /**
     * Use the native file dialog on the Mac because the Swing one is bad. On linux, the native
     * one is bad. No preference on Windows.
     * @param title
     * @return
     */
    private File selectExportFile(final String title, FileNameExtensionFilter fileNameExtensionFilter) {
        if (Boolean.parseBoolean(System.getProperty("use.native.choosers", Boolean.toString(OSType.isMac())))) {
            FileDialog exportDialog = fileDialogs.get(title);
            if (exportDialog == null) {
                exportDialog = new FileDialog(this, title, FileDialog.SAVE);
                fileDialogs.put(title, exportDialog);
            }

            exportDialog.setFile(options.fileNameStem + ".xml");

            exportDialog.setVisible(true);

            // Mac dialog box will already have asked about overwriting file...
            if (exportDialog.getFile() != null) {
                return new File(exportDialog.getDirectory(), exportDialog.getFile());
            }
        } else {
            JFileChooser exportChooser = fileChoosers.get(title);
            if (exportChooser == null) {
                exportChooser = new JFileChooser(Utils.getCWD());

                // make JFileChooser chooser remember previous path
                exportChooser = new JFileChooser(Utils.getCWD());
                exportChooser.setFileFilter(fileNameExtensionFilter);
                exportChooser.setDialogTitle(title);

                fileChoosers.put(title, exportChooser);
            }

            // offer stem as default
            exportChooser.setSelectedFile(new File(options.fileNameStem + ".xml"));

            final int returnVal = exportChooser.showSaveDialog(this);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                File file = exportChooser.getSelectedFile();

                int n = JOptionPane.YES_OPTION;

                if (file.exists()) {
                    n = JOptionPane.showConfirmDialog(this, file.getName(),
                            "Overwrite the existing file?", JOptionPane.YES_NO_OPTION);
                }

                if (n == JOptionPane.YES_OPTION) {
                    return file;
                }
            }
        }

        return null;
    }

    public void switchToPanel(String panelName) {
        for (int i = 0; i < tabbedPane.getTabCount(); i++) {
            if (tabbedPane.getTitleAt(i).equals(panelName)) {
                tabbedPane.setSelectedIndex(i);
                break;
            }
        }
    }

    public JComponent getExportableComponent() {

        JComponent exportable = null;
        Component comp = tabbedPane.getSelectedComponent();

        if (comp instanceof Exportable) {
            exportable = ((Exportable) comp).getExportableComponent();
        } else if (comp instanceof JComponent) {
            exportable = (JComponent) comp;
        }

        return exportable;
    }

    public Action getImportAction() {
        return importAlignmentAction;
    }

    protected AbstractAction importAlignmentAction = new AbstractAction("Import Data...") {
        private static final long serialVersionUID = 3217702096314745005L;

        public void actionPerformed(java.awt.event.ActionEvent ae) {
            doImport();
        }
    };

    public Action getImportTraitsAction() {
        return importTraitsAction;
    }

    protected AbstractAction importTraitsAction = new AbstractAction("Import Traits") {
        private static final long serialVersionUID = 3217702096314745005L;

        public void actionPerformed(java.awt.event.ActionEvent ae) {
            doImportTraits();
        }
    };

    public Action getExportAction() {
        return generateAction;
    }

    protected AbstractAction generateAction = new AbstractAction("Generate BEAST File...", gearIcon) {
        private static final long serialVersionUID = -5329102618630268783L;

        public void actionPerformed(java.awt.event.ActionEvent ae) {
            doGenerate();
        }
    };

}
