/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2007-2015 Fred Hutchinson Cancer Research Center and Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


package org.broad.igv.ui.panel;

import org.broad.igv.event.IGVEvent;
import org.broad.igv.logging.*;
import org.broad.igv.feature.Range;
import org.broad.igv.feature.RegionOfInterest;
import org.broad.igv.feature.Strand;
import org.broad.igv.feature.genome.GenomeManager;
import org.broad.igv.lists.GeneList;
import org.broad.igv.ui.IGV;
import org.broad.igv.event.IGVEventBus;
import org.broad.igv.event.IGVEventObserver;
import org.broad.igv.event.ViewChange;
import org.broad.igv.ui.util.IGVMouseInputAdapter;
import org.broad.igv.util.StringUtils;

import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.event.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;

/**
 * @author Damon May
 *         <p/>
 *         This dialog displays a list of RegionOfInterest in a table and allows editing.
 *         Navigation to the start of a region is done by selecting the appropriate row.
 *         <p/>
 *         This dialog is not intended to be persistent.  To view one of these, create it.
 *         <p/>
 */
public class RegionNavigatorDialog extends org.broad.igv.ui.IGVDialog  implements Observer, IGVEventObserver {

    private static Logger log = LogManager.getLogger(AttributePanel.class);

    //Column indexes, in case table structure changes
    private static final int TABLE_COLINDEX_CHR = 0;
    private static final int TABLE_COLINDEX_START = 1;
    private static final int TABLE_COLINDEX_END = 2;
    private static final int TABLE_COLINDEX_DESC = 3;

    //The active instance of RegionNavigatorDialog (only one at a time)
    public static RegionNavigatorDialog activeInstance;


    private DefaultTableModel regionTableModel;
//    private List<RegionOfInterest> regions;

    private TableRowSorter<TableModel> regionTableRowSorter;

    //Indicates that we're in the process of synching the table with the regions list, so we shouldn't
    //do anything about TableChanged events.
    private boolean synchingRegions = false;


    /**
     * Return the active RegionNavigatorDialog. null if none
     *
     * @return
     */
    public static RegionNavigatorDialog getOrCreateInstance(Frame owner) {
        if (activeInstance == null) {
            activeInstance = new RegionNavigatorDialog(owner);
        }
        return activeInstance;
    }

    /**
     * Return the active RegionNavigatorDialog. null if none
     *
     * @return
     */
    public static RegionNavigatorDialog getInstance() {
        return activeInstance;
    }

    /**
     * dispose the active instance and get rid of the pointer. Return whether or not there was an
     * active instance
     */
    public static boolean destroyInstance() {
        if (activeInstance == null)
            return false;
        activeInstance.dispose();
        activeInstance = null;
        return true;
    }

    private RegionNavigatorDialog(Frame owner) {
        super(owner);
        initComponents();
        postInit();
    }

    private RegionNavigatorDialog(Dialog owner) {
        super(owner);
        initComponents();
        postInit();
    }

    public void update(Observable observable, Object object) {
        synchRegions();
    }

    public void receiveEvent(IGVEvent e) {
            synchRegions();
    }

    /**
     * Synchronize the regions ArrayList with the passed-in regionsCollection, and update UI
     */
    public void synchRegions() {
        //Indicate that we're synching regions, so that we don't respond to tableChanged events
        synchingRegions = true;
        List<RegionOfInterest> regions = retrieveRegionsAsList();
        regionTableModel = (DefaultTableModel) regionTable.getModel();
        while (regionTableModel.getRowCount() > 0) {
            regionTableModel.removeRow(0);
        }
        regionTableModel.setRowCount(regions.size());
        for (int i = 0; i < regions.size(); i++) {
            RegionOfInterest region = regions.get(i);

            regionTableModel.setValueAt(region.getDescription(), i, TABLE_COLINDEX_DESC);
            regionTableModel.setValueAt(region.getDisplayStart(), i, TABLE_COLINDEX_START);
            regionTableModel.setValueAt(region.getDisplayEnd(), i, TABLE_COLINDEX_END);
            regionTableModel.setValueAt(region.getChr(), i, TABLE_COLINDEX_CHR);
        }
        //Done synching regions, allow ourselves to respond to tableChanged events
        synchingRegions = false;

        regionTableModel.fireTableDataChanged();
    }

    private List<RegionOfInterest> retrieveRegionsAsList() {
        return new ArrayList<RegionOfInterest>(IGV.getInstance().getSession().getAllRegionsOfInterest());
    }

    /**
     * Populate the table with the loaded regions
     */
    private void postInit() {
        regionTableModel = (DefaultTableModel) regionTable.getModel();

        regionTable.getSelectionModel().addListSelectionListener(new RegionTableSelectionListener());
        regionTableModel.addTableModelListener(new RegionTableModelListener());

        //custom row sorter required for displaying only a subset of rows
        regionTableRowSorter = new TableRowSorter<TableModel>(regionTableModel);
        regionTable.setRowSorter(regionTableRowSorter);
        regionTableRowSorter.setRowFilter(new RegionRowFilter());

        textFieldSearch.getDocument().addDocumentListener(new SearchFieldDocumentListener());

        updateChromosomeDisplayed();

        synchRegions();

        ReferenceFrame defFrame = FrameManager.getDefaultFrame();
        IGVEventBus.getInstance().subscribe(ViewChange.class, this);
        IGV.getInstance().getSession().getRegionsOfInterestObservable().addObserver(this);

        //resize window if small number of regions.  By default, tables are initialized with 20
        //rows, and that can look ungainly for empty windows or windows with a few rows.
        //This correction is rather hacky. Minimum size of 5 rows set.
        int newTableHeight = Math.min(regionTableModel.getRowCount() + 1, 5) * regionTable.getRowHeight();
        //This is quite hacky -- need to find the size of the other components programmatically somehow, since
        //it will vary on different platforms
        int extraHeight = 225;

        int newDialogHeight = newTableHeight + extraHeight;
        if (newDialogHeight < getHeight()) {
            regionTable.setPreferredScrollableViewportSize(new Dimension(regionTable.getPreferredSize().width,
                    newTableHeight));
            setSize(getWidth(), newTableHeight + extraHeight);
            update(getGraphics());
        }

        regionTable.addMouseListener(new RegionTablePopupHandler());
        updateButtonsEnabled();
    }

    private class SearchFieldDocumentListener implements DocumentListener {
        public void changedUpdate(DocumentEvent e) {
            System.err.println("This should not happen");
        }

        public void insertUpdate(DocumentEvent e) {
            regionTableModel.fireTableDataChanged();
        }

        public void removeUpdate(DocumentEvent e) {
            regionTableModel.fireTableDataChanged();
        }
    }

    /**
     * When chromosome that's displayed is changed, need to update displayed regions.  showSearchedRegions will do that
     */
    public void updateChromosomeDisplayed() {
//        regionTable.updateUI();
//        showSearchedRegions();
        regionTableModel.fireTableDataChanged();
    }

    /**
     * Test whether we should display an entry
     *
     * @param regionChr
     * @param regionDesc
     * @return
     */
    protected boolean shouldIncludeRegion(String regionChr, String regionDesc) {
        //if table is empty, a non-region event is fed here.  Test for it and don't display
        if (regionChr == null)
            return false;

        String filterStringLowercase = null;
        if (textFieldSearch.getText() != null)
            filterStringLowercase = textFieldSearch.getText().toLowerCase();

        String chr = FrameManager.getDefaultFrame().getChrName();

        //show only regions matching the search string (if specified)
        if ((filterStringLowercase != null && !filterStringLowercase.isEmpty() &&
                (regionDesc == null || !regionDesc.toLowerCase().contains(filterStringLowercase))))
            return false;

        //if this checkbox is checked, show all chromosomes
        if (checkBoxShowAllChrs.isSelected())
            return true;

        //show only regions in the current chromosome
        if (chr != null && !chr.isEmpty() && !regionChr.equals(chr))
            return false;


        return true;
    }

    /**
     * A row filter that shows only rows that contain filterString, case-insensitive
     */
    private class RegionRowFilter extends RowFilter<TableModel, Object> {

        public RegionRowFilter() {
            super();
        }

        public boolean include(RowFilter.Entry entry) {
            return shouldIncludeRegion((String) entry.getValue(TABLE_COLINDEX_CHR),
                    (String) entry.getValue(TABLE_COLINDEX_DESC));
        }
    }

    /**
     * Listen for updates to the cells, save changes to the Regions
     */
    private class RegionTableModelListener implements TableModelListener {
        public void tableChanged(TableModelEvent e) {
            //If we're in the middle of synching regions, do nothing
            if (synchingRegions)
                return;

            List<RegionOfInterest> regions = retrieveRegionsAsList();
            int firstRow = e.getFirstRow();
            //range checking because this method gets called after a clear event, and we don't want to
            //try to find an updated region then
            if (firstRow > regions.size() - 1)
                return;
            //update all rows affected
            for (int i = firstRow; i <= Math.max(firstRow, Math.min(regionTable.getRowCount(), e.getLastRow())); i++)
                updateROIFromRegionTable(i);
        }
    }

    /**
     * Updates all ROIs with the values currently stored in the region table
     */
    public void updateROIsFromRegionTable() {
        for (int i = 0; i < regionTable.getRowSorter().getModelRowCount(); i++)
            updateROIFromRegionTable(i);
    }

    /**
     * Updates a single ROI with the values currently stored in the region table
     *
     * @param tableRow: the viewable index of the table row
     */
    public void updateROIFromRegionTable(int tableRow) {
        List<RegionOfInterest> regions = retrieveRegionsAsList();

        if (tableRow > regionTable.getRowCount() - 1)
            return;

        //must convert row index from view to model, in case of sorting, filtering
        int rowIdx = 0;

        try {
            rowIdx = regionTable.getRowSorter().convertRowIndexToModel(tableRow);
        } catch (ArrayIndexOutOfBoundsException x) {
            return;
        }

        RegionOfInterest region = regions.get(rowIdx);

        //dhmay changing 20110505: just update region values from all columns, instead of checking the event
        //to see which column is affected. This is in response to an intermittent bug.

        Object descObject = regionTableModel.getValueAt(rowIdx, TABLE_COLINDEX_DESC);
        if (descObject != null)
            region.setDescription(descObject.toString());

        //stored values are 0-based end-exclusive, viewed values are 1-based end-inclusive.  Check for negative number just in case
        int storeStartValue =
                Math.max(0, (Integer) regionTableModel.getValueAt(rowIdx, TABLE_COLINDEX_START) - 1);
        region.setStart(storeStartValue);

        int storeEndValue =
                Math.max(0, (Integer) regionTableModel.getValueAt(rowIdx, TABLE_COLINDEX_END));
        region.setEnd(storeEndValue);
    }

    /**
     * Listen for selection change events, navigate UI to start of selected region
     */
    private class RegionTableSelectionListener implements ListSelectionListener {
        public void valueChanged(ListSelectionEvent e) {
            if (!e.getValueIsAdjusting()) {
                List<RegionOfInterest> regions = retrieveRegionsAsList();

                int[] selectedRows = regionTable.getSelectedRows();
                if (selectedRows != null && selectedRows.length > 0
                        && regions.size() >= selectedRows.length)  //dhmay: this is hacky. Bad things can happen with clear regions
                {
                    RegionOfInterest firstStartRegion = null;
                    RegionOfInterest lastEndRegion = null;

                    Set<String> selectedChrs = new HashSet<String>();

                    //Figure out which region has the first start and which has the last end
                    for (int selectedRowIndex : selectedRows) {
                        int selectedModelRow = regionTableRowSorter.convertRowIndexToModel(selectedRowIndex);
                        RegionOfInterest region = regions.get(selectedModelRow);
                        selectedChrs.add(region.getChr());
                        if (firstStartRegion == null || region.getStart() < firstStartRegion.getStart())
                            firstStartRegion = region;
                        if (lastEndRegion == null || region.getEnd() > lastEndRegion.getEnd())
                            lastEndRegion = region;
                    }

                    //If there are multiple chromosomes represented in the selection, do nothing.
                    //Because what would we do? Maybe a status message should be displayed somehow, but a
                    //dialog would get annoying.
                    if (selectedChrs.size() > 1)
                        return;


//                    if (checkBoxZoomWhenNav.isSelected()) {
//                        // Option (1), zoom and center on group of selected regions, with an interval equal to
//                        // 20% of the length of the end regions on either side for context (dhmay reduced from 100%)
//                        int start = firstStartRegion.getStart() - (int) (0.2 * firstStartRegion.getLength());
//                        int end = lastEndRegion.getEnd() + (int) (0.2 * lastEndRegion.getLength());
//                        FrameManager.getDefaultFrame().jumpTo(selectedChrs.iterator().next(), start, end);
//                    } else {
//                        // Option (2), center on the FIRST selected region without changing resolution
//                        FrameManager.getDefaultFrame().centerOnLocation(firstStartRegion.getCenter());
//                    }

                }
            }
        }
    }


    /**
     * Return the selected regions in the table view.
     *
     * @param selectedRows
     * @return
     */
    private List<RegionOfInterest> getSelectedRegions(int[] selectedRows) {
        List<RegionOfInterest> selectedRegions = new ArrayList<RegionOfInterest>();
        List<RegionOfInterest> regions = retrieveRegionsAsList();

        for (int selectedRowIndex : selectedRows) {
            int selectedModelRow = regionTableRowSorter.convertRowIndexToModel(selectedRowIndex);
            selectedRegions.add(regions.get(selectedModelRow));
        }
        return selectedRegions;
    }

    private void regionTableMouseClicked(MouseEvent e) {
        //We update the state of associated buttons
        //whenever the user clicks a mouse button
        updateButtonsEnabled();
    }

    private void updateButtonsEnabled() {
        boolean enableMutStates = regionTable.getSelectedRowCount() >= 1;
        boolean enableZoomToRegion = regionTable.getSelectedRowCount() == 1;
        removeButton.setEnabled(enableMutStates);
        viewButton.setEnabled(enableMutStates);
        checkBoxZoomWhenNav.setEnabled(enableZoomToRegion);
    }

    private void thisWindowActivated(WindowEvent e) {
        synchRegions();
    }

    private void thisWindowDeactivated(WindowEvent e) {
        updateROIsFromRegionTable();
    }

    private void thisWindowClosed(WindowEvent e) {
        IGV.getInstance().getSession().getRegionsOfInterestObservable().deleteObserver(this);
        destroyInstance();
    }


    private void initComponents() {
        // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
        // Generated using JFormDesigner non-commercial license
        dialogPane = new JPanel();
        contentPanel = new JPanel();
        panel3 = new JPanel();
        checkBoxShowAllChrs = new JCheckBox();
        addButton = new JButton();
        removeButton = new JButton();
        scrollPane1 = new JScrollPane();
        regionTable = new JTable();
        panel1 = new JPanel();
        panel2 = new JPanel();
        viewButton = new JButton();
        checkBoxZoomWhenNav = new JCheckBox();
        panel4 = new JPanel();
        label1 = new JLabel();
        textFieldSearch = new JTextField();
        clearSearchButton = new JButton();
        cancelAction = new CancelAction();
        addAction = new AddRegionAction();
        actionRemoveRegions = new RemoveSelectedRegionsAction();
        showAllChromosomesCheckboxAction = new ShowAllChromosomesCheckboxAction();
        viewAction = new ViewSelectedAction();

        //======== this ========
        setTitle("Regions of Interest");
        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowActivated(WindowEvent e) {
                thisWindowActivated(e);
            }

            @Override
            public void windowClosed(WindowEvent e) {
                thisWindowClosed(e);
            }

            @Override
            public void windowDeactivated(WindowEvent e) {
                thisWindowDeactivated(e);
            }
        });
        Container contentPane = getContentPane();
        contentPane.setLayout(new BorderLayout());

        //======== dialogPane ========
        {
            dialogPane.setBorder(null);
            dialogPane.setLayout(new BorderLayout());

            //======== contentPanel ========
            {
                contentPanel.setLayout(new BorderLayout());

                //======== panel3 ========
                {
                    panel3.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
                    panel3.setBorder(LineBorder.createBlackLineBorder());
                    panel3.setLayout(new FlowLayout(FlowLayout.LEFT));

                    //---- checkBoxShowAllChrs ----
                    checkBoxShowAllChrs.setAction(showAllChromosomesCheckboxAction);
                    checkBoxShowAllChrs.setToolTipText("View regions from all chromosomes (otherwise, current chromosome only)");
                    checkBoxShowAllChrs.setSelected(true);
                    panel3.add(checkBoxShowAllChrs);

                    //---- addButton ----
                    addButton.setAction(addAction);
                    addButton.setText("Add");
                    addButton.setActionCommand("Add");
                    panel3.add(addButton);

                    //---- removeButton ----
                    removeButton.setAction(actionRemoveRegions);
                    removeButton.setText("Remove");
                    panel3.add(removeButton);
                }
                contentPanel.add(panel3, BorderLayout.NORTH);

                //======== scrollPane1 ========
                {

                    //---- regionTable ----
                    regionTable.setModel(new DefaultTableModel(
                            new Object[][]{
                                    {null, null, null, null},
                            },
                            new String[]{
                                    "Chr", "Start", "End", "Description"
                            }
                    ) {
                        Class<?>[] columnTypes = new Class<?>[]{
                                String.class, Integer.class, Integer.class, Object.class
                        };
                        boolean[] columnEditable = new boolean[]{
                                false, true, true, true
                        };

                        @Override
                        public Class<?> getColumnClass(int columnIndex) {
                            return columnTypes[columnIndex];
                        }

                        @Override
                        public boolean isCellEditable(int rowIndex, int columnIndex) {
                            return columnEditable[columnIndex];
                        }
                    });
                    {
                        TableColumnModel cm = regionTable.getColumnModel();
                        cm.getColumn(0).setPreferredWidth(50);
                        cm.getColumn(1).setPreferredWidth(100);
                        cm.getColumn(2).setPreferredWidth(100);
                        cm.getColumn(3).setPreferredWidth(200);
                    }
                    regionTable.setAutoCreateRowSorter(true);
                    regionTable.addMouseListener(new IGVMouseInputAdapter() {
                        @Override
                        public void igvMouseClicked(MouseEvent e) {
                            regionTableMouseClicked(e);
                        }
                    });
                    scrollPane1.setViewportView(regionTable);
                }
                contentPanel.add(scrollPane1, BorderLayout.CENTER);

                //======== panel1 ========
                {
                    panel1.setLayout(new BoxLayout(panel1, BoxLayout.Y_AXIS));

                    //======== panel2 ========
                    {
                        panel2.setBorder(LineBorder.createBlackLineBorder());
                        panel2.setLayout(new FlowLayout(FlowLayout.LEFT));

                        //---- viewButton ----
                        viewButton.setText("View");
                        viewButton.setAction(viewAction);
                        viewButton.setActionCommand("View");
                        panel2.add(viewButton);

                        //---- checkBoxZoomWhenNav ----
                        checkBoxZoomWhenNav.setText("Zoom to Region");
                        checkBoxZoomWhenNav.setToolTipText("When navigating to a region, change zoom level?");
                        checkBoxZoomWhenNav.setSelected(true);
                        panel2.add(checkBoxZoomWhenNav);
                    }
                    panel1.add(panel2);

                    //======== panel4 ========
                    {
                        panel4.setBorder(LineBorder.createBlackLineBorder());
                        panel4.setLayout(new FlowLayout(FlowLayout.LEFT));

                        //---- label1 ----
                        label1.setText("Search");
                        panel4.add(label1);

                        //---- textFieldSearch ----
                        textFieldSearch.setToolTipText("Search for regions containing the specified description text.");
                        textFieldSearch.setPreferredSize(new Dimension(200, 28));
                        panel4.add(textFieldSearch);

                        //---- clearSearchButton ----
                        clearSearchButton.setAction(cancelAction);
                        clearSearchButton.setText("Clear Search");
                        panel4.add(clearSearchButton);
                    }
                    panel1.add(panel4);
                }
                contentPanel.add(panel1, BorderLayout.SOUTH);
            }
            dialogPane.add(contentPanel, BorderLayout.CENTER);
        }
        contentPane.add(dialogPane, BorderLayout.CENTER);
        pack();
        setLocationRelativeTo(getOwner());
        // JFormDesigner - End of component initialization  //GEN-END:initComponents
    }

    // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
    // Generated using JFormDesigner non-commercial license
    private JPanel dialogPane;
    private JPanel contentPanel;
    private JPanel panel3;
    private JCheckBox checkBoxShowAllChrs;
    private JButton addButton;
    private JButton removeButton;
    private JScrollPane scrollPane1;
    private JTable regionTable;
    private JPanel panel1;
    private JPanel panel2;
    private JButton viewButton;
    private JCheckBox checkBoxZoomWhenNav;
    private JPanel panel4;
    private JLabel label1;
    private JTextField textFieldSearch;
    private JButton clearSearchButton;
    private CancelAction cancelAction;
    private AddRegionAction addAction;
    private RemoveSelectedRegionsAction actionRemoveRegions;
    private ShowAllChromosomesCheckboxAction showAllChromosomesCheckboxAction;
    private ViewSelectedAction viewAction;
    // JFormDesigner - End of variables declaration  //GEN-END:variables


    private class CancelAction extends AbstractAction {
        private CancelAction() {
            // JFormDesigner - Action initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
            // Generated using JFormDesigner non-commercial license
            putValue(NAME, "Cancel");
            putValue(SHORT_DESCRIPTION, "Clear search box");
            // JFormDesigner - End of action initialization  //GEN-END:initComponents
        }

        public void actionPerformed(ActionEvent e) {
            textFieldSearch.setText("");
        }
    }

    /**
     * Add a new RegionOfInterest for the current chromosome, with 0 start and end
     */
    private class AddRegionAction extends AbstractAction {
        private AddRegionAction() {
            // JFormDesigner - Action initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
            // Generated using JFormDesigner non-commercial license
            putValue(NAME, "Add");
            putValue(SHORT_DESCRIPTION, "Add a new region");
            // JFormDesigner - End of action initialization  //GEN-END:initComponents
        }

        public void actionPerformed(ActionEvent e) {
            String chr = FrameManager.getDefaultFrame().getChrName();
            if (FrameManager.isGeneListMode()) {
                JOptionPane.showMessageDialog(IGV.getInstance().getMainFrame(),
                        "Regions cannot be created in gene list or split-screen views.",
                        "Error", JOptionPane.INFORMATION_MESSAGE);

            } else if (chr == null || chr.isEmpty()) {
                JOptionPane.showMessageDialog(IGV.getInstance().getMainFrame(),
                        "No chromosome is specified. Can't create a region without a chromosome.",
                        "Error", JOptionPane.INFORMATION_MESSAGE);
            } else if (chr.equalsIgnoreCase("All")) {
                JOptionPane.showMessageDialog(IGV.getInstance().getMainFrame(),
                        "Regions cannot be created in the All Chromosomes view.",
                        "Error", JOptionPane.INFORMATION_MESSAGE);
            } else {
                Range r = FrameManager.getDefaultFrame().getCurrentRange();
                RegionOfInterest newRegion = new RegionOfInterest(r.getChr(), r.getStart(), r.getEnd(), "");
                IGV.getInstance().getSession().addRegionOfInterestWithNoListeners(newRegion);
            }
            updateButtonsEnabled();
        }
    }

    private class RemoveSelectedRegionsAction extends AbstractAction {
        private RemoveSelectedRegionsAction() {
            // JFormDesigner - Action initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
            // Generated using JFormDesigner non-commercial license
            putValue(NAME, "Remove");
            putValue(SHORT_DESCRIPTION, "Remove all selected regions");
            // JFormDesigner - End of action initialization  //GEN-END:initComponents
        }

        public void actionPerformed(ActionEvent e) {
            int[] selectedRows = regionTable.getSelectedRows();
            if (selectedRows != null && selectedRows.length > 0) {
                List<RegionOfInterest> selectedRegions = getSelectedRegions(selectedRows);
                IGV.getInstance().getSession().removeRegionsOfInterest(selectedRegions);
                synchRegions();
            } else {
                //todo dhmay -- I don't fully understand this call.  Clean this up.
                JOptionPane.showMessageDialog(IGV.getInstance().getMainFrame(), "No regions have been selected for removal.",
                        "Error", JOptionPane.INFORMATION_MESSAGE);
            }
            updateButtonsEnabled();
        }
    }


    private class ViewSelectedAction extends AbstractAction {
        private ViewSelectedAction() {
            // JFormDesigner - Action initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
            // Generated using JFormDesigner non-commercial license
            putValue(NAME, "View");
            // JFormDesigner - End of action initialization  //GEN-END:initComponents
        }

        public void actionPerformed(ActionEvent e) {
            int[] selectedRows = regionTable.getSelectedRows();
            if (selectedRows != null && selectedRows.length > 0) {
                List<RegionOfInterest> selectedRegions = getSelectedRegions(selectedRows);

                // Create an "on-the-fly" gene list
                // TODO -- this is inefficient (converting regions -> strings then back again)
                List<String> loci = new ArrayList<String>(selectedRegions.size());
                if (checkBoxZoomWhenNav.isSelected() || selectedRegions.size() >= 2 || FrameManager.isGeneListMode()) {
                    for (RegionOfInterest roi : selectedRegions) {
                        loci.add(roi.getLocusString());
                    }
                } else {
                    //Need to preserve current zoom, iff checkbox not selected and only choosing 1
                    RegionOfInterest roi = selectedRegions.get(0);
                    Range range = FrameManager.getDefaultFrame().getCurrentRange();
                    int length = range.getLength();
                    int start = roi.getCenter() - length / 2;
                    int end = start + length;
                    //Shift so we don't go below 0
                    if (start < 0) {
                        end += Math.abs(start);
                        start = 0;
                    }
                    loci.add(new RegionOfInterest(roi.getChr(), start, end, roi.getDescription()).getLocusString());
                }
                GeneList geneList = new GeneList("Regions of Interest", loci);
                IGV.getInstance().setGeneList(geneList);
                IGV.getInstance().resetFrames();

            }
            updateButtonsEnabled();
        }
    }

    private class ShowAllChromosomesCheckboxAction extends AbstractAction {
        private ShowAllChromosomesCheckboxAction() {
            // JFormDesigner - Action initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
            // Generated using JFormDesigner non-commercial license
            putValue(NAME, "Show All Chrs");
            // JFormDesigner - End of action initialization  //GEN-END:initComponents
        }

        public void actionPerformed(ActionEvent e) {
            // TODO add your code here
            synchRegions();
            updateButtonsEnabled();
        }
    }

    /**
     * Creates an appropriate popup for the row under the cursor, with Copy Sequence and Copy Details actions.
     * This class doesn't go back to the RegionOfInterest model -- it relies on the values stored in the
     * TableModel, since that's all we need.  It could easily grab the Region, though, like in RegionTableModelListener
     */
    private class RegionTablePopupHandler extends MouseAdapter {
        // Maximum length for "copy sequence" action
        private static final int MAX_SEQUENCE_LENGTH = 1000000;

        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e)) {
                Point p = e.getPoint();
                //must convert row index from view to model, in case of sorting, filtering
                int row = regionTable.getRowSorter().convertRowIndexToModel(regionTable.rowAtPoint(p));
                int col = regionTable.columnAtPoint(p);

                if (row >= 0 && col >= 0) {
                    final String chr = (String) regionTableModel.getValueAt(row, TABLE_COLINDEX_CHR);
                    //displayed values are 1-based, so subract 1
                    final int start = (Integer) regionTableModel.getValueAt(row, TABLE_COLINDEX_START) - 1;
                    final int end = (Integer) regionTableModel.getValueAt(row, TABLE_COLINDEX_END) - 1;

                    final String desc = (String) regionTableModel.getValueAt(row, TABLE_COLINDEX_DESC);

                    JPopupMenu popupMenu = new IGVPopupMenu();
                    JMenuItem copySequenceItem = new JMenuItem("Copy Sequence");
                    copySequenceItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                            int length = end - start;
                            if (length > MAX_SEQUENCE_LENGTH) {
                                JOptionPane.showMessageDialog(RegionNavigatorDialog.this, "Region is to large to copy sequence data.");
                            } else {
                                IGV.copySequenceToClipboard(GenomeManager.getInstance().getCurrentGenome(),
                                        chr, start, end, Strand.NONE);
                            }
                        }
                    });
                    popupMenu.add(copySequenceItem);

                    JMenuItem copyDetailsItem = new JMenuItem("Copy Details");
                    copyDetailsItem.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                            String details = chr + ":" + start + "-" + end;
                            if (desc != null && !desc.isEmpty())
                                details = details + ", " + desc;
                            StringUtils.copyTextToClipboard(details);
                        }
                    });
                    popupMenu.add(copyDetailsItem);
                    popupMenu.show(regionTable, p.x, p.y);
                }
            }
        }

    }

}
