package org.baderlab.lola.view;

import org.baderlab.lola.LolaParameterSet;
import org.baderlab.lola.LolaCurrentParameters;
import org.baderlab.lola.resources.LolaResources;
import org.baderlab.lola.view.actions.SaveLogosToFileAction;
import org.baderlab.lola.view.actions.MakeLogoTreeAction;
import org.baderlab.lola.view.actions.SaveLogoTreeToFile;
import org.baderlab.lola.model.ProfileSet;
import org.baderlab.lola.model.Profile;
import org.baderlab.lola.model.LogoTree;
import org.baderlab.lola.model.LogoGenerator;
import org.baderlab.brain.util.JMultiLineToolTip;

import javax.swing.*;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.net.URL;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

/**
 Copyright 2007 Bader Lab

 This program 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 3 of the License, or
 (at your option) any later version.

 This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

 */

/**
 * This Runnable constructs the main application window and underlying components.
 *
 * User: moyez
 * Date: May 25, 2007
 * Time: 11:40:19 PM
 */
public class LolaMainFrame implements Runnable {

    // data allProfiles - contains list of ALL OPEN profiles to be processed
    private ProfileSet allProfiles;

    // data allProfiles - contains list of SELECTED profiles to be processed
    private ProfileSet selectedProfiles;

    //Parameters for Lola
    private LolaParameterSet currentParamsCopy;

    //Shared widgets

    //tabbed pane
    private JTabbedPane tabbedPane;

    //files panel
    private JTextField profileFileNameTextField;
    private JTextField biasFileNameTextField;
    private JTextField outputPathTextField;
    private JCheckBox uniquePeptidesCheckBox;
    private JButton openProfileButton;
    private JButton drawLogoButton;
    private JButton saveLogoButton;
    private JButton closeSelectedProfilesButton;
    private JButton closeAllProfilesButton;
    private JButton quitButton;

    //logo settings panel
    private JPanel logoPanel;
    private JFormattedTextField logoStartIndexTextField;
    private JFormattedTextField logoHeightTextField;
    private JFormattedTextField logoTrimPercentageTextField;
    private JComboBox logoFormatComboBox;
    private JComboBox logoColorComboBox;

    //logo tree panel
    private JPanel treePanel;
    private JTextField logoTreeTitle;
    private JTextField logoTreeOutputFilename;
    private JButton createLogoTreeButton;
    private JButton saveLogoTreeButton;

    //profile pane
    JList profileList;

    private JScrollPane logoPane;



    public LolaMainFrame() {
        super();

        // initialize data model
        allProfiles = new ProfileSet();
        selectedProfiles = new ProfileSet();

        // register model listener
        allProfiles.addListDataListener(new ListDataListener() {

            public void intervalAdded(ListDataEvent listDataEvent) {
                //To change body of implemented methods use File | Settings | File Templates.
                drawLogoButton.setEnabled(true);
                saveLogoButton.setEnabled(true);

                //if there >1 profiles open, allow logo tree generation
                if (allProfiles.size() > 1) {
                    createLogoTreeButton.setEnabled(true);
                    saveLogoTreeButton.setEnabled(true);
                }
            }

            public void intervalRemoved(ListDataEvent listDataEvent) {
                //To change body of implemented methods use File | Settings | File Templates.
                if (allProfiles.size() == 0) {
                    drawLogoButton.setEnabled(false);
                    saveLogoButton.setEnabled(false);
                    createLogoTreeButton.setEnabled(false);
                    saveLogoTreeButton.setEnabled(false);
                }
            }

            public void contentsChanged(ListDataEvent listDataEvent) {
                //To change body of implemented methods use File | Settings | File Templates.
            }
        });
    }

    private static final JFrame mainFrame = new JFrame(
            LolaResources.APPLICATION_NAME
            + " " + LolaResources.APPLICATION_VERSION);

    public void run() {
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // make a split pane
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        splitPane.setContinuousLayout(true);
        splitPane.setOneTouchExpandable(true);

        // left pane is for parameters and profile list (vertically split)
        JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
        leftSplitPane.setContinuousLayout(true);
        leftSplitPane.setOneTouchExpandable(true);

        // top left pane is the parameters pane
        JScrollPane paramPane = new JScrollPane(getParameterPanel());
        
        // bottom left pane is the profile list pane
        profileList = new JList(allProfiles);

        //TODO - display a tree instead of a list in the scroll pane using JTree
        //JTree profileTree = new JTree(allProfiles); // come back to this

        JPanel profilePanel = new JPanel(new BorderLayout());


        JScrollPane profilePane = new JScrollPane(profileList);
        profilePane.setBorder(BorderFactory.createEtchedBorder());

        profileList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        profileList.addListSelectionListener(new ListSelectionListener() {

            public void valueChanged(ListSelectionEvent listSelectionEvent) {
//                Object obj = ((JList)listSelectionEvent.getSource()).getSelectedValue();
                Object[] objs = ((JList)listSelectionEvent.getSource()).getSelectedValues();

                //return if nothing selected
                if (objs.length < 1) return;

                //add all selected profiles to selected profile set
                selectedProfiles.removeAllElements();
                for (int i = 0; i < objs.length; i++) {
                    Profile profile = null;
                    if (objs[i] != null) {
                        profile = (Profile) objs[i];
                        selectedProfiles.addElement(profile);
                    }
                    //show last selected image
                    //TODO - instead of displaying only the last image, try showing a grid of all selected images
                    Icon icon = new ImageIcon(profile.getImage());
                    logoPane.setViewportView(new JLabel(icon));

                }
                closeSelectedProfilesButton.setEnabled(true);
//                if (obj != null) {
//                    Profile profile = (Profile) obj;
//                    Icon icon = new ImageIcon(profile.getImage());
//                    logoPane.setViewportView(new JLabel(icon));
//                }
            }
        });

        //create the bottom button panel
        JPanel bottomPanel = new JPanel(new FlowLayout());
        closeSelectedProfilesButton = new JButton("Close Selected");
        closeSelectedProfilesButton.addActionListener(new closeSelectedProfilesAction());
        closeSelectedProfilesButton.setEnabled(false);
        bottomPanel.add(closeSelectedProfilesButton);

        closeAllProfilesButton = new JButton("Close All");
        closeAllProfilesButton.addActionListener(new closeAllProfilesAction());
        closeAllProfilesButton.setEnabled(true);
        bottomPanel.add(closeAllProfilesButton);

        quitButton = new JButton("Quit");
        quitButton.addActionListener(new quitAction());
        quitButton.setEnabled(true);
        bottomPanel.add(quitButton);

        profilePanel.add(profilePane, BorderLayout.CENTER);
        profilePanel.add(bottomPanel, BorderLayout.AFTER_LAST_LINE);


        // construct left pane
        leftSplitPane.setTopComponent(paramPane);
        leftSplitPane.setBottomComponent(profilePanel);

        //right pane is the logo image pane
        Icon icon = null;
        URL imageURL;
        imageURL = LolaMainFrame.class.getResource(LolaResources.APPLICATION_WELCOME_IMAGE);
        if (imageURL != null) {
            icon = new ImageIcon(imageURL);
        }
        JLabel imageLabel = new JLabel(icon);
        logoPane = new JScrollPane(imageLabel);
        logoPane.setBorder(BorderFactory.createEtchedBorder());

        // construct main split pane
        splitPane.setLeftComponent(leftSplitPane);
        splitPane.setRightComponent(logoPane);

        // frame properties
        mainFrame.add(splitPane, BorderLayout.CENTER);
        mainFrame.setSize(1400,900);
        //frame.pack();
        mainFrame.setLocationRelativeTo(null);
        mainFrame.setVisible(true);
    };

    private JPanel getParameterPanel() {
        //get the current parameters (actually a copy to avoid changing directly on user input)
        currentParamsCopy = LolaCurrentParameters.getInstance().getParamsCopy();

        // The main panel to contain a tabbed pane
        JPanel mainPanel = new JPanel(new BorderLayout());

        //create the tabbed pane
        tabbedPane = new JTabbedPane();
        tabbedPane.addTab("Files", null, getFilesPanel(), "Set parameters that affect how sequence logos are generated.");

        logoPanel = getLogoGenerationParamsPanel();
        tabbedPane.addTab("Logo Options", null, logoPanel);

        treePanel = getLogoTreeParamsPanel();
        tabbedPane.addTab("Logo Tree", null, treePanel);
        tabbedPane.addTab("About", null, getAboutPanel());
        tabbedPane.addMouseListener(new tabbedPaneMouseListener());



        mainPanel.add(tabbedPane, BorderLayout.CENTER);
//        mainPanel.add(bottomPanel,BorderLayout.SOUTH);

        return mainPanel;
    }

    private JPanel getFilesPanel() {
        // Tab 1 - parameters for sequence logo generation
        JPanel filesPanel = new JPanel(new BorderLayout());

        // Tab 1 sub-panel - selection of profile file
        JPanel profileSelectionPanel = new JPanel(new BorderLayout());
        profileSelectionPanel.setBorder(BorderFactory.createTitledBorder("Profile Selection"));

        // profile file options
        JPanel profileChooserPanel = new JPanel();
        profileFileNameTextField = new JTextField();
        profileFileNameTextField.setToolTipText("The file name of the profile or list of profiles.");
        if (currentParamsCopy.getProfileFile() != null) {
            profileFileNameTextField.setText(currentParamsCopy.getProfileFile().toString());
        }
        profileFileNameTextField.setColumns(30);
        //this only handles VK_ENTER key events
        profileFileNameTextField.addActionListener(new profileFileNameAction());
        //this handles other text change events
        profileFileNameTextField.getDocument().addDocumentListener(new profileFileNameAction());
        profileChooserPanel.add(profileFileNameTextField);
        JButton profileChooseFileButton = new JButton("Browse...");
        profileChooseFileButton.addActionListener(new chooseProfileAction());
        profileChooserPanel.add(profileChooseFileButton);
        profileSelectionPanel.add(profileChooserPanel, BorderLayout.NORTH);

        //subpanel for unique peptides checkbox
        JPanel profileChooserSubSubPanel = new JPanel();
        uniquePeptidesCheckBox = new JCheckBox("Unique peptides", false) {
            public JToolTip createToolTip() {
                return new JMultiLineToolTip();
            }
        };
        uniquePeptidesCheckBox.addItemListener(new uniquePeptidesCheckBoxAction());
        uniquePeptidesCheckBox.setToolTipText("If checked, only unique peptide sequences will be loaded from the profile.");
        uniquePeptidesCheckBox.setSelected(currentParamsCopy.getUniquePeptides());
        profileChooserSubSubPanel.add(uniquePeptidesCheckBox);
        profileSelectionPanel.add(profileChooserSubSubPanel, BorderLayout.SOUTH);

        // Tab 1 sub-panel - selection of codon bias
        JPanel biasSelectionPanel = new JPanel(new BorderLayout());
        biasSelectionPanel.setBorder(BorderFactory.createTitledBorder("Codon Bias File"));

        // bias file options
        JPanel biasFileChooserPanel = new JPanel();
        biasFileNameTextField = new JTextField();
        biasFileNameTextField.setToolTipText("The file name of the codon bias file specification.");
        if (currentParamsCopy.getCodonBiasFile() != null) {
            biasFileNameTextField.setText(currentParamsCopy.getCodonBiasFile().toString());
        }
        biasFileNameTextField.setColumns(30);
        //this only handles VK_ENTER key events
        biasFileNameTextField.addActionListener(new codonBiasFileNameAction());
        //this handles other text change events
        biasFileNameTextField.getDocument().addDocumentListener(new codonBiasFileNameAction());
        biasFileChooserPanel.add(biasFileNameTextField);
        JButton biasFileChooseFileButton = new JButton("Browse...");
        biasFileChooseFileButton.addActionListener(new chooseCodonBiasAction());
        biasFileChooserPanel.add(biasFileChooseFileButton);
        biasSelectionPanel.add(biasFileChooserPanel, BorderLayout.NORTH);

        // Tab 1 sub-panel - selection of output path
        JPanel outputPathSelectionPanel = new JPanel(new BorderLayout());
        outputPathSelectionPanel.setBorder(BorderFactory.createTitledBorder("Output Directory"));
        //output path options
        JPanel outputPathChooserPanel = new JPanel();
        outputPathTextField = new JTextField();
        outputPathTextField.setToolTipText("The directory path where output files will be saved.");
        if (currentParamsCopy.getOutputPath() != null) {
            outputPathTextField.setText(currentParamsCopy.getOutputPath().toString());
        }        
        outputPathTextField.setColumns(30);
        //this only handles VK_ENTER key events
        outputPathTextField.addActionListener(new outputPathAction());
        //this handles other text change events
        outputPathTextField.getDocument().addDocumentListener(new outputPathAction());
        outputPathChooserPanel.add(outputPathTextField);
        JButton outputPathChooseFileButton = new JButton("Browse...");
        outputPathChooseFileButton.addActionListener(new chooseOutputPathAction());
        outputPathChooserPanel.add(outputPathChooseFileButton);
        outputPathSelectionPanel.add(outputPathChooserPanel, BorderLayout.NORTH);

        //create the bottom panel - Open/Clear buttons
        JPanel bottomPanel = new JPanel(new FlowLayout());
        openProfileButton = new JButton("Open");
        openProfileButton.addActionListener(new MakeLogosFromProfileSetAction(allProfiles, currentParamsCopy));
        openProfileButton.setEnabled(false);
        bottomPanel.add(openProfileButton);
        JButton clearButton = new JButton("Clear");
        clearButton.addActionListener(new clearFileFieldsAction());
        bottomPanel.add(clearButton);

        // put tab 1 together
        JPanel tab1Panel = new JPanel();
        tab1Panel.setMinimumSize(new Dimension(100,200));
        tab1Panel.setLayout(new BoxLayout(tab1Panel, BoxLayout.Y_AXIS));
        tab1Panel.add(profileSelectionPanel);
        tab1Panel.add(biasSelectionPanel);
        tab1Panel.add(outputPathSelectionPanel);
        tab1Panel.add(bottomPanel);

        filesPanel.add(tab1Panel, BorderLayout.NORTH);

        return tab1Panel;
    }

    private JPanel old_getAboutPanel() {

        JPanel aboutPanel = new JPanel();
        aboutPanel.setLayout(new FlowLayout());

        JTextArea aboutTextArea = new JTextArea();
        aboutTextArea.setText(
                "\n\n"
                + LolaResources.APPLICATION_NAME + " - "
                + LolaResources.APPLICATION_SHORT_DESCRIPTION
                + "\nVersion " + LolaResources.APPLICATION_VERSION 
                + "(" + LolaResources.APPLICATION_BUILD_DATE + ")"
                + "\n\nMoyez Dharsee\nGary Bader\n\n"
                + LolaResources.APPLICATION_WEB_URL);

        aboutTextArea.setAlignmentX(JTextArea.CENTER_ALIGNMENT);
        aboutTextArea.setAlignmentY(JTextArea.CENTER_ALIGNMENT);
        aboutTextArea.setEditable(false);
        aboutTextArea.setRows(10);
        aboutTextArea.setOpaque(false);

        aboutPanel.setAlignmentX(JPanel.CENTER_ALIGNMENT);
        aboutPanel.add(aboutTextArea);

        return aboutPanel;
    }


    /**
     * Creates the About tab - adapted from MCODE class MCODEAboutDialog
     * @return A JPanel containing the contents of the About tab
     */
    private JPanel getAboutPanel() {

        JPanel aboutPanel = new JPanel();
        aboutPanel.setLayout(new FlowLayout());

        //main panel for dialog box
        JEditorPane editorPane = new JEditorPane();
        editorPane.setOpaque(false);
        editorPane.setMargin(new Insets(10,10,10,10));
        editorPane.setEditable(false);
        editorPane.setEditorKit(new HTMLEditorKit());
        editorPane.addHyperlinkListener(new HyperlinkAction(editorPane));

        editorPane.setText(
                "<html><body><P align=center>" +
                "<b>" + LolaResources.APPLICATION_NAME + " - " + LolaResources.APPLICATION_SHORT_DESCRIPTION + "</b><BR>" +
                "Version " + LolaResources.APPLICATION_VERSION + "<BR>" +
                LolaResources.APPLICATION_BUILD_DATE + "<BR>" +
                "<BR>Moyez Dharsee<BR>" +
                "Gary Bader<BR><BR>" +
                "<a href='http://www.baderlab.org'>Bader Lab<BR>" + LolaResources.APPLICATION_WEB_URL + "</a>");
        aboutPanel.add(editorPane);
        return aboutPanel;
    }

    /**
     * Launches a web browser if a Hyperlink object is clicked
     */
    private class HyperlinkAction implements HyperlinkListener {
        JEditorPane pane;

        public HyperlinkAction(JEditorPane pane) {
            this.pane = pane;
        }

        public void hyperlinkUpdate(HyperlinkEvent event) {
            if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                cytoscape.util.OpenBrowser.openURL(event.getURL().toString());
            }
        }
    }

    private JPanel getLogoTreeParamsPanel() {

        //title sub-panel for logo tree settings
        JPanel logoTreeOutputPanel = new JPanel();
        logoTreeOutputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeOutputPanel.setLayout(new BorderLayout());
        logoTreeOutputPanel.setBorder(BorderFactory.createTitledBorder(""));

        //output left sub-panel
        JPanel leftOutputPanel = new JPanel();
        //LayoutManager layout = new BoxLayout(leftOutputPanel, BoxLayout.Y_AXIS);
        LayoutManager layout = new GridBagLayout();
        leftOutputPanel.setLayout(layout);
        leftOutputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);        


        //tree title
        JPanel titlePanel = new JPanel();
        LayoutManager titleLayout = new BoxLayout(titlePanel, BoxLayout.X_AXIS);
        titlePanel.setLayout(titleLayout);
        JLabel treeTitleLabel = new JLabel("Title: ");
        treeTitleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeTitle = new JFormattedTextField();
        logoTreeTitle.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeTitle.setToolTipText("Title to be displayed at the top of the logo tree image.");
        logoTreeTitle.setColumns(30);
        logoTreeTitle.addPropertyChangeListener("value", new textFieldChangeListener());
        
        //initialize title text field value if a valid title is available
        String title = currentParamsCopy.getTreeTitle();
        if (title != null) logoTreeTitle.setText(title);

//        leftOutputPanel.add(treeTitleLabel);
//        leftOutputPanel.add(logoTreeTitle);
        addGridBagComponent(leftOutputPanel, treeTitleLabel, 0, 0, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(leftOutputPanel, logoTreeTitle, 1, 0, 1, 1,
                GridBagConstraints.CENTER, GridBagConstraints.BOTH);

//        leftOutputPanel.add(titlePanel);


        //output file name
        JPanel outputPanel = new JPanel();
        LayoutManager outputLayout = new BoxLayout(outputPanel, BoxLayout.X_AXIS);
        outputPanel.setLayout(outputLayout);
        JLabel outputNameLabel = new JLabel();
        outputNameLabel.setText("Output Filename: ");
        outputNameLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeOutputFilename = new JFormattedTextField();
        logoTreeOutputFilename.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeOutputFilename.setToolTipText("Name of logo tree output file. DIRECTORY PATH IS NOT REQUIRED.   The file will be saved in the Output Directory set in the Files panel.");
        logoTreeOutputFilename.setColumns(30);
        logoTreeOutputFilename.addPropertyChangeListener("value", new textFieldChangeListener());
//        leftOutputPanel.add(outputNameLabel);
//        leftOutputPanel.add(logoTreeOutputFilename);
        addGridBagComponent(leftOutputPanel, outputNameLabel, 0, 1, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(leftOutputPanel, logoTreeOutputFilename, 1, 1, 1, 1,
                GridBagConstraints.CENTER, GridBagConstraints.BOTH);


//        leftOutputPanel.add(outputPanel);

        //output right sub-panel
        JPanel rightOutputPanel = new JPanel();
        rightOutputPanel.setLayout(new FlowLayout());
        //logo format
        JPanel logoFormatPanel = new JPanel();
        JLabel logoFormatLabel = new JLabel();
        logoFormatLabel.setText("Output Format:");
        logoFormatComboBox = new JComboBox(LolaResources.TREE_IMAGE_FORMAT_STRINGS);
        logoFormatComboBox.setToolTipText("Ouput file format of logo tree.");
        //set currently selected format
        String currentFormat = currentParamsCopy.getLogoImageFileFormat();
        if (currentFormat != null) {
            for (int i = 0; i < LolaResources.TREE_IMAGE_FORMAT_STRINGS.length; i++) {
                if (currentFormat.equalsIgnoreCase(LolaResources.TREE_IMAGE_FORMAT_STRINGS[i])) {
                    logoFormatComboBox.setSelectedIndex(i);
                    break;
                }
            }
        }
        logoFormatComboBox.addActionListener(new selectLogoTreeFormatAction());
//        logoFormatPanel.add(logoFormatLabel);
//        logoFormatPanel.add(logoFormatComboBox);
//        logoFormatPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
//        rightOutputPanel.add(logoFormatPanel);
        addGridBagComponent(leftOutputPanel, logoFormatLabel, 0, 2, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(leftOutputPanel, logoFormatComboBox, 1, 2, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);



        logoTreeOutputPanel.add(leftOutputPanel, BorderLayout.BEFORE_FIRST_LINE);
        logoTreeOutputPanel.add(rightOutputPanel, BorderLayout.AFTER_LAST_LINE);

        //create the bottom button panel
        JPanel bottomPanel = new JPanel(new FlowLayout());
        createLogoTreeButton = new JButton("Generate");
//        createLogoTreeButton.addActionListener(new MakeLogoTreeAction(allProfiles, currentParamsCopy));
        createLogoTreeButton.addActionListener(new generateLogoTreeAction());
        createLogoTreeButton.setEnabled(false);
        bottomPanel.add(createLogoTreeButton);
        saveLogoTreeButton = new JButton("Save");
        saveLogoTreeButton.addActionListener(new SaveLogoTreeToFile(allProfiles, currentParamsCopy));
        saveLogoTreeButton.setEnabled(false);
        bottomPanel.add(saveLogoTreeButton);

        //the panel to return
        JPanel thePanel = new JPanel(new BorderLayout());
        thePanel.add(logoTreeOutputPanel, BorderLayout.NORTH);
        thePanel.add(bottomPanel, BorderLayout.SOUTH);
        return thePanel;


    }

    private static void addGridBagComponent(
            Container container, Component component,
            int gridx, int gridy, int gridwidth, int gridheight, int anchor, int fill) {

        Insets insets = new Insets(5,0,0,5);
        GridBagConstraints gbc = new GridBagConstraints(gridx, gridy, gridwidth, gridheight, 1.0, 1.0,
                anchor, fill, insets, 1, 0);
        container.add(component, gbc);
    }

    

    private JPanel getLogoGenerationParamsPanel() {

        // titled sub-panel for logo settings
        JPanel logoSettingsPanel = new JPanel();
        logoSettingsPanel.setLayout(new FlowLayout());
        logoSettingsPanel.setBorder(BorderFactory.createTitledBorder("Logo Settings"));

        // left panel in logo settings
        JPanel leftSettingsPanel = new JPanel();
        leftSettingsPanel.setLayout(new BoxLayout(leftSettingsPanel, BoxLayout.Y_AXIS));

        //start index
        JPanel startIndexPanel = new JPanel(new BorderLayout());
        JLabel startIndexLabel = new JLabel();
        startIndexLabel.setText("Start Index:");
        logoStartIndexTextField = new JFormattedTextField();
        logoStartIndexTextField.setToolTipText("The first index on the X-axis of the logo graph.");
        logoStartIndexTextField.setValue(currentParamsCopy.getLogoStartIndex());
        logoStartIndexTextField.setColumns(3);
        logoStartIndexTextField.addPropertyChangeListener("value", new textFieldChangeListener());
//        logoStartIndexTextField.setInputVerifier(new numericalTextVerifier()); //just experimenting
        startIndexPanel.add(startIndexLabel, BorderLayout.WEST);
        startIndexPanel.add(logoStartIndexTextField, BorderLayout.EAST);
        startIndexPanel.setAlignmentX(JPanel.RIGHT_ALIGNMENT);
        leftSettingsPanel.add(startIndexPanel);

        //logo height
        JPanel logoHeightPanel = new JPanel(new BorderLayout());
        JLabel logoHeightLabel = new JLabel();
        logoHeightLabel.setText("Height:");
        logoHeightTextField = new JFormattedTextField();
        logoHeightTextField.setToolTipText("The height of the logo image, in pixels.");
        logoHeightTextField.setValue(currentParamsCopy.getLogoHeight());
        logoHeightTextField.setColumns(3);
        logoHeightTextField.addPropertyChangeListener("value", new textFieldChangeListener());
        logoHeightPanel.add(logoHeightLabel, BorderLayout.WEST);
        logoHeightPanel.add(logoHeightTextField, BorderLayout.EAST);
        logoHeightPanel.setAlignmentX(JPanel.RIGHT_ALIGNMENT);
        leftSettingsPanel.add(Box.createRigidArea(new Dimension(0,5)));
        leftSettingsPanel.add(logoHeightPanel);

        //logo trim percentage
        JPanel logoTrimPercentagePanel = new JPanel(new BorderLayout());
        JLabel logoTrimPercentageLabel = new JLabel();
        logoTrimPercentageLabel.setText("Trim Percentage:");
        logoTrimPercentageTextField = new JFormattedTextField() {
            public JToolTip createToolTip() {
                JMultiLineToolTip t = new JMultiLineToolTip();
                t.setColumns(40);               
                return t;
            }
        };
        logoTrimPercentageTextField.setToolTipText("If columns on either end of the profile are not above this height" +
                " (in terms of percentage of total possible column height), they will be trimmed." +
                " This has the effect of highlighting the most informative contiguous region. The value must be" +
                " between 0 and 1, inclusively.");
        logoTrimPercentageTextField.setValue(currentParamsCopy.getLogoTrimPercentage());
        logoTrimPercentageTextField.setColumns(3);
        logoTrimPercentageTextField.addPropertyChangeListener("value", new textFieldChangeListener());
        logoTrimPercentagePanel.add(logoTrimPercentageLabel, BorderLayout.WEST);
        logoTrimPercentagePanel.add(logoTrimPercentageTextField, BorderLayout.EAST);
        logoTrimPercentagePanel.setAlignmentX(JPanel.RIGHT_ALIGNMENT);
        leftSettingsPanel.add(Box.createRigidArea(new Dimension(0,5)));
        leftSettingsPanel.add(logoTrimPercentagePanel);

        // right panel in logo settings
        JPanel rightSettingsPanel = new JPanel();
        rightSettingsPanel.setLayout(new BoxLayout(rightSettingsPanel, BoxLayout.Y_AXIS));

        //colour style
        JPanel logoColorPanel = new JPanel();
        JLabel logoColorLabel = new JLabel();
        logoColorLabel.setText("Color Style: ");
        logoColorComboBox = new JComboBox(LolaResources.COLOR_STYLES);
        logoColorComboBox.setToolTipText("Residue coloring scheme");
        String currentColor = currentParamsCopy.getLogoColorStyle();
        if (currentColor != null) {
            for (int i = 0; i < LolaResources.COLOR_STYLES.length; i++) {
                if (currentColor.equalsIgnoreCase(LolaResources.COLOR_STYLES[i])) {
                    logoColorComboBox.setSelectedIndex(i);
                    break;
                }
            }
        }
        logoColorComboBox.addActionListener(new selectColorStyleAction());
        logoColorPanel.add(logoColorLabel);
        logoColorPanel.add(logoColorComboBox);
        logoColorPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
        rightSettingsPanel.add(logoColorPanel);

        //logo format
        JPanel logoFormatPanel = new JPanel();
        JLabel logoFormatLabel = new JLabel();
        logoFormatLabel.setText("Output Format:");
        logoFormatComboBox = new JComboBox(LolaResources.IMAGE_FORMAT_STRINGS);
        logoFormatComboBox.setToolTipText("Ouput file format of logo image.");
        //set currently selected format
        String currentFormat = currentParamsCopy.getLogoImageFileFormat();
        if (currentFormat != null) {
            for (int i = 0; i < LolaResources.IMAGE_FORMAT_STRINGS.length; i++) {
                if (currentFormat.equalsIgnoreCase(LolaResources.IMAGE_FORMAT_STRINGS[i])) {
                    logoFormatComboBox.setSelectedIndex(i);
                    break;
                }
            }
        }
        logoFormatComboBox.addActionListener(new selectLogoFormatAction());
        logoFormatPanel.add(logoFormatLabel);
        logoFormatPanel.add(logoFormatComboBox);
        logoFormatPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
        rightSettingsPanel.add(logoFormatPanel);

        //create the bottom button panel
        JPanel bottomPanel = new JPanel(new FlowLayout());
        drawLogoButton = new JButton("Redraw All");
//        drawLogoButton.addActionListener(new DuplicateProfilesAndLogosAction(selectedProfiles, allProfiles, currentParamsCopy));
        drawLogoButton.addActionListener(new MakeLogosFromProfileSetAction(allProfiles, currentParamsCopy));
        drawLogoButton.setEnabled(false);
        bottomPanel.add(drawLogoButton);

        saveLogoButton = new JButton("Save All");
        saveLogoButton.addActionListener(new SaveLogosToFileAction(allProfiles, currentParamsCopy));
        saveLogoButton.setEnabled(false);
        bottomPanel.add(saveLogoButton);

//        JButton clearButton = new JButton("Clear");
//        clearButton.addActionListener(new clearFileFieldsAction());
//        bottomPanel.add(clearButton);


        //construct the main panel
        logoSettingsPanel.add(leftSettingsPanel);
        logoSettingsPanel.add(Box.createRigidArea(new Dimension(0,50)));
        logoSettingsPanel.add(rightSettingsPanel);
        JPanel thePanel = new JPanel(new BorderLayout());
        thePanel.add(logoSettingsPanel, BorderLayout.NORTH);
        thePanel.add(bottomPanel, BorderLayout.SOUTH);
        return thePanel;
    }

    /**
     * Input verifier for JTextField components.
     * TODO This input verifier doesn't seem to work - fix it.
     */
    class numericalTextVerifier extends InputVerifier {

        /**
         * Returns true iff text can be parsed into an integer.
         * @param jComponent A JTextField component
         * @return boolean
         */
        public boolean verify(JComponent jComponent) {
            if (jComponent instanceof JTextField) {
                String text = ((JTextField) jComponent).getText();
                if (text != null) {
                    try {
                        int i = Integer.valueOf(text);
                        return true;
                    } catch (NumberFormatException e) {
                        return false;
                    }
                }
                return false;
            }
            return false;  //To change body of implemented methods use File | Settings | File Templates.
        }

        public boolean shouldYieldFocus() {
            return false;
        }
    }

    
    /**
     * Handles setting profile filename if the user edits the text field directly without using the file chooser
     */
    private class profileFileNameAction extends AbstractAction implements DocumentListener {

        profileFileNameAction() {
            super();
        }

        //any change that is made is handled the same - parameter is updated
        private void handleChange() {
            String value = profileFileNameTextField.getText();
            if (value != null) {
                File file = new File(value);
                currentParamsCopy.setProfileFile(file);

                //enable open profile button, redraw button, save button
                openProfileButton.setEnabled(true);

                //set output path text field to this profile file's parent path
                String parentPath = file.getParent();
                if (parentPath != null) {
                    outputPathTextField.setText(parentPath);
                }

                //set logo tree output name text field
                String fileName = file.getName()
                        + LolaResources.TREE_FILE_SUFFIX
                        + LolaResources.getFileExtension(currentParamsCopy.getTreeImageFormat());

                logoTreeOutputFilename.setText(fileName);
                currentParamsCopy.setTreeFilename(fileName);
            }
        }

        public void actionPerformed(ActionEvent e) {
            handleChange();
        }

        public void changedUpdate(DocumentEvent e) {
            handleChange();
        }

        public void insertUpdate(DocumentEvent e) {
            handleChange();
        }

        public void removeUpdate(DocumentEvent e) {
            handleChange();
        }
    }

    /**
     * Handles choosing a profile
     */
    private class chooseProfileAction extends AbstractAction {

        chooseProfileAction() {
            super();
        }

        public void actionPerformed(ActionEvent e) {
            //File chosenFile = FileUtil.getFile("Select profiles...", FileUtil.LOAD);
            File chosenFile = browseFile("Select Profile File", false);
            if (chosenFile != null) {
                currentParamsCopy.setProfileFile(chosenFile);
                profileFileNameTextField.setText(chosenFile.toString());
            }
        }
    }

    
    /**
     * Handles setting of the 'unique peptides' parameter
     */
    private class uniquePeptidesCheckBoxAction implements ItemListener {

        public void itemStateChanged(ItemEvent e) {
            if (e.getStateChange() == ItemEvent.DESELECTED) {
                currentParamsCopy.setUniquePeptides(false);
            }
            else if (e.getStateChange() == ItemEvent.SELECTED) {
                currentParamsCopy.setUniquePeptides(true);
            }
        }
    }


    /**
     * Handles setting codon bias specification filename if the user edits the text field directly without using the file chooser
     */
    private class codonBiasFileNameAction extends AbstractAction implements DocumentListener {

        codonBiasFileNameAction() {
            super();
        }

        //any change that is made is handled the same - parameter is updated
        private void handleChange() {
            String value = biasFileNameTextField.getText();
            if (value != null) {
                File file = new File(value);
                currentParamsCopy.setCodonBiasFile(file);
            }
        }

        public void actionPerformed(ActionEvent e) {
            handleChange();
        }

        public void changedUpdate(DocumentEvent e) {
            handleChange();
        }

        public void insertUpdate(DocumentEvent e) {
            handleChange();
        }

        public void removeUpdate(DocumentEvent e) {
            handleChange();
        }
    }

    /**
     * Handles choosing a codon bias file
     */
    private class chooseCodonBiasAction extends AbstractAction {

        chooseCodonBiasAction() {
            super();
        }

        public void actionPerformed(ActionEvent e) {
            //File chosenFile = FileUtil.getFile("Select Codon Bias Specification...", FileUtil.LOAD);
            File chosenFile = browseFile("Select Codon Bias Specification File", false);
            if (chosenFile != null) {
                currentParamsCopy.setCodonBiasFile(chosenFile);
                biasFileNameTextField.setText(chosenFile.toString());
            }
        }
    }


    /**
     * Handles setting output path if the user edits the text field directly without using the file chooser
     */
    private class outputPathAction extends AbstractAction implements DocumentListener {

        outputPathAction() {
            super();
        }

        //any change that is made is handled the same - parameter is updated
        private void handleChange() {
            String value = outputPathTextField.getText();
            if (value != null) {
                File file = new File(value);
                currentParamsCopy.setOutputPath(file);
            }
        }

        public void actionPerformed(ActionEvent e) {
            handleChange();
        }

        public void changedUpdate(DocumentEvent e) {
            handleChange();
        }

        public void insertUpdate(DocumentEvent e) {
            handleChange();
        }

        public void removeUpdate(DocumentEvent e) {
            handleChange();
        }
    }

    /**
     * Handles choosing an output path using the file chooser
     */
    private class chooseOutputPathAction extends AbstractAction {

        chooseOutputPathAction() {
            super();
        }

        public void actionPerformed(ActionEvent e) {
            //File chosenFile = FileUtil.getFile("Select Codon Bias Specification...", FileUtil.LOAD);
            File chosenFile = browseDirectory("Select Output Path", true);
            if (chosenFile != null) {
                currentParamsCopy.setOutputPath(chosenFile);
                outputPathTextField.setText(chosenFile.toString());
            }
        }
    }

    /**
     * Handles setting output path if the user edits the text field directly without using the file chooser
     */
    private class logoStartIndexTextFieldAction extends AbstractAction implements DocumentListener {

        logoStartIndexTextFieldAction() {
            super();
        }

        //any change that is made is handled the same - parameter is updated
        private void handleChange() {
            String value = logoStartIndexTextField.getText();
            if (value != null) {
                try {
                    currentParamsCopy.setLogoStartIndex(Integer.valueOf(value));
                } catch (NumberFormatException e) {
                    //TODO report a visible error message if this happens
                }
            }
        }

        public void actionPerformed(ActionEvent e) {
            handleChange();
        }

        public void changedUpdate(DocumentEvent e) {
            handleChange();
        }

        public void insertUpdate(DocumentEvent e) {
            handleChange();
        }

        public void removeUpdate(DocumentEvent e) {
            handleChange();
        }
    }

    /**
     * Handles setting of values in text fields
     */
    private class textFieldChangeListener implements PropertyChangeListener {

        public void propertyChange(PropertyChangeEvent event) {
            Object obj = event.getSource();
            if (obj instanceof JTextField) {
                JTextField textField = (JTextField) obj;
                String text = textField.getText();
                if (text != null) {
                    //deal with negative value
                    boolean isNegative = false;
                    if ((text.length() > 1) && text.substring(0,1).equals("-")) {
                        isNegative = true;
                        Number value = (Number) Integer.valueOf(text);
                    }
                    //logo start index
                    if (textField == logoStartIndexTextField) {
                        
//                        Number value = (Number) Integer.valueOf(text);
                        Number value = (Number) logoStartIndexTextField.getValue();
                        if (value != null) {
                            currentParamsCopy.setLogoStartIndex(value.intValue());
                        } else {
                            //TODO report a visible error message if this happens
                            logoStartIndexTextField.setValue(currentParamsCopy.getLogoStartIndex());
                        }
                    }
                    //logo height
                    else if (textField == logoHeightTextField) {            
                        Number value = (Number) logoHeightTextField.getValue();
                        if ((value != null) && (value.intValue() >= LolaResources.LOGO_HEIGHT_MIN)
                                && (value.intValue() <= LolaResources.LOGO_HEIGHT_MAX)) {
                            currentParamsCopy.setLogoHeight(value.intValue());
                        } else {
                            //TODO report a visible error message if this happens
                            logoHeightTextField.setValue(currentParamsCopy.getLogoHeight());
                        }
                    }
                    //logo trim percentage
                    else if (textField == logoTrimPercentageTextField) {
                        Number value = (Number) logoTrimPercentageTextField.getValue();
                        if ((value != null) && (value.doubleValue() >= LolaResources.LOGO_TRIM_PERCENTAGE_MIN)
                                && (value.doubleValue() <= LolaResources.LOGO_TRIM_PERCENTAGE_MAX)) {
                            currentParamsCopy.setLogoTrimPercentage(value.doubleValue());
                        } else {
                            //TODO report a visible error message if this happens
                            logoTrimPercentageTextField.setValue(currentParamsCopy.getLogoTrimPercentage());
                        }
                    }
                    //logo tree title
                    else if (textField == logoTreeTitle) {
                        if ((text != null) && (text.length() > 0)) {
                            currentParamsCopy.setTreeTitle(text);
                        }
                        else {
                            currentParamsCopy.setTreeTitle("");
                        }
                    }
                    //logo tree filename
                    else if (textField == logoTreeOutputFilename) {
                        if ((text != null) && (text.length() > 0)) {
                            currentParamsCopy.setTreeFilename(text);
                        }
                    }
                }
            }
        }
    }


    /**
     * Handles choosing a logo output file format
     */
    private class selectLogoFormatAction extends AbstractAction {
        selectLogoFormatAction() {
            super();
        }

        public void actionPerformed(ActionEvent event) {
            if ("comboBoxChanged".equals(event.getActionCommand())) {
                JComboBox cb = (JComboBox) event.getSource();
                String formatName = (String) cb.getSelectedItem();
                currentParamsCopy.setLogoImageFormat(formatName);
            }
        }
    }

    /**
     * Handles choosing a logo color style
     */
    private class selectColorStyleAction extends AbstractAction {
        selectColorStyleAction() {
            super();
        }

        public void actionPerformed(ActionEvent event) {
            if ("comboBoxChanged".equals(event.getActionCommand())) {
                JComboBox cb = (JComboBox) event.getSource();
                String colorName = (String) cb.getSelectedItem();
                currentParamsCopy.setLogoColorStyle(colorName);
            }
        }
    }

    /**
     * Handles choosing a logo tree output file format
     */
    private class selectLogoTreeFormatAction extends AbstractAction {
        selectLogoTreeFormatAction() {
            super();
        }

        public void actionPerformed(ActionEvent event) {
            if ("comboBoxChanged".equals(event.getActionCommand())) {
                JComboBox cb = (JComboBox) event.getSource();
                String formatName = (String) cb.getSelectedItem();
                currentParamsCopy.setTreeImageFormat(formatName);
            }
        }
    }


    /**
     * Displays a file dialog with the given title and returns the selected directory.
     * @return a File object for the selected file.
     */
    private File browseDirectory(String title, boolean directoryOnly) {
        //FileDialog fileDialog = new FileDialog(mainFrame, title, FileDialog.LOAD);
        JFileChooser fileDialog = new JFileChooser();
        if (directoryOnly) {
            fileDialog.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        }
        fileDialog.setDialogTitle(title);
        fileDialog.setVisible(true);
        fileDialog.showDialog(mainFrame, "OK");
        File file = fileDialog.getSelectedFile();

        return file;
    }

    /**
     * Displays a file dialog with the given title and returns the selected file.
     * @param title
     * @param directoryOnly
     * @return
     */
    private File browseFile(String title, boolean directoryOnly) {
        FileDialog fileDialog = new FileDialog(mainFrame, title, FileDialog.LOAD);
        fileDialog.setTitle(title);
        fileDialog.setVisible(true);

        String filePath = fileDialog.getFile();
        if (filePath != null) {
            return new File(fileDialog.getDirectory()+"/"+ fileDialog.getFile() );
        }
        return null;
    }

    /**
     * Action for the cancel button (does not save parameters)
     */
    private class clearFileFieldsAction extends AbstractAction {

        clearFileFieldsAction() {
            super();
        }

        public void actionPerformed(ActionEvent e) {
            profileFileNameTextField.setText(null);
            biasFileNameTextField.setText(null);
            outputPathTextField.setText(null);
            logoTreeOutputFilename.setText(null);
            openProfileButton.setEnabled(false);
            createLogoTreeButton.setEnabled(false);
            saveLogoTreeButton.setEnabled(false);


        }
    }

    /**
     * Close all selected profiles.
     */
    private class closeSelectedProfilesAction implements ActionListener {

        public void actionPerformed(ActionEvent actionEvent) {


            while (selectedProfiles.size() > 0) {
                Profile p = (Profile) selectedProfiles.get(0);
                allProfiles.removeElement(p);
            }
            selectedProfiles.removeAllElements();
            closeSelectedProfilesButton.setEnabled(false);
            logoPane.setViewportView(null);
        }
    }

    /**
     * Close all open profiles.
     */
    private class closeAllProfilesAction implements ActionListener {

        public void actionPerformed(ActionEvent actionEvent) {

            allProfiles.removeAllElements();
            closeSelectedProfilesButton.setEnabled(false);

            logoPane.setViewportView(null);
        }
    }

    /**
     * Exit the application.
     */
    private class quitAction implements ActionListener {

        public void actionPerformed(ActionEvent actionEvent) {
            Runtime.getRuntime().exit(0);
        }
    }

    /**
     * Displays the logo tree for all open profiles in the logo pane
     */
    private void displayLogoTree() {
        try {
            LogoTree tree = allProfiles.getLogoTree();
            if (tree != null) {
                Image image = tree.getImage();
                if (image != null) {
                    Icon icon = new ImageIcon(image);
                    logoPane.setViewportView(new JLabel(icon));
                }
                else {
                    throw new Exception ("Could not obtain logo tree image.");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * Display the (last) selected image based on the logo selected in profileList
     */
    private void displaySelectedLogo() {
        Object[] objs = profileList.getSelectedValues();
        if (objs.length < 1) return;
        Profile profile = (Profile)objs[objs.length-1];
        Icon icon = new ImageIcon(profile.getImage());
        logoPane.setViewportView(new JLabel(icon));
    }


    /**
     * A mouse listener for the main JTabbedPane.
     * Displays the logo tree image in the logo pane when the Logo Tree tab is selected with the mosue. 
     */
    private class tabbedPaneMouseListener implements MouseListener {

        public void mouseClicked(MouseEvent mouseEvent) {
            Component selectedComponent = tabbedPane.getSelectedComponent();
            if (selectedComponent.equals(treePanel)) {
                displayLogoTree();
            }
            else if (selectedComponent.equals(logoPanel)) {
                displaySelectedLogo();
            }
        }

        public void mousePressed(MouseEvent mouseEvent) {
            //do nothing
        }

        public void mouseReleased(MouseEvent mouseEvent) {
            //do nothing
        }

        public void mouseEntered(MouseEvent mouseEvent) {
            //do nothing
        }

        public void mouseExited(MouseEvent mouseEvent) {
            //do nothing
        }
    }

    /**
     * This is an attempt to fire an event to MakeLogoTreeAction and, when done, display the generated logo tree.
     * TODO - make sure this is the "correct" way of doing this, i.e. is it "ok" for an action to fire another action??
     *
     */
    private class generateLogoTreeAction implements ActionListener {
        public void actionPerformed(ActionEvent actionEvent) {
            ActionListener action = new MakeLogoTreeAction(allProfiles, currentParamsCopy);
            action.actionPerformed(actionEvent);
            displayLogoTree();
        }
    }
}
