/*
 * RunManager.java
 *
 * Created on January 24, 2006, 3:44 PM
 *
 */
package usda.weru.wmrm;

import com.klg.jclass.cell.JCCellRenderer;
import usda.weru.weps.*;

import usda.weru.util.*;
import java.beans.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import com.klg.jclass.table.*;
import com.klg.jclass.table.data.JCVectorDataSource;
import com.klg.jclass.cell.editors.JCStringCellEditor;
import com.klg.jclass.util.JCListenerList;
import de.schlichtherle.truezip.file.TFile;
import java.awt.CardLayout;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.print.PageFormat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.HashMap;
import usda.weru.weps.reports.ReportManager;
import javax.swing.JCheckBoxMenuItem;
import org.jdom2.Element;
import usda.weru.util.table.Helper;
import usda.weru.util.table.WepsTableEnum;
import usda.weru.util.wepsFileChooser2.WepsFileChooser2;
import usda.weru.util.wepsFileChooser2.WepsFileTypes2;
import usda.weru.util.table.ColumnFilter;
import java.awt.print.Paper;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import usda.weru.util.table.WepsPrintPreview;
import usda.weru.weps.RunFileData;
import usda.weru.soil.IFC;
import usda.weru.wmrm.gui.EditHeaderWindow_n;
import usda.weru.util.ConversionCalculator;


/**
 *
 * @author joelevin -Updated by Ryan
 */
public class Wmrm extends usda.weru.wmrm.gui.Wmrm_n implements JCTableDataListener,
        PropertyChangeListener, TreeSelectionListener, TreeWillExpandListener,
        KeyListener, JCResizeCellListener {

    private static final long serialVersionUID = 1L;

    private final Application c_app = Application.WMRM;
    private final TFile configFile = new TFile("wmrm/wmrm_table.xml").getCanOrAbsFile();
    private ConvertedValueCellRenderer c_convertedValueCellRenderer;
    private String c_projectsPath;
    private DataModel c_model;
    private TableMeta c_meta;
    private String c_unitsSystem;
    // keeps track of which column is being sorted by
    // initialize to -1 because there is no sorting to start off
    private int c_sortIndex = -1;
    private int c_sortDirection = -999;
    private final Weps c_weps;
    private ArrayList<FilterSetWmrm> c_wmrmFilterSets;
    private ArrayList<String> c_wmrmnames;
    private final String c_filtersPath = "wmrm/wmrm_filters.xml";
    private final String[] textFields = new String[13];

    private String header_text;

    private String[] item_name;
    protected JCheckBoxMenuItem[] view_item_box;
    protected JMenuItem[] report_menu;

    private int[] c_rowSizeCache;
    
    private int currentReport;

    protected JCListenerList c_parseListeners = null;

    private static final String PARSE_X_PATTERN_STRING
            = "(?:\\$X\\{([.[^\\}]]*)\\.([.[^\\}]]*)\\})|(?:\\$X\\{([.[^\\}]]*)\\})";
    private final Pattern parseXPattern = Pattern.compile(PARSE_X_PATTERN_STRING);

    /**
     * Creates a new instance of RunManager in standalone mode.
     *
     * @param weps
     */
    public Wmrm(Weps weps) {
        super();
        if (weps == null) {
            weps = new usda.weru.weps.Weps();
        }
        c_weps = weps;

        M_runs.setEnabled(false);
        M_help1.setEnabled(false);
        LT_runs.setEnabled(false);
        JTR_groups.setEnabled(false);
        M_addBackRemovedRun.setEnabled(false);
        CardLayout cards = (CardLayout) loadingCardPanel.getLayout();
        cards.show(loadingCardPanel, "loading");

        setTitle("WMRM");

        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        LT_runs.setTrackCursor(false);
        LT_runs.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

        //empty data set while loading
        LT_runs.setDataSource(new JCVectorDataSource(0, 0, null, null, null));
    }
    
    
    /**
     * Exists due to old threading, probably obsolete, will look at when
     * refactoring internal_worker_replacement
     */
    public void init() {
        c_model = new DataModel();

        this.setSize(1170, 600);
        this.setLocationRelativeTo(c_weps);
        c_projectsPath = ConfigData.getDefault().getDataParsed(ConfigData.ProjDir);
        
        TFile runs = c_weps.getRunsLocation();
        if (runs != null) {
            c_model.addDirectoryToModel(runs);
        }

        if(c_model.getRunWrapper(0) == null) {
            
            int response;
            response = JOptionPane.showConfirmDialog(null
                    , "No available runs. Choose a run folder or create a WEPS run."
                    , "No WEPS Runs available."
                    , JOptionPane.YES_NO_OPTION);
            
            if(response == JOptionPane.YES_OPTION) {
                MI_addProject_actionPerformed(null);
            } else {
                this.dispose();
                return;
            }
        }
        
        TFile tempFile = new TFile(ConfigData.getDefault().getDataParsed(ConfigData.CurrentProj));
        DataModel.RunGroup tempGroup = c_model.lookupRunGroup(tempFile);
        if (tempGroup == null) {
            //Need to add the directory to the tree
            tempGroup = c_model.addDirectoryToModel(tempFile);
        }
        c_model.setCurrentProject(tempGroup);

        c_unitsSystem = ConfigData.getDefault().getData(ConfigData.Units);

        c_weps.rfd.addPropertyChangeListener(this);
        c_weps.rfd.fireAll(this);

        c_weps.addPropertyChangeListener(this);
        LT_runs.addResizeCellListener(this);

        LT_runs.addMouseMotionListener(new MouseMotionAdapter() {

            @Override
            public void mouseMoved(MouseEvent me) {

                JCCellPosition position = LT_runs.XYToCell(me.getX(), me.getY());
                int row = position.row;
                row = c_meta.getDataRow(row);
                row = row - c_meta.getNumHeaderRows();

                //adjust row for header
                int column = c_meta.getDataColumn(position.column);

                String tooltip = c_model.getTableDataToolTipText(row, column);
                if (tooltip != null) {
                    LT_runs.setToolTipText(tooltip);
                } else {
                    LT_runs.setToolTipText(null);
                }

            }
        });

        //return true;
        // Done method
        //finished loading everything            
        //WMRM listens for add events to reapply the sort order.
        c_model.addTableDataListener(this);

        c_meta = new TableMeta(configFile);

        //Link the datasource/meta to the table
        c_meta.linkTable(LT_runs);
        c_meta.setUnitsSystem(c_unitsSystem);

        LT_runs.setColumnHidden(-1, true);   //Hide row numbers
        LT_runs.setAutoscrolls(false);
        LT_runs.setAutoScroll(JCTableEnum.AUTO_SCROLL_NONE);
        LT_runs.setHorizSBPosition(JCTableEnum.POSITION_AT_SIDE);
        LT_runs.setSelectionPolicy(JCTableEnum.SELECT_RANGE);
        LT_runs.setPopupMenuEnabled(false);

        ActionInitiator initiator = new MouseActionInitiator(MouseEvent.BUTTON1, ActionInitiator.NO_MODIFIER_MASK);
        LT_runs.addAction(new TableAction(initiator, TableAction.COLUMN_SORT_ACTION));
        ActionInitiator ini = new MouseActionInitiator(MouseEvent.BUTTON1, InputEvent.SHIFT_MASK);
        LT_runs.addAction(new TableAction(ini, JCTableEnum.ROW_DRAG_ACTION));

        //Code to allow only one row to be selected.
        LT_runs.addSelectListener(new JCSelectAdapter() {

            @Override
            public void select(JCSelectEvent event) {
                if (event.getAction() == JCSelectEvent.EXTEND && event.getStartRow() != event.getEndRow()) {
                    event.setCancelled(true);
                }
            }
        });

        //Setup the tree
        JTR_groups.addTreeSelectionListener(this);

        JTR_groups.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
        JTR_groups.addTreeWillExpandListener(this);
        JTR_groups.addKeyListener(this);

        //Make converted values use the new cell renderer
        c_convertedValueCellRenderer = new ConvertedValueCellRenderer();
        c_convertedValueCellRenderer.setUnitsSystem(c_unitsSystem);
        LT_runs.setCellRenderer(ConvertedValue.class, c_convertedValueCellRenderer);
        LT_runs.setCellRenderer(String.class, WmrmWordWrapCellRenderer.class);

        //Test cell editor
        CellStyleModel style = LT_runs.getUniqueCellStyle(0, 0);
        style.setCellEditor(new JCStringCellEditor());
        LT_runs.setCellStyle(0, 0, style);
        

        ConfigData.getDefault().addPropertyChangeListener(this);
        ConfigData.getDefault().fireAll(this);

        LT_runs.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent me) {
                if (SwingUtilities.isLeftMouseButton(me) && me.getClickCount() == 1) {
                    mouseSingleClicked(me);
                } else if (SwingUtilities.isRightMouseButton(me)) {
                    int rowIndex = LT_runs.getCellAreaHandler().getRow(me.getX(), me.getY());
                    if (rowIndex > 3) { // doesn't do anything for header
                        MI_removeRuns_actionPerformed(null);
                    }
                } else if (SwingUtilities.isLeftMouseButton(me) && me.getClickCount() == 2) {
                    mouseDoubleClicked(me);
                }
            }
        });

        c_meta.setDataSource(c_model);
        JTR_groups.setModel(c_model);
        c_model.fireDataReset();

        M_runs.setEnabled(true);
        M_help1.setEnabled(true);
        LT_runs.setEnabled(true);
        JTR_groups.setEnabled(true);

        CardLayout cards = (CardLayout) loadingCardPanel.getLayout();
        cards.show(loadingCardPanel, "table");
        loadingCircle.setActive(false);

        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        LT_runs.setTrackCursor(true);
        LT_runs.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

        //End New Model Code
        setTitle(c_app.getName() + " - " + c_app.getDescription());
        // Assign head column name and add the names to the view menu 
        assignHeadColumnName();
        add_view_item();
        loadFilters();
        add_report_menu();
        parseHeader(c_meta.getLabelAt(0, 0));
        selectReport(currentReport);
        smartRowResize();
    }

    /**
     * Fills in the item_name array which is used later to set up the the GUI
     * list that allows the user to choose which columns show
     */
    private void assignHeadColumnName() {
        int i;
        item_name = new String[c_meta.getNumColumns()];
        
        for (i = 0; i < 9; i++) {
            item_name[i] = c_meta.getLabelAt(1, i);
        }
        for (i = 9; i < c_meta.getNumColumns(); i++) {
            item_name[i] = c_meta.getLabelAt(2, i);
        }
    }

    /**
     * Loads the filters used to set up the different reports
     */
    private void loadFilters() {
        c_wmrmFilterSets = new ArrayList<>();
        c_wmrmnames = new ArrayList<>();
        TFile file = new TFile(c_filtersPath);
        Element node = Helper.getRootNode(file);
        List<Element> filtersetList = node.getChildren(WepsTableEnum.XML_filterset);
        FilterSetWmrm defaultSet = null;
        int i = 0;
        try {
            for (Element filterSetNode : filtersetList) {
                FilterSetWmrm filterSet = new FilterSetWmrm();
                filterSet.fromXml(filterSetNode);

                String name = filterSet.getName();
                c_wmrmnames.add(name);
                c_wmrmFilterSets.add(filterSet);
                if (defaultSet == null) {
                    defaultSet = filterSet;
                }
                if (filterSet.isReportSelected()) {
                    currentReport = i;
                }
                i++;
            }
        } catch (ConcurrentModificationException ex) {
            System.out.println("************WMRM.loadFilters()**************");
        }
    }

    /**
     * Adds the GUI menu logic to allow user to select different reports
     */
    private void add_report_menu() {

        report_menu = new JMenuItem[c_wmrmnames.size()];
        for (int i = 0; i < c_wmrmnames.size(); i++) {
            final int t = i;

            report_menu[i] = new JMenuItem();
            report_menu[i].setText(c_wmrmnames.get(i));

            if (c_wmrmnames.get(i).equals("Default Report")) {
                selectReport(i);
            }
            report_menu[i].addActionListener((java.awt.event.ActionEvent evt) -> {
                selectReport(t);
                smartRowResize();
            });
            m_reports.add(report_menu[i]);
        }

    }

    /**
     * Takes report 'i' and selects to correct columns needed to display
     *
     * @param i
     */
    private void selectReport(int i) {
        // Grab the column id's from the filter set
        ColumnFilter[] currentFilters = c_wmrmFilterSets.get(i).columnFilters();
        setTitle(c_app.getName() + " - " + c_app.getDescription() + ": " + c_wmrmnames.get(i));

        currentReport = i;

        // Loop to compare column id's, probably a better way to do this
        for (int j = 0; j < c_meta.getNumColumns(); j++) {
            ColumnFilter colId = c_meta.getColumnMeta(j).getId();
            for (ColumnFilter currentFilter : currentFilters) {
                if (currentFilter.equals(colId)) {
                    LT_runs.setColumnHidden(j, false);
                    // Update the view items to reflect current columns
                    view_item_box[j].setSelected(true);
                    break;
                } else {
                    LT_runs.setColumnHidden(j, true);
                    view_item_box[j].setSelected(false);
                }
            }
        }
        parseHeader(c_meta.getLabelAt(0, 0));
    }

    /**
     * Adds each column to a GUI check box item so that the user can select
     * which individual columns they wish to display
     */
    private void add_view_item() {

        view_item_box = new JCheckBoxMenuItem[item_name.length];
        int i;
        for (i = 0; i < item_name.length; i++) {

            final int t = i;
            view_item_box[i] = new JCheckBoxMenuItem();
            view_item_box[i].setText(item_name[i]);

            view_item_box[i].addActionListener((java.awt.event.ActionEvent evt) -> {
                if (view_item_box[t].isSelected()) {
                    view_item_box[t].setSelected(true);
                    LT_runs.setColumnHidden(t, false);
                } else {
                    view_item_box[t].setSelected(false);
                    LT_runs.setColumnHidden(t, true);
                }
                smartRowResize();
            });
            m_view.add(view_item_box[i]);
        }
    }
    
    // This is what is used to change the header via the first row
    // if you would like to adjust when this updates look for calls to this
    private void parseHeader(String text) {
        // Need to change this if stmt
        if (text == null) {
        }
        // This section is meant to store the version of the header with
        // variable names such that it can be changed again later in the code
        if (header_text == null) {
            header_text = text;
        }
        text = header_text;

        String value = text;
        Matcher matcher = parseXPattern.matcher(text);
        int i = 0;
        while (matcher.find()) {
            try {
                String expression = matcher.group();
                String loneField = matcher.group(3);
                String field;
                if (loneField != null && loneField.length() > 0) {
                    field = loneField;
                } else {
                    field = matcher.group(2);
                }
                
                int dataIndex = c_meta.getDataRow(4) - c_meta.getNumHeaderRows();
                RunWrapper topRun = c_model.getRunWrapper(dataIndex);
                
                if(topRun == null) {
                    return; //Prevents bug report if there are no runs for some reason - EL
                }
                
                topRun.readRunFile(topRun.getDirectory().getAbsolutePath());

                String parsedText = parse(field, topRun);
                if (parsedText != null) {
                    textFields[i] = parsedText;
                    text = text.replace(expression, parsedText);
                }
                i++;
                
                value = text;
            } catch (ConversionCalculator.ConversionNotFoundException
                    | ConversionCalculator.UnitNotFoundException ex) {
                System.err.println("problems converting units in WMRM.");
            }
        }
        c_meta.setLabelAt(value, 0, 0);
        
    }

    public String parse(String field, RunWrapper topRun) 
            throws ConversionCalculator.ConversionNotFoundException, ConversionCalculator.UnitNotFoundException {
        DecimalFormat df = new DecimalFormat("#.#");
        String unit;
        
        switch (field) {
            case "title":
                return c_wmrmnames.get(currentReport);
            case "soil":
                return topRun.getWrapperValue(RunWrapper.DataTag.SoilName).toString();
            case "tvalue":
                IFC soilFile = new IFC();
                soilFile.readIfc(topRun.getSoilFile());
                int tValue = soilFile.soilLossTolerance;
                if(this.isUSUnits()) {
                    return String.valueOf(tValue) + " t/ac/yr";
                } else {
                    return String.valueOf(tValue) + " kg/m^2/yr";
                }
            case "latlon":
                String[] latLon = topRun.getData("RFD-LatLong").split(";");
                String newLatLon;
                if(latLon.length == 2) {
                    if (latLon[0].contains("+")) {
                        newLatLon = latLon[0].replace("+", "") + "° N";
                    } else {
                        newLatLon = latLon[0].replace("-", "") + "° S";
                    }
                    
                    if (latLon[1].contains("+")) {
                        newLatLon = newLatLon + " | " + latLon[1].replace("+", "") + "° E";
                    } else {
                        newLatLon = newLatLon + " | " + latLon[1].replace("-", "") + "° W";
                    }
                    
                } else {
                    newLatLon = Arrays.toString(latLon);
                }
                
                return newLatLon;
            case "client":
                return topRun.getWrapperValue(RunWrapper.DataTag.ClientName).toString();
            case "farm":
                return topRun.getWrapperValue(RunWrapper.DataTag.FarmNo).toString();
            case "tract":
                return topRun.getWrapperValue(RunWrapper.DataTag.TractNo).toString();
            case "field":
                return topRun.getWrapperValue(RunWrapper.DataTag.FieldNo).toString();
            case "xLength":
                Double xLength = (Double) topRun.getWrapperValue(RunWrapper.DataTag.XLength);
                unit = "m";
                
                if (this.isUSUnits()) {
                    xLength = ConversionCalculator.convert(xLength, "m", "ft");
                    unit = "ft";
                }
                return df.format(xLength) + " " + unit;
            case "yLength":
                Double yLength = (Double) topRun.getWrapperValue(RunWrapper.DataTag.YLength);
                unit = "m";
                
                if (this.isUSUnits()) {
                    yLength = ConversionCalculator.convert(yLength, "m", "ft");
                    unit = "ft";
                }
                return df.format(yLength) + " " + unit;
            case "field area":
                Double area = (Double) topRun.getWrapperValue(RunWrapper.DataTag.FieldSize);
                unit = "ha";
                
                if (this.isUSUnits()) {
                    Double x = ConversionCalculator.convert((Double) topRun.getWrapperValue(RunWrapper.DataTag.XLength), "m", "ft");
                    Double y = ConversionCalculator.convert((Double) topRun.getWrapperValue(RunWrapper.DataTag.YLength), "m", "ft");
                    area = x * y;
                    area = area / 43560;
                    unit = "ac";
                } else {
                    area = area / 10000;
                }
                
                return df.format(area) + " " + unit;
            case "orientation":
                return topRun.getWrapperValue(RunWrapper.DataTag.Orientation).toString() + "°";
            case "shape":
                return topRun.getWrapperValue(RunWrapper.DataTag.Shape).toString();
            default:
                return "";
        }
    }
    
    private boolean isUSUnits() {
        return c_weps.cd.getData("CD-measurement").equals("US");
    }

    private void editHeader(HashMap<String, String> updatedInfo) {
        String text = header_text;
        Matcher matcher = parseXPattern.matcher(text);

        String value = text;

        while (matcher.find()) {
            String expression = matcher.group();
            String loneField = matcher.group(3);
            String field;
            if (loneField != null && loneField.length() > 0) {
                field = loneField;
            } else {
                field = matcher.group(2);
            }

            String parsedText = updatedInfo.get(field);
            if (parsedText != null) {
                text = text.replace(expression, parsedText);
            }

            value = text;
        }
        c_meta.setLabelAt(value, 0, 0);
        c_model.fireDataReset();
    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        switch (event.getPropertyName()) {
            case ConfigData.ProjDir:
                c_projectsPath = ((String) event.getNewValue());
                c_projectsPath = Util.parse(c_projectsPath);
                break;
            case ConfigData.Units:
                c_unitsSystem = (String) event.getNewValue();
                c_convertedValueCellRenderer.setUnitsSystem(c_unitsSystem);
                c_meta.setUnitsSystem(c_unitsSystem);
                c_model.fireDataReset();
                break;
            case ConfigData.CurrentProj:
                TFile tempFile = new TFile(Util.parse(event.getNewValue().toString()));
                DataModel.RunGroup tempGroup = c_model.lookupRunGroup(tempFile);
                if (tempGroup == null) {
                    //Need to add the directory to the tree
                    tempGroup = c_model.addDirectoryToModel(tempFile);
                }
                c_model.setCurrentProject(tempGroup);
                break;
            case Weps.WEPS_RUNCREATED:
                c_model.addRunFileToModel(null, new TFile(event.getNewValue().toString()));
                break;
            case Weps.WEPS_RUNRESTORED:
                c_model.addRunFileToModel(null, new TFile(event.getNewValue().toString()));
                break;
            case ConfigData.DefaultRunsLocation:
                break;
            case RunFileData.RunsLocation:
                c_model.addDirectoryToModel(new TFile(event.getNewValue().toString()));
                break;
        }
    }

    private void mouseDoubleClicked(MouseEvent event) {
        int rowIndex = LT_runs.getCellAreaHandler().getRow(event.getX(), event.getY());
        //Double clicked a header cell.

        if (rowIndex < c_meta.getNumHeaderRows()) {

            return;
        }
        rowIndex = c_meta.getDataRow(rowIndex) - c_meta.getNumHeaderRows();

        int columnIndex = LT_runs.getCellAreaHandler().getColumn(event.getX(), event.getY());
        TableMeta.ColumnMeta column = c_meta.getColumnMeta(columnIndex);

        RunWrapper run = c_model.getRunWrapper(rowIndex);
        takeActionOnRun(run, column.getAction());
        //  LT_runs.setColumnHidden(columnIndex,true);
    }

    private void mouseSingleClicked(MouseEvent event) {
        int rowIndex = LT_runs.getCellAreaHandler().getRow(event.getX(), event.getY());
        //Clicked a data cell.
        if (rowIndex < 0 || rowIndex >= c_meta.getNumHeaderRows()) {
            return;
        }
        int columnIndex = LT_runs.getCellAreaHandler().getColumn(event.getX(), event.getY());
        //Was a new column clicked?

        if (columnIndex != c_sortIndex) {
            //Set to default direction
            c_sortIndex = columnIndex;
            c_sortDirection = Sort.ASCENDING;
        } else {
            //Change sort direction
            switch (c_sortDirection) {
                case Sort.ASCENDING:
                    c_sortDirection = Sort.DESCENDING;
                    break;
                case Sort.DESCENDING:
                    c_sortIndex = -1;
                    c_sortDirection = -999;
            }
        }

        applySort();
        smartRowResize();
        parseHeader(c_meta.getLabelAt(0, 0));
    }

    private void applySort() {
        //prevent sorting if no runs in model
        if (c_model.getNumRows() == 0) {
            return;
        }

        if (c_sortIndex > -1) {
            c_meta.sortByColumn(c_sortIndex, c_sortDirection);
        } else {
            c_meta.sortByColumn(c_sortIndex, c_sortDirection);
            c_meta.resetRowMap();
        }
    }

    public void takeActionOnRun(RunWrapper run, String action) {
        if (action.equalsIgnoreCase("open_runsummary")) {
            ReportManager.getDefault().displayReport(run.getDirectory(), ReportManager.REPORT_RUN);
        } else if (action.equalsIgnoreCase("open_cropsummary")) {
            ReportManager.getDefault().displayReport(run.getDirectory(), ReportManager.REPORT_CROPSUM);
        } else if (action.equalsIgnoreCase("open_detail")) {
            ReportManager.getDefault().displayReport(run.getDirectory(), ReportManager.REPORT_DETAIL);
        } else if (action.equalsIgnoreCase("open_managementsummary")) {
            ReportManager.getDefault().displayReport(run.getDirectory(), ReportManager.REPORT_MANAGEMENT);
        } else if (action.equalsIgnoreCase("open_stir")) {
            ReportManager.getDefault().displayReport(run.getDirectory(), ReportManager.REPORT_STIR);
        } else if (action.equalsIgnoreCase("open_soil")) {
            if (("none".equals(run.getData(RunFileData.SoilFile))) || ("".equals(run.getData(RunFileData.SoilFile)))) {

                ExceptionEventQueue.invokeLater(() -> {
                    JOptionPane pane = new JOptionPane("Soil file is missing in:\n"
                            + run.getDirectory().getAbsolutePath(), JOptionPane.WARNING_MESSAGE) {
                                private static final long serialVersionUID = 1L;
                                
                                @Override
                                public int getMaxCharactersPerLineCount() {
                                    return 80;
                                }
                                
                            };
                    
                    JDialog dialog = pane.createDialog("Loading Soil File");
                    dialog.setVisible(true);
                });

            } else {
                ReportManager.getDefault().displayReport(run.getDirectory(), ReportManager.REPORT_SOIL);
            }
        } else if ("open_runslocation".equalsIgnoreCase(action)) {
            try {
                Desktop.getDesktop().open(run.getDirectory().getParentFile());
            } catch (IOException de) {

            }
        }
    }

    public DataModel getDataModel() {
        return c_model;
    }

    /**
     * Create the GUI and show it. For thread safety, this method should be
     * invoked from the event-dispatching thread.
     */
    private static void createAndShowGUI() {
        Wmrm multipleRuns = new Wmrm(null);
        multipleRuns.setVisible(true);
    }

    /**
     * Used to resize the height of cells, as of right now must run at table
     * creation otherwise all cell heights are too short
     */
    private void smartRowResize() {
        checkRowSizeCache();

        //Find the target size for each row
        for (int rowIndex = 0; rowIndex < LT_runs.getNumRows(); rowIndex++) {

            int maxTextHeight = 0;
            for (int columnIndex = 0; columnIndex < LT_runs.getNumColumns(); columnIndex++) {
                Font cellFont = LT_runs.getCellStyle(rowIndex, columnIndex).getFont();
                FontMetrics fm = LT_runs.getGraphics().getFontMetrics(cellFont);
                int textHeight = fm.getHeight() + 6;
                if (LT_runs.getDataView().getTableDataItem(rowIndex, columnIndex) != null) {
                    JCCellRenderer wrap = c_meta.getCellRenderer(rowIndex, columnIndex);
                    if (wrap instanceof WmrmWordWrapCellRenderer) {
                        JCTableCellInfo info = new JCTableCellInfo();
                        info.initialize(LT_runs,
                                LT_runs.getDataView().getTableDataItem(rowIndex, columnIndex).toString(),
                                rowIndex,
                                columnIndex);
                        Dimension d = ((WmrmWordWrapCellRenderer) wrap).getPreferredSize(LT_runs.getGraphics(),
                                info,
                                LT_runs.getDataView().getTableDataItem(rowIndex, columnIndex).toString());
                        int newHeight = d.height + 6;
                        if (newHeight > textHeight + 6) {
                            textHeight = newHeight;
                        }
                    }
                }
                if (textHeight > maxTextHeight) {
                    maxTextHeight = textHeight;
                }
            }
            LT_runs.setPixelHeight(rowIndex, maxTextHeight);
        }

    }

    /**
     * Used to see the current state of the rowSizeCache
     */
    private void checkRowSizeCache() {
        if (c_rowSizeCache == null) {
            buildRowSizeCache();
            resetRowSizeCache();
        }
        if (LT_runs.getNumRows() != c_rowSizeCache.length) {
            buildRowSizeCache();
            resetRowSizeCache();
        }
    }

    /**
     * Initializes the rowSizeCache
     */
    private void buildRowSizeCache() {
        c_rowSizeCache = new int[LT_runs.getNumRows()];
    }

    /**
     * resets the rowSizeCache by resetting all heights
     */
    private void resetRowSizeCache() {
        for (int i = 0; i < LT_runs.getNumRows(); i++) {
            c_rowSizeCache[i] = LT_runs.getPixelHeight(i);
        }

    }

    static public void main(String args[]) {
        javax.swing.SwingUtilities.invokeLater(() -> {
            String mainConfig = "cfg/weps.cfg";
            String userConfig = null;
            //if the user config is not set use the name of the main config file
            if (userConfig == null) {
                userConfig = About.getUserWeps().getAbsolutePath() + "/" + new TFile(mainConfig).getName();
            }
            ConfigData.getDefault().load(new TFile(mainConfig), new TFile(userConfig));
            
            createAndShowGUI();
        });
    }

    private int getSelectedRow() {
        if (c_model.getNumRows() > 0 && LT_runs.getSelectedCells() != null && LT_runs.getSelectedCells().size() > 0) {
            JCCellRange range = (JCCellRange) LT_runs.getSelectedCells().toArray()[0];
            return range.start_row;
        } else {
            return -1;
        }
    }
    //Menu Events

    @Override
    protected void MI_editHeader_actionPerformed(java.awt.event.ActionEvent evt) {

        // Create anonymous subclass to allow for data to be passed
        EditHeaderWindow_n editHeader = new EditHeaderWindow_nImpl(this, false, textFields);
        editHeader.configureWindow();
        editHeader.setLocationRelativeTo(null);
        editHeader.setVisible(true);
    }

    @Override
    protected void MI_about_ActionPerformed(java.awt.event.ActionEvent evt) {
        new usda.weru.util.AboutDialog(this, Application.WMRM).setVisible(true);
    }

    @Override
    protected void MI_close_ActionPerformed(java.awt.event.ActionEvent evt) {
        this.dispose();
    }

    @Override
    protected void MI_addProject_actionPerformed(java.awt.event.ActionEvent evt) {
        WepsFileChooser2 wfc = new WepsFileChooser2(WepsFileTypes2.Dir, c_projectsPath, WepsFileChooser2.Action.Open);

        if (c_projectsPath != null) {
            wfc.setCurrentDirectory(c_projectsPath);
        }
        wfc.homeToolTip(wfc);

        int returnValue = wfc.showDialog(this);
        if (returnValue == WepsFileChooser2.APPROVE_OPTION) {
            TFile directory = new TFile(wfc.getSelectedFile());
            c_model.addDirectoryToModel(directory);
        }

    }

    @Override
    protected void MI_refreshTable_actionPerformed(java.awt.event.ActionEvent evt) {
        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        //c_model.checkRunGroup();
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        //parseHeader(c_meta.getLabelAt(0, 0));
    }

    @Override
    protected void MI_addRun_actionPerformed(java.awt.event.ActionEvent evt) {
        WepsFileChooser2 wfc = new WepsFileChooser2(WepsFileTypes2.Run, c_projectsPath, WepsFileChooser2.Action.Open);
        if (c_projectsPath != null) {
            wfc.setCurrentDirectory(new TFile(c_projectsPath).getAbsolutePath());
        }
        wfc.homeToolTip(wfc);

        int returnValue = wfc.showDialog(this);
        if (returnValue == WepsFileChooser2.APPROVE_OPTION) {
            TFile file = new TFile(wfc.getSelectedFile());
            c_model.addRunFileToModel(null, file);
        }
    }

    @Override
    protected void MI_exportRun_actionPerformed(java.awt.event.ActionEvent evt) {
        int rowIndex = getSelectedRow();
        if (rowIndex < c_meta.getNumHeaderRows()) {
            JOptionPane.showMessageDialog(this, "No Run Selected",
                    Application.WMRM.getName(), JOptionPane.INFORMATION_MESSAGE);
            return;
        }
        rowIndex = c_meta.getDataRow(rowIndex) - c_meta.getNumHeaderRows();
        RunWrapper run = c_model.getRunWrapper(rowIndex);
        RunExporter exporter = new RunExporter(run.getDirectory(), null, false, c_weps);

    }

    @Override
    protected void MI_deleteRuns_actionPerformed(java.awt.event.ActionEvent evt) {
        try {
            setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            int rowIndex = getSelectedRow();
            if (rowIndex < c_meta.getNumHeaderRows()) {
                JOptionPane.showMessageDialog(this, "No Run Selected",
                        Application.WMRM.getName(), JOptionPane.INFORMATION_MESSAGE);
                return;
            }
            // Make sure that user understands that the run is fully deleted
            if (JOptionPane.showConfirmDialog(this, "This will delete the run from your computer are you "
                    + "sure you wish to continue?", Application.WMRM.getName(),
                    JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {

            } else {
                return;
            }
            rowIndex = c_meta.getDataRow(rowIndex) - c_meta.getNumHeaderRows();
            if (rowIndex > c_model.getNumRows()) {
                return;
            }
            RunWrapper run = c_model.getRunWrapper(rowIndex);
            c_weps.deleteRun(run.getDirectory());
            c_model.fireDataReset();
        } finally {
            setCursor(Cursor.getDefaultCursor());
        }

    }

    @Override
    protected void MI_removeRuns_actionPerformed(java.awt.event.ActionEvent evt) {
        try {
            setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            int rowIndex = getSelectedRow();
            if (rowIndex < c_meta.getNumHeaderRows()) {
                JOptionPane.showMessageDialog(this, "No Run Selected",
                        Application.WMRM.getName(), JOptionPane.INFORMATION_MESSAGE);
                return;
            }

            if (rowIndex < LT_runs.getNumRows()) {
                int dataIndex = c_meta.getDataRow(rowIndex) - c_meta.getNumHeaderRows();
                
                RunWrapper saveRun = c_model.getRunWrapper(dataIndex);
   
                if (c_model.removeRunWrapper(dataIndex)) {
                    this.addRemovedRunToMenu(saveRun);
                    c_model.fireDataReset();
                    parseHeader(c_meta.getLabelAt(0, 0));
                }
            }

        } finally {
            setCursor(Cursor.getDefaultCursor());
        }
    }
    
    //adds a removed run to a menu and allows a user to select it to add it back
    private void addRemovedRunToMenu(RunWrapper run) {
        JMenuItem removedRun = new JMenuItem();
        removedRun.setText((String) run.getWrapperValue(RunWrapper.DataTag.RunName));
        removedRun.addActionListener((ActionEvent evt) -> {
            c_model.addRunWrapper(run);
            c_model.fireDataReset();
            this.parseHeader(c_meta.getLabelAt(0, 0));
            M_addBackRemovedRun.remove(removedRun);
            
            if (M_addBackRemovedRun.getItemCount() == 0) {
                M_addBackRemovedRun.setEnabled(false);
            }
            
        });
        M_addBackRemovedRun.add(removedRun);
        M_addBackRemovedRun.setEnabled(true);
    }

    @Override
    protected void MI_reloadRun_ActionPerformed(java.awt.event.ActionEvent event) {
        int rowIndex = getSelectedRow();
        if (rowIndex < c_meta.getNumHeaderRows()) {
            JOptionPane.showMessageDialog(this, "No Run Selected",
                    Application.WMRM.getName(), JOptionPane.INFORMATION_MESSAGE);
            return;
        }
        rowIndex = c_meta.getDataRow(rowIndex) - c_meta.getNumHeaderRows();
        RunWrapper run = c_model.getRunWrapper(rowIndex);
        c_weps.restoreRun(run.getDirectory());
    }

    @Override
    protected void MI_printAction_performed(java.awt.event.ActionEvent evt) {
        final WmrmPrintTable table = new WmrmPrintTable(LT_runs, .75);
        PageFormat format = new PageFormat();
        format.setOrientation(PageFormat.LANDSCAPE);
        Paper paper = new Paper();
        paper.setSize(612, 792);    //8.5 x 11
        paper.setImageableArea(36, 36, 540, 720);
        format.setPaper(paper);
        table.setPageFormat(format);
        WepsPrintPreview pf = new WepsPrintPreview("Print Preview", table);
        pf.showPage(0);
    }

    @Override
    protected void MI_createWMRM_actionPerformed(java.awt.event.ActionEvent evt) {
        Wmrm wmrm = new Wmrm(c_weps);
        wmrm.setVisible(true);
        wmrm.init();
    }

    //Tree Events
    @Override
    public void valueChanged(TreeSelectionEvent event) {
        try {
            //c_model.selectRunGroup((DataModel.RunGroup) event.getNewLeadSelectionPath().getLastPathComponent());
            DataModel.RunGroup[] groups = new DataModel.RunGroup[JTR_groups.getSelectionPaths().length];
            int i = 0;
            for (TreePath path : JTR_groups.getSelectionPaths()) {
                groups[i] = (DataModel.RunGroup) path.getLastPathComponent();
                i++;
            }
            c_model.selectRunGroups(groups);
            parseHeader(c_meta.getLabelAt(0, 0));
        } catch (NullPointerException npe) {
        }
    }

    @Override
    public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
        if (event.getPath().getLastPathComponent() == c_model.getRoot()) {
            throw new ExpandVetoException(event);
        }
    }

    @Override
    public void treeWillExpand(TreeExpansionEvent event) {
    }

    //Key Events
    @Override
    public void keyPressed(KeyEvent event) {
        if (event.getSource() == JTR_groups) {
            //Tree keyboard events
            if (event.getKeyCode() == KeyEvent.VK_DELETE) {
                //Pressed the delete key
                //We don't delete the root or it's direct children.
                Object selectedObject = JTR_groups.getSelectionPath().getLastPathComponent();
                if (c_model.getRoot() == selectedObject) {
                    return;
                }
                int selectedIndex = c_model.getIndexOfChild(c_model.getRoot(), selectedObject);
                if (selectedIndex >= 0) {
                    return;                //make sure the user wants to delete the node.
                }
                int result = JOptionPane.showConfirmDialog(this, "Are you sure you want to remove "
                        + selectedObject.toString() + "?\nThis will not remove the physical file.  "
                        + "Only the display will be changed.", Application.WMRM.getName(),
                        JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
                if (result == JOptionPane.YES_OPTION) {
                    JTR_groups.setSelectionPath(c_model.getPathToNode(((DataModel.RunGroup) selectedObject).getParent()));
                    c_model.removeRunGroupFromModel((DataModel.RunGroup) selectedObject);
                }

            }
        }
    }

    @Override
    public void keyReleased(KeyEvent event) {
    }

    @Override
    public void keyTyped(KeyEvent event) {
    }

    //Window events
    @Override
    protected void form_windowClosed(java.awt.event.WindowEvent evt) {
    }

    //JCTableDataListener Events
    @Override
    public void dataChanged(JCTableDataEvent e) {
        int command = e.getCommand();
        if (command == JCTableDataEvent.ADD_ROW || command == JCTableDataEvent.RESET
                || command == JCTableDataEvent.NUM_ROWS || command == JCTableDataEvent.REMOVE_ROW) {
            applySort();
        }
    }

    //Component events
    @Override
    protected void LT_runs_componentResized(java.awt.event.ComponentEvent evt) {
        smartRowResize();
    }

    @Override
    public void beforeResizeCell(JCResizeCellEvent event) {
    }

    @Override
    public void resizeCell(JCResizeCellEvent event) {
    }

    @Override
    public void afterResizeCell(JCResizeCellEvent event) {
        checkRowSizeCache();
        int rowIndex = event.getRow();
        if (rowIndex == JCTableEnum.NOVALUE) {
            return;
        }
        c_rowSizeCache[rowIndex] = event.getNewRowHeight();

    }

    private class EditHeaderWindow_nImpl extends EditHeaderWindow_n {

        private static final long serialVersionUID = 1L;

        //setLocationRelativeTo(null) centers the frame in the center of the screen - EL
        public EditHeaderWindow_nImpl(Frame frame, boolean bln, String[] strings) {
            super(frame, bln, strings);
        }

        @Override
        public void editHeaderAction(HashMap<String, String> updatedInfo) {
            editHeader(updatedInfo);
        }
    }
}
