/*
 * DetailReport.java
 *
 * Created on June 27, 2006, 12:10 PM
 *
 */
package usda.weru.weps.reports;

import com.klg.jclass.cell.JCCellInfo;
import com.klg.jclass.cell.renderers.JCWordWrapCellRenderer;
import com.klg.jclass.table.DataViewModel;
import com.klg.jclass.table.JCCellBorder;
import com.klg.jclass.table.JCCellPosition;
import com.klg.jclass.table.JCCellRange;
import com.klg.jclass.table.JCPrintEvent;
import com.klg.jclass.table.JCPrintListener;
import com.klg.jclass.table.JCTableCellInfo;
import com.klg.jclass.table.JCTableEnum;
import com.klg.jclass.table.TableDataModel;
import com.klg.jclass.table.data.AbstractDataSource;
import java.awt.EventQueue;
import java.awt.Component;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileInputStream;

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Vector;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.help.CSH;
import javax.help.HelpBroker;
import javax.help.HelpSet;
import javax.swing.ButtonGroup;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.ProgressMonitor;
import org.apache.log4j.Logger;
import org.jdom2.Element;
import org.jfree.data.category.DefaultCategoryDataset;
import usda.weru.util.About;
import usda.weru.util.ConfigData;
import usda.weru.util.ConversionUnit;
import usda.weru.util.Help;
import usda.weru.util.ReportContext;
import usda.weru.util.Util;
import usda.weru.util.table.FilterSet;
import usda.weru.util.table.Helper;
import usda.weru.util.table.ParseEvent;
import usda.weru.util.table.ParseListener;
import usda.weru.util.table.WepsTable;
import usda.weru.util.table.Column;
import usda.weru.util.table.WepsTableEnum;
import usda.weru.util.table.WepsTableMeta;
import usda.weru.util.table.ColumnFilter;
import usda.weru.util.table.RowFilter;
import usda.weru.util.table.filters.RowKeyFilter;
import usda.weru.weps.RunFileData;
import usda.weru.weps.reports.gui.DetailReport_n;

/**
 *
 * @author Joseph Levin
 */
public class DetailReport extends DetailReport_n implements PropertyChangeListener, ParseListener, JCPrintListener {

    private static final long serialVersionUID = 1L;
    protected static final String c_metaPath = "tables/detail.xml";
    protected String c_filtersPath = "tables/detail_filters.xml";
    protected TFile c_runFile;
    protected List<FilterSet> c_qplotFilterSets;
    protected ProgressMonitor c_progress;
    protected boolean c_loadingFilters;
    protected ArrayList<String> c_qplotnames;
    protected RunFileData c_run;    //Run Info
    protected String c_client = "";
    protected String c_farm = "";
    protected String c_tract = "";
    protected String c_field = "";
    protected String c_management = "";
    protected String c_soil = "";
    
     /**
     * For getting the current plot name without having to construct anything
     */
    protected int c_currentPlotIndex; 
    
    /**
     * Saves the initial dataview
     */
    protected DataViewModel c_fullDataViewModel;
    
     /**
     * Flag to select from hidden quickplot list
     */
    protected boolean c_fromHiddenQuickplotList_flag = false;
    
    protected String c_angle = "";    //Config info

    /**
     *
     */
    protected String c_rotationDateFormat = "";
    Hashtable<String, String> c_tooltips;

    private final Set<String> quickPlotColumns = new HashSet<String>();

    /**
     * Creates a new instance of DetailReport
     * @param runFile
     */
    public DetailReport(TFile runFile) {
        c_runFile = runFile;
        g_wepsTable.addParseListener(this);
        setTitle("Tabular Detail Report - " + c_runFile.getName());
        setIconImage(About.getWeruIconImage());
        initCSH();

        //setup quickplot dropdown button
        for (QuickPlotCustomizer.Type t : QuickPlotCustomizer.Type.values()) {
            quickPlotTypeMenu.add(createQuickPlotTypeMenuItem(t));
        }

        quickPlotRestore.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent ae) {
                setQuickPlot(getSelectedFilterSet());
            }
        });

        quickPlotSelect.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent ae) {
                setAllQuickPlot(true);
            }
        });

        quickPlotClear.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent ae) {
                setAllQuickPlot(false);
            }
        });
        
        // create dynamic list of quickplot options with event listeners
        ArrayList<String> quickplotMenuItems = getDynamicListNames();
        
        for (String qplotname : quickplotMenuItems) {
            
            javax.swing.JMenuItem temp = new javax.swing.JMenuItem();
            temp.setText(qplotname);
            temp.addActionListener(
                new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        dynamic_quickplot_actionPreformed(event);
                    }
            });
            temp.setName(qplotname); // need to set name to find which action event is used
            defaultPlotMenu.add(temp);
        }

        g_quickplot.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent ae) {
                chartActionPerformed();
            }
        });
    }
    
    private void initCSH() {
        HelpSet hs = Help.getHelpSet();
        HelpBroker hb = hs.createHelpBroker();
        TFile file = new TFile("cfg", "reportstooltips.cfg");
        c_tooltips = Util.loadToolTipsTable(file);
        Util.loadToolTips((Container) this, file);
        if (hb != null) {
            hb.enableHelpOnButton(g_helpButton, "outDetScreen_html", hs);
            ActionListener aboutHelp = new CSH.DisplayHelpFromSource(hb);
            g_helpButton.addActionListener(aboutHelp);
            g_cshButton.addActionListener(new CSH.DisplayHelpAfterTracking(hs, "javax.help.Popup", null));
        } else {
            g_cshButton.setEnabled(false);
        }

        CSH.setHelpIDString(JTB_main, "outDebButtons_html");

        TableCSHManager cm = new TableCSHManager(g_wepsTable);
        CSH.addManager(cm);

        g_wepsTable.addMouseMotionListener(new MouseMotionListener() {

            @Override
            public void mouseDragged(MouseEvent e) {
            }

            @Override
            public void mouseMoved(MouseEvent me) {
                try {
                    int columnIndex = g_wepsTable.XYToCell(me.getX(), me.getY()).column;
                    Column column = g_wepsTable.getMeta().getColumn(columnIndex);

                    if (column != null) {
                        String paramName = column.getId();
                        String tooltip = c_tooltips.get("RepDetailPanel:" + paramName);
                        if (tooltip != null) {
                            g_wepsTable.setToolTipText("<html>" + tooltip + "</html>");
                            return;
                        }
                    }
                    g_wepsTable.setToolTipText("");
                } catch (Exception e) {
                }
            }
        });
    }

    private QuickPlotCustomizer.Type quickPlotType = QuickPlotCustomizer.Type.Line;
    private final ButtonGroup typeGroup = new ButtonGroup();
    
    // Indexes through the components in the drop down menu, comparing the name set
    // to the events action command. When matched, returns that index
    private void dynamic_quickplot_actionPreformed(java.awt.event.ActionEvent event) {
        int index = 0;
        for (int i = 0; i < defaultPlotMenu.getComponentCount(); i++) {
            if (event.getActionCommand().equals(defaultPlotMenu.getComponent(i).getName())) {
                index = i;
                break;
            }
        }
        justInitQuickplotFrominstance(index); 
    }
    
    // just gets the names off the nodes to build a list
    private ArrayList<String> getDynamicListNames() {
        TFile filters = new TFile(ConfigData.getDefault().getData(ConfigData.DetailTableFilterFile));
        
        Element node = Helper.getRootNode(filters);
        List<Element> qnames = node.getChildren("filterset");
        
        ArrayList<String> dropDownValues = new ArrayList<String>();
        
        try {
            for (Element filterSetNode : qnames) {
                FilterSet filterSet = new FilterSet();
                filterSet.fromXml(filterSetNode);
                String qname = filterSet.getqplotname();
                if (qname != null) {
                    dropDownValues.add(qname);
                }
            }
            return dropDownValues;
        } catch (ConcurrentModificationException ex) {
            // Nothing
        }
        return dropDownValues;
    }

    private JMenuItem createQuickPlotTypeMenuItem(final QuickPlotCustomizer.Type type) {
        final JRadioButtonMenuItem result = new JRadioButtonMenuItem(type.title(), quickPlotType.equals(type));
        typeGroup.add(result);

        result.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent ae) {
                quickPlotType = type;
                result.setSelected(true);
            }
        });
        return result;
    }

    private void setQuickPlot(FilterSet filter) {
        setAllQuickPlot(false);
        g_wepsTable.setRepaintEnabled(false);
        try {
            for (String id : filter.quickPlotColumns()) {
                g_wepsTable.getMeta().getColumn(id).setQuickPlotL(true);
            }
        } finally {
            g_wepsTable.setRepaintEnabled(true);
            g_wepsTable.repaint();
        }
    }

    private void setAllQuickPlot(boolean quickplot) {
        g_wepsTable.setRepaintEnabled(false);
        try {
            for (Column column : g_wepsTable.getMeta().getColumns()) {
                column.setQuickPlotL(quickplot);
            }
        } finally {
            g_wepsTable.setRepaintEnabled(true);
            g_wepsTable.repaint();
        }
    }

    /**
     *
     */
    public void display() {
        Thread task = new Thread("DetailReport.display()") {

            @Override
            public void run() {
                try {
                    setEnabled(false);
                    g_wepsTable.setQuickPlot(true);
                    g_wepsTable.setPixelWidth(-1, 20);
                    g_wepsTable.setMinWidth(-1, 20);
                    g_wepsTable.setMaxWidth(-1, 20);
                    loadMeta();

                    loadFilters();
                    loadData();

                    EventQueue.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            //Hack to recalc first data row height.
                            g_wepsTable.setPixelHeight(g_wepsTable.getMeta().getLabelRowCount() - 1, JCTableEnum.VARIABLE);

                            setVisible(true);
                            setEnabled(true);

                        }
                    });

                    requestFocusInWindow();

                    //Awful hack to make sure the icons are drawn
                    //TODO
                    try {

                        for (int i = 0; i < 10; i++) {
                            EventQueue.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    g_wepsTable.repaint();

                                }

                            });
                            sleep(200);
                        }
                    } catch (InterruptedException e) {
                        //do nothing
                    }

                    //System.out.println((end - start));
                } catch (ConcurrentModificationException ex) {
                    counter++;
                    if (counter >= 20) {
                        int result = JOptionPane.showConfirmDialog(DetailReport.this,
                                "Detail Report has failed to open 20 times./nWould you like to continue trying?",
                                "Error", JOptionPane.YES_NO_OPTION);
                        if (result == JOptionPane.YES_OPTION) {
                            counter = 0;
                        } else if (result == JOptionPane.NO_OPTION) {
                            return;
                        }
                    }
                    Logger.getLogger(DetailReport.class).error("Failed to open Detail Report " + counter
                            + " times.  Trying again", ex);
                    dispose();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ex1) {
                        ex1.printStackTrace();
                    }
                    ReportManager.getDefault().displayReport(c_runFile, ReportManager.REPORT_DETAIL);

                }
            }
        };
        task.start();
    }
    private static int counter = 0;

    @Override
    protected void g_defaultPlotList_ActionPerformed(java.awt.event.ActionEvent evt) {   
        //Just displays the most recently accessed default quickplot
        justInitQuickplotFrominstance(c_currentPlotIndex);
    } 
    
    /*
    Emulates some necessary actions to display a Quickplot, while also skipping 
    unecessary steps. This is to be preformed from the instance of detail report,
    so it doesn't need to load anything, just prepare the instance.
    */
    private void justInitQuickplotFrominstance(int customPlot) {
        setEnabled(true);
        g_wepsTable.setQuickPlot(true);
        g_quickplot.setText("Quick Plot");
        c_currentPlotIndex = customPlot; // For display use
        c_fromHiddenQuickplotList_flag = true;
        
        FilterSet filterSet = c_qplotFilterSets.get(customPlot);
        setQuickplotType();
        
        quickPlot(quickPlotType, filterSet.quickPlotColumns(), filterSet.quickPlotColumnsR());
    }
   
     /**
     *
     * This Function is designed to emulate the necessary actions for QuickPlot
     * graphs to be displayed, that is usually done in display, while not 
     * displaying the detail report instance. Is used in Report Manager in 
     * switch statement in createAndDisplayJFrame().
     */
    public void justInitQuickplot(int customPlot) {
        setEnabled(true);
        g_wepsTable.setQuickPlot(true);
        g_quickplot.setText("Quick Plot");
        loadMeta();
        loadFilters();
        g_wepsTable.applyFilterSet(c_qplotFilterSets.get(customPlot));
        c_currentPlotIndex = customPlot; // For display use
        loadData();
        c_fromHiddenQuickplotList_flag = true;
        setQuickplotType();
        chartActionPerformed();
    }
    
   
    private void loadMeta() {
        TFile file = new TFile(c_metaPath);
        WepsTableMeta meta = new WepsTableMeta();
        meta.fromFile(file);
        g_wepsTable.setMeta(meta);
        g_wepsTable.hookBorderPainters(this);
        c_fullDataViewModel = g_wepsTable.getDataView();
    }

    private void loadFilters() {
        c_loadingFilters = true;
        c_qplotFilterSets = new ArrayList<>();
        c_qplotnames = new ArrayList<>();
        TFile file = new TFile(c_filtersPath);
        Element node = Helper.getRootNode(file);
        List<Element> filtersetList = node.getChildren(WepsTableEnum.XML_filterset);
        FilterSet defaultSet = null;
        try {
            for (Element filterSetNode : filtersetList) {
                FilterSet filterSet = new FilterSet();
                filterSet.fromXml(filterSetNode);
                
                String qname = filterSet.getqplotname();
                // if this has a qplot name and no 'name', its a hidden default set
                if (qname != null && filterSet.getName() == null) {
                    c_qplotnames.add(qname);
                    c_qplotFilterSets.add(filterSet);
                } else if (qname != null && filterSet.getName() != null){
                    // if you want it as a default quickplot AND a filterset on the detail report
                    c_qplotnames.add(qname);
                    c_qplotFilterSets.add(filterSet);
                    g_reportDropDown.addItem(filterSet);
                    if (defaultSet == null) {
                        defaultSet = filterSet;
                    }
                } else {
                    // just adds it to the list of filtersets on detial report
                    g_reportDropDown.addItem(filterSet);
                    if (defaultSet == null) {
                        defaultSet = filterSet;
                    }
                }
            }
        } catch (ConcurrentModificationException ex) {
            System.out.println("************DetailReport.loadFilters()**************");
            ex.printStackTrace();
        }
        if (defaultSet != null) {
            g_reportDropDown.setSelectedItem(defaultSet);
            g_wepsTable.applyFilterSet(defaultSet);
        }
        c_loadingFilters = false;
    }

    private void loadData() {
        ReportContext.enter();
        try {
            c_run = new RunFileData(c_runFile.getAbsolutePath(), true);
            c_run.fireAll(this);
            //        g_wepsTable.setDataSource(new DetailData());
            TFile dataFile = new TFile(c_runFile, "gui1_data.out");
            DetailData data = new DetailData();

            if (data.load(this, dataFile)) {
/**
                 * Note:  Assertions are not enabled.  These will be useless items
                 * unless assertions are enabled.  Thus, they will be commented out unless
                 * the user wishes to enable specific assertions (feed the virtual machine 
                 * the -ea argument).
                 */
//                assert EventQueue.isDispatchThread() : "Not supposed to be called here!";
                g_wepsTable.setDataSource(data);
            }
        } finally {
            ReportContext.exit();
        }

    }
    
     /**
     * This function sets the current quickplot type for default quickplots menus.
     * try-catches are due to the quickplot() method needing the Type Enum in QuickplotCustomizer
     * @Sets quickPlotType, default is line
     */
    private void setQuickplotType() {
        String plottype = c_qplotFilterSets.get(c_currentPlotIndex).getplottype();
        // default to line
        QuickPlotCustomizer.Type type = QuickPlotCustomizer.Type.valueOf("Line");
        
        try {
            type = QuickPlotCustomizer.Type.valueOf(plottype);
        } catch (IllegalArgumentException e) {
            // do nothing, just try to set the type. Don't want to crash and burn from a typo in a filterset
        } catch (NullPointerException n) {
            // if no type specified, default line is returned
        }
        finally {
            quickPlotType = type;
        }
    }

    /**
     *
     * @param event
     */
    @Override
    public void propertyChange(PropertyChangeEvent event) {
        String property = event.getPropertyName();
        Object newValue = event.getNewValue();
        String newStringValue = newValue.toString();
        switch (property) {
            case usda.weru.util.ConfigData.Units:
                g_wepsTable.setUnitsSystem(newStringValue);
                break;
            case RunFileData.UserName:
                c_client = newStringValue;
                break;
            case RunFileData.FarmId:
                c_farm = newStringValue;
                break;
            case RunFileData.TractId:
                c_tract = newStringValue;
                break;
            case RunFileData.FieldId:
                c_field = newStringValue;
                break;
            case RunFileData.ManageFile:
                c_management = new TFile(newStringValue).getName().replace(".man", "").replace(".xml", "");
                break;
            case RunFileData.SoilFile:
                c_soil = new TFile(newStringValue).getName().replace(".ifc", "");
                break;
            case RunFileData.RegionAngle:
                c_angle = newStringValue;
                break;
            case usda.weru.util.ConfigData.FormatOperationDate:
                c_rotationDateFormat = newStringValue;
                break;
            case usda.weru.util.ConfigData.DetailTableFilterFile:
                c_filtersPath = newStringValue;
                break;
        }

    }

    @Override
    public void parse(ParseEvent event) {
        String field = event.getField();
        String system = g_wepsTable.getUnitsSystem();
        switch (field) {
            case "wind_energy_limit":
                switch (system) {
                    case usda.weru.util.Util.SIUnits:
                        event.setParsedExpression("> 8m/s");
                        break;
                    case usda.weru.util.Util.USUnits:
                        event.setParsedExpression("> 18mph");  // Rounded from 17.9 to even value - LEW
                        break;
                }
                break;
            case "snow_depth_limit":
                switch (system) {
                    case usda.weru.util.Util.SIUnits:
                        event.setParsedExpression("> 20mm");
                        break;
                    case usda.weru.util.Util.USUnits:
                        event.setParsedExpression("> 0.75in");  // Rounded to 3/4" - LEW
                        break;
                }
                break;
            case "aggregateslimit":
                switch (system) {
                    case usda.weru.util.Util.SIUnits:
                        event.setParsedExpression("< 0.84mm");
                        break;
                    case usda.weru.util.Util.USUnits:
                        // Will not display in English units, 0.03", since it has always been reported as 0.84mm - LEW
                        event.setParsedExpression("< 0.03in");
                        break;
                }
                break;
            case "run":
                event.setParsedExpression(c_runFile.getName().replace(RunFileData.RunSuffix, ""));
                break;
            case "client":
                event.setParsedExpression(c_client);
                break;
            case "farm":
                event.setParsedExpression(c_farm);
                break;
            case "tract":
                event.setParsedExpression(c_tract);
                break;
            case "field":
                event.setParsedExpression(c_field);
                break;
            case "management":
                event.setParsedExpression(c_management);
                break;
            case "soil":
                event.setParsedExpression(c_soil);
                break;
            case "fieldangle":
                event.setParsedExpression(c_angle);
                break;
        }

    }

    /**
     *
     * @param evt
     */
    @Override
    protected void reportDropDownItemStateChanged(ItemEvent evt) {
        if (c_loadingFilters) {
            return;
        }
        if (evt.getStateChange() != ItemEvent.SELECTED) {
            return;
        }
        FilterSet filterSet = (FilterSet) g_reportDropDown.getSelectedItem();
        g_wepsTable.applyFilterSet(filterSet);
        columnEndings.clear();
        this.repaint();
        g_wepsTable.repaintNow();
    }
    
    public ArrayList<String> getqplotnames() {
        return c_qplotnames;
    }

    /**
     *
     * @param evt
     */
    @Override
    protected void csButtonActionPerformed(java.awt.event.ActionEvent evt) {
        ReportManager.getDefault().displayReport(c_runFile, ReportManager.REPORT_CROPSUM);
    }

    /**
     *
     * @param evt
     */
    @Override
    protected void ssButtonActionPerformed(java.awt.event.ActionEvent evt) {
        ReportManager.getDefault().displayReport(c_runFile, ReportManager.REPORT_STIR);
    }

    /**
     *
     * @param evt
     */
    @Override
    protected void ccButtonActionPerformed(java.awt.event.ActionEvent evt)
    {
        ReportManager.getDefault().displayReport(c_runFile, ReportManager.REPORT_COVERCROPDET);
    }

    /**
     *
     * @param evt
     */
    @Override
    protected void ciButtonActionPerformed(java.awt.event.ActionEvent evt)
    {
        ReportManager.getDefault().displayReport(c_runFile, ReportManager.REPORT_CROP_INT_SUM);
    }

    /**
     *
     * @param evt
     */
    @Override
    protected void msButtonActionPerformed(java.awt.event.ActionEvent evt) {
        ReportManager.getDefault().displayReport(c_runFile, ReportManager.REPORT_MANAGEMENT);
    }

    /**
     *
     * @param evt
     */
    @Override
    protected void psButtonActionPerformed(java.awt.event.ActionEvent evt) {
        ReportManager.getDefault().displayReport(c_runFile, ReportManager.REPORT_RUN);
    }

    /**
     *
     * @param evt
     */
    @Override
    protected void cshButtonActionPerformed(java.awt.event.ActionEvent evt) {
    }

    /**
     *
     * @param evt
     */
    @Override
    protected void helpButtonActionPerformed(java.awt.event.ActionEvent evt) {
    }

    /**
     *
     * @param evt
     */
    @Override
    protected void printButtonActionPerformed(java.awt.event.ActionEvent evt) {
        g_wepsTable.printPreview(this);

    }

    /**
     *
     * @param evt
     */
    @Override
    protected void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {
        dispose();
    }

    /**
     *
     * @param event
     */
    public void printEnd(JCPrintEvent event) {
    }

    /**
     *
     * @param event
     */
    @Override
    public void printPageBody(JCPrintEvent event) {
    }

    /**
     *
     * @param e
     */
    @Override
    public void printPageFooter(JCPrintEvent e) {
        Graphics gc = e.getGraphics();
        Rectangle r = gc.getClip().getBounds(); // fix to get rid of dep method
        FontMetrics fm = gc.getFontMetrics();

        String note = "Created: " + new Date(c_runFile.lastModified()).toString() + "   ";

        gc.drawString(note, r.width - fm.stringWidth(note), r.height / 2);
    }

    /**
     *
     * @param e
     */
    @Override
    public void printPageHeader(JCPrintEvent e) {
        Graphics gc = e.getGraphics();

        Rectangle r = gc.getClip().getBounds(); // fix to get rid of dep method
        FontMetrics fm = gc.getFontMetrics();

        String note = "Run: " + c_runFile.getAbsolutePath() + "   ";
        gc.drawString("Detail Report", 0, r.height / 2);
        gc.drawString(note, r.width - fm.stringWidth(note), r.height / 2);
    }

    class DetailData extends AbstractDataSource implements TableDataModel {

        private static final long serialVersionUID = 1L;

        private List<String> c_keyMap;
        private List<Object[]> c_data;

        public DetailData() {
            c_keyMap = new Vector<String>();
            c_data = new Vector<Object[]>();
            c_data = Collections.synchronizedList(c_data);
            c_keyMap = Collections.synchronizedList(c_keyMap);
            fireDataReset();
        }
        

        protected List<String> getKeys() {
            return c_keyMap;
        }

        protected List<Object[]> getDataList() {
            return c_data;
        }

        @Override
        public int getNumColumns() {
            synchronized (c_keyMap) {
                return c_keyMap.size();
            }
        }

        @Override
        public int getNumRows() {
            synchronized (c_data) {
                return c_data.size();
            }
        }

        @Override
        public Object getTableColumnLabel(int columnIndex) {
            synchronized (c_keyMap) {
                return c_keyMap.get(columnIndex);
            }
        }

        @Override
        public Object getTableDataItem(int rowIndex, int columnIndex) {
            synchronized (c_data) {
                Object[] row = c_data.get(rowIndex);
                return row[columnIndex];
            }

        }

        @Override
        public Object getTableRowLabel(int rowIndex) {
            return null;
        }

        //Load data
        public boolean load(Component parent, TFile file) {
            String inLine;
            boolean success = true;
            //read the file one line at a time into a vector for processing
            ProgressMonitor progress = null;
            try {
                try (InputStreamReader streamReader = new InputStreamReader(new TFileInputStream(file))) {
                    BufferedReader in = new BufferedReader(streamReader);
                    do {
                        inLine = in.readLine();
                        if (inLine != null) {
//                        progress.setProgress((int)channel.position());
                            parseLine(inLine);
                        }
//                    if (progress.isCanceled()){
//                        success = false;
//                        break;
//                    }

                    } while (inLine != null);

                    in.close();
                }

            } catch (FileNotFoundException fnfe) {
                JOptionPane.showMessageDialog(parent, "An expected data file is missing.\n\""
                        + file.getName() + "\" may have been deleted.", "Missing Data File",
                        JOptionPane.ERROR_MESSAGE);
                success = false;
            } catch (OutOfMemoryError oome) {
//                progress.close();
                JOptionPane.showMessageDialog(parent, "The data file is too large.",
                        "Out of Memory", JOptionPane.ERROR_MESSAGE);
                success = false;
            } catch (IOException e) {
                e.printStackTrace();
                success = false;
            }
            fireDataReset();
            return success;

        }

        private void parseLine(String line) {
            //We have the header line.
            if (line.startsWith("key")) {
                StringTokenizer st = new StringTokenizer(line, "|");
                while (st.hasMoreTokens()) {
                    synchronized (c_keyMap) {
                        c_keyMap.add(st.nextToken().trim());
                    }
                }
            } //We have the totals line.
            else {
                StringTokenizer st = new StringTokenizer(line, "|");
                int index = 0;
                Object[] row = null;
                synchronized (c_keyMap) {
                    row = new Object[c_keyMap.size()];
                }
                String lineKey = "";
                boolean skipRow = false;
                synchronized (c_keyMap) {
                    while (st.hasMoreTokens() && index < c_keyMap.size()) {

                        String value = st.nextToken().trim();
                        if (index == 0) {
                            lineKey = value;
                            lineKey = lineKey.trim();
                        }

                        Object newValue = value;

                        if (lineKey.equals("P") && index == 1) {
                            //Month Field
                            newValue = formatDate(value);
                        } else if (index == 2 || index == 3) {
                            if (newValue.toString().indexOf("~") >= 0) {
                                newValue = newValue.toString().replace("~", "\n");
                            }
                            if (newValue.toString().trim().length() == 0) {
                                newValue = null;
                            }

                        } else {
                            try {
                                newValue = Double.parseDouble(value);
                            } catch (NumberFormatException nfe) {
                                newValue = value;
                            }
                        }

                        row[index] = newValue;
                        index++;
                    }
                }
                if (!skipRow) {
                    synchronized (c_data) {
                        c_data.add(row);
                    }
                }
            }
        }

        private String formatDate(String value) {
            StringTokenizer st = new StringTokenizer(value, "/");
            if (st.countTokens() != 3) {
                return value;
            }
            String days = st.nextToken().trim();
            String month = st.nextToken().trim();
            String year = st.nextToken().trim();

            try {
                String x = (year.equals("13")) ? "12" : "13";
                String temp = month + "/" + x + "/" + year;
                SimpleDateFormat format = new SimpleDateFormat("M/d/y");
                format.setLenient(true);
                Date d = format.parse(temp);
                format.applyPattern(c_rotationDateFormat);
                String outStr = format.format(d);
                if (days.length() > 2) {
                    while (days.length() < 5) {
                        days = " " + days;
                    }
                }
                outStr = outStr.replace(x, days);
                return outStr;
            } catch (ParseException ex) {
                return "#ERR#";
            }
        }
    }

    static class TableCSHManager implements CSH.Manager {

        private final WepsTable c_table;

        public TableCSHManager(WepsTable table) {
            c_table = table;
        }

        private void translateMouseEvent(MouseEvent me) {
            Object parent = me.getSource();
            Component comp = c_table;
            int x = 0;
            int y = 0;
            while (comp != parent) {
                x += comp.getX();
                y += comp.getY();
                comp = comp.getParent();
            }

            me.translatePoint(-x, -y);
        }

        @Override
        public String getHelpIDString(Object comp, AWTEvent evt) {
            if (comp != c_table) {
                return null;
            }
            try {
                if (evt instanceof MouseEvent) {
                    MouseEvent me = (MouseEvent) evt;
                    translateMouseEvent(me);

                    int columnIndex = c_table.XtoColumn(me.getX());
                    Column column = c_table.getMeta().getColumn(columnIndex);
                    String id = column.getId();

                    if (id != null) {
                        id = id.replace(" ", "_");
                        return "detail:" + id + "_html";
                    } else {
                        return null;
                    }
                } else {
                    return null;
                }
            } catch (Exception e) {
                return null;
            }
        }

        @Override
        public HelpSet getHelpSet(java.lang.Object comp, java.awt.AWTEvent e) {
            return Help.getHelpSet();
        }
    }

    @Override
    protected void chartActionPerformed() {
        //figure out the columns to plot
        List<String> fields = new ArrayList<>();
        List<String> fields2 = new ArrayList<>();
        for (Column column : g_wepsTable.getMeta().getColumns()) {
            if (column.isQuickPlotL() && isColumnVisible(column) && column.getDataType() == WepsTableEnum.DATA_numeric) {
                fields.add(column.getDataKey());
            }
            if(column.isQuickPlotR() && isColumnVisible(column) && column.getDataType() == WepsTableEnum.DATA_numeric) {
                fields2.add(column.getDataKey());
            }
        }
        if (fields.isEmpty() && fields2.isEmpty()) {
            //prompt user to select fields
            JOptionPane.showMessageDialog(this, "Select at least one detail column to plot.",
                    "Quick Plot", JOptionPane.WARNING_MESSAGE);
        } else {
            quickPlot(quickPlotType, fields.toArray(new String[fields.size()]), 
                    fields2.toArray(new String[fields2.size()]));
        }

    }

    private boolean isColumnVisible(Column column) {
        FilterSet filter = getSelectedFilterSet();
        for (ColumnFilter cf : filter.columnFilters()) {
            if (cf.acceptColumn(column)) {
                return true;
            }
        }
        return false;
    }

    /**
     *
     * @return
     */
    public FilterSet getSelectedFilterSet() {
        // If this flag is set, it means the filterset should come from the list of 
        // hidden filtersets, not just references whats set in the detail report drop
        // down menu.
        if (c_fromHiddenQuickplotList_flag) {
            Object result = c_qplotFilterSets.get(c_currentPlotIndex);
            if (result instanceof FilterSet) {
                return (FilterSet) result;
            }
                return null;
        } else {
            Object result = g_reportDropDown.getSelectedItem();
            if (result instanceof FilterSet) {
                return (FilterSet) result;
            }
                return null;
        }
    }

    private boolean isRowQuickPlot(String key) {
        for (String test : getSelectedFilterSet().quickPlotRows()) {
            if (test != null && test.equals(key)) {
                return true;
            }
        }
        return false;
    }

    /**
     *
     * @param type
     * @param fields
     * @param fields2
     */
    public void quickPlot(final QuickPlotCustomizer.Type type, final String[] fields, final String[] fields2) {
        //make sure we're not on the event queue!
        if (EventQueue.isDispatchThread()) {
            Thread background = new Thread("Quick Plot: " + Arrays.toString(fields)) {

                @Override
                public void run() {
                    quickPlot(type, fields, fields2);
                }

            };
            background.start();
            return;
        }

        final ReportPack pack = ReportManager.getDefault().getReportPack(
                c_runFile, ReportManager.REPORT_QUICKPLOT, false);

        //make the dataset
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        DefaultCategoryDataset dataset2 = new DefaultCategoryDataset();
        ArrayList<DefaultCategoryDataset> collectionLeft = new ArrayList<>();
        ArrayList<DefaultCategoryDataset> collectionRight = new ArrayList<>();

//        MessageFormat dateFormat = new MessageFormat("{2} {0}-{1}, {3}");
//        DateFormat dateFormat2 = new SimpleDateFormat("MMM-dd-y");
//        
        MinMax left = new MinMax();
        MinMax right = new MinMax();

        if (c_fromHiddenQuickplotList_flag) { // from quickplot lists
            fillQuickPlotDataset(dataset, fields, "Left Axis", left);
            fillQuickPlotDataset(dataset2, fields2, "Right Axis", right);
            fillQuickPlotSubsets(collectionLeft, fields, "Left Axis");
            fillQuickPlotSubsets(collectionRight, fields2, "Right Axis");
        } else { // generated off detail report
            fillDataset(dataset, fields, "Left Axis", left);
            fillDataset(dataset2, fields2, "Right Axis", right);
            fillSubsets(collectionLeft, fields, "Left Axis");
            fillSubsets(collectionRight, fields2, "Right Axis");
        }

        // If this triggers, theres only one year worth of data, so we don't need the subset graphs
        if ((collectionLeft.size() == 1 && collectionRight.size() == 1)
                || (collectionLeft.isEmpty() && collectionRight.size() == 1)
                || (collectionLeft.size() == 1 && collectionRight.isEmpty())) {
            collectionLeft.clear();
            collectionRight.clear();
        }

        collectionLeft.add(0, dataset);
        collectionRight.add(0, dataset2);
        QuickPlotCustomizer.Type tempType = type;
        
        //If a stacked bar chart is selected with both datasets having data,
        //we want to convert to a bar chart.
        if (dataset.getColumnCount() != 0
                && dataset2.getColumnCount() != 0
                && tempType.equals(QuickPlotCustomizer.Type.BarStacked)) {
            tempType = QuickPlotCustomizer.Type.Bar;
        }
//        if (tempType.equals(QuickPlotCustomizer.Type.Bar)) {
//            for (int index = 0; index < Math.min(collectionLeft.size(), collectionRight.size()); index++) {
//                DefaultCategoryDataset leftD = collectionLeft.get(index);
//                DefaultCategoryDataset rightD = collectionRight.get(index);
//                rightD = nullIn(leftD, rightD);
//                collectionRight.set(index, rightD);
//            }
//        }

        //add the dataset to the report parameters
        try {
            /* Fix for weps freezing up if an axis exists but has no data to plot. Sets scale to a max of .1 
            so graph displays and doesn't freeze up.
             */
            if (fields.length != 0 && isDoubleZero(left.max) && isDoubleZero(left.min)) {
                left.setMax(0.1);
            }
            if (fields2.length != 0 && isDoubleZero(right.max) && isDoubleZero(right.min)) {
                right.setMax(0.1);
            }

            if (c_fromHiddenQuickplotList_flag) {
                pack.getReportParameters().put("PLOT_NAME", c_qplotFilterSets.get(c_currentPlotIndex).getqplotname());
                c_fromHiddenQuickplotList_flag = false;
            }
            pack.getReportParameters().put(QuickPlotCustomizer.PARAMETER_TYPE, tempType.key());
            pack.getReportParameters().put("LMax", left.getMax());
            pack.getReportParameters().put("RMax", right.getMax());
            pack.getReportParameters().put("LMin", left.getMin());
            pack.getReportParameters().put("RMin", right.getMin());
            pack.connectQuickPlot(collectionLeft, collectionRight);
        } catch (SQLException error) {
            System.out.println("We failed at passing the quickplot data to the quickplot report");
        }
        EventQueue.invokeLater(() -> {
            //todo: just display the frame myself, don't want any of this to be cached by the report manager!
            ReportViewer viewer = new ReportViewer(pack, true);
            
            //dirty hacks
            String reportTitle = "WEPS Quick Plot";
            Rectangle screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
            Dimension size = viewer.getSize();
            size.height = screen.height < 800 ? screen.height : 800; //975 x 800
            size.width = screen.width < 975 ? screen.width : 975;
            viewer.setSize(size);
            viewer.setLocation(screen.x, screen.y);
            
            viewer.setTitle(reportTitle + " : " + Util.purgeExtensions(c_run.fileObject.getName(), ".wjr"));
            viewer.setVisible(true);
        });
    }
    
    // Sincle floating point numbers cannot be compared, we need to test it against a margin of error
    // hence this beuatiful method. 
    private boolean isDoubleZero(double num) {
        final double threshold = 0.00000001; // Change this if min/max gets even smaller?
        return num >= (-threshold) && num <= threshold;
    }
    
    
    /* This function is designed to fill the subsets of the quickplots. Each dataset in the collection
    is meant to be a year of data. Filled of the reaw data in the 'gui1_data.out' file.
    */
    private void fillQuickPlotSubsets(ArrayList<DefaultCategoryDataset> collection, final String[] fields, String axis) {
        
        final int indexOfFirstDataKey = 4; // This is how data is set up.
        
        try {
            // Get the raw data from the current runfile.
            TFile dataFileFromCurrentRun = new TFile(c_runFile, "gui1_data.out");
            DetailData currentRunData = new DetailData();
            currentRunData.load(this, dataFileFromCurrentRun); // Now currentRunData has all the runFile data in it.
            
            // get the total list of keys, populate string array with keys
            String[] keys = new String[currentRunData.getKeys().size()];
            currentRunData.getKeys().toArray(keys);
            
            int[] indicies = new int[fields.length];
            // dirty nested for-loop, could use something fancier but doesn't run often
            for (int keyIndex = indexOfFirstDataKey; keyIndex < keys.length; keyIndex++){
                for (int fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
                    if (keys[keyIndex].equals(fields[fieldIndex])) {
                        indicies[fieldIndex] = keyIndex; // Save the index for accessing.
                    }
                }
            }
            
            // This data structure is an array of Objects, where each object is an array of Objects.
            // the first 4 items in each sub-array (0-3) are labels. The rest SHOULD be data.
            Object[] dataRows = new Object[currentRunData.getDataList().size()];
            currentRunData.getDataList().toArray(dataRows); // dataRows is now and Object[][] essentially
            
            // we need to get the filterset, to get the allowed row filters to populate the graph.
            FilterSet filterSet = getSelectedFilterSet();
            RowFilter[] rowFilters = filterSet.rowFilters();
            
            String[] acceptableFilters = new String[rowFilters.length]; // for storing what keys are allowed
            
            /* This for loop rolls through the current applyied filter set and gets the keys that determine
            what data is displayed. */
            for (int i = 0; i < rowFilters.length; i++) {
                if (rowFilters[i] instanceof RowKeyFilter) {
                    acceptableFilters[i] = ((RowKeyFilter) rowFilters[i]).getKeyFilter();
                }
            }
            
            // if this has any field in it, the data in it should be converted from a fraction (x/100)
            // to a percent 0-100%
            String[] toPercentList = c_qplotFilterSets.get(c_currentPlotIndex).getFractionToPercentIDs();
            
            // wrote this one a for-each. This is to address the correct field for display. All the numbers will still
            // be zero because it runs down the zeroth row in 'gui1_data.out' file, and they're all strings, which in the 
            // following code will be set to zero. A little dirty, but good for now. 
            int counter;
            
            DefaultCategoryDataset dataset = new DefaultCategoryDataset(); // initial dataset
            
            for (int rowIndex = 0; rowIndex < dataRows.length; rowIndex++) {

                counter = 0; // reset to 0 for next iteration of loop
                
                for (int columnIndex : indicies) {
                    // Uncommenting this line will not include *AT ALL* fields that can't be found.
                    // if (columnIndex < indexOfFirstDataKey) continue;
                    
                    // This boolean denotes if the current field on the inner for loop should be turned to a percent.
                    boolean isPercent = false;
                        for (String percentField : toPercentList) {
                            if (percentField.equals(keys[columnIndex])) {
                                isPercent = true;
                                break;
                            }
                    }

                    // we need this to apply the proper 'adjustments' to the data for use/display
                    Column column = g_wepsTable.getMeta().getColumn(fields[counter]);
                    
                    
                    Object[] currentRow = dataRows[rowIndex] instanceof Object[] ? (Object[])dataRows[rowIndex] : null;
                    
                    if (currentRow == null) {
                        continue;
                    }
                    
                    String key = currentRow[0] instanceof String ? (String) currentRow[0] : null;
                    
                    // Since this function fills subsets, check for individual year data and cut at that point.
                    if (key.equals("Y")) {
                        collection.add(dataset);
                        dataset = new DefaultCategoryDataset(); // reset dataset
                        break; // Need to actually drop out of this for loop at this point
                    }
                    
                    
                    // test if key is acceptable
                    boolean isAcceptable = false;
                    for (String k : acceptableFilters) {
                        if (k.equals(key)) {
                            isAcceptable = true; // if key is in list of acceptable keys
                            break;
                        }
                    }
                    
                    // if no match was found, we don't want to include the data.
                    if (!isAcceptable)
                        continue;
                    
                    Object num = currentRow[columnIndex]; // get the object at current field's indicie
                    
                    Number dataValue = null; // initialize data value

                    if (!(num instanceof Number)) num = 0; // make a 'Number' object if null or some erroneous value

                    // Data must be adjusted for display use. This is done on a column basis.
                    num = column.applyAdjustments(num, ConfigData.getDefault().getData(ConfigData.Units));
                    num = column.applyDisplayUnits(num, ConfigData.getDefault().getData(ConfigData.Units));

                    dataValue = (Number) num; // create number, thi is what dataset takes
                    
                    if (isPercent) { // convert from fraction to percent
                        dataValue = dataValue.doubleValue() * 100;
                    }
                    
                    // get the data associated with this data point
                    Object dateObj = currentRow[1]; // index one is where in the row the data string lives

                    // get unit abreviation for display
                    ConversionUnit units = column.getDisplayUnits(g_wepsTable.getUnitsSystem());
                    String unitStr = units == null ? "" : units.getAbbreviation();
                    
                    String dateString = dateObj instanceof String ? (String) dateObj : "";
                    
                    // get legend string and add to dataset
                    dataset.addValue(dataValue, getLegendString(fields[counter], column, unitStr, axis), dateString);
                    
                    counter++; // increment for next loop
                } // end inner for loop 
            } // end outer for-each loop          
        } catch (Exception e) {
            System.err.println("Error in retrieving raw data for quickplots.");
        } 
        
        
    }

    /* 
    This function takes in the fields (keys) for accessing the data, the axis that the data lives on,
    and an empty dataset to populate. It uses the raw data from the 'gui1_data.out' file to populate 
    the dataset regardless of the detail report interface showing. This only creates the first page of 
    the dataset, which is a summary of every year in the cycle.
    */
    private void fillQuickPlotDataset(DefaultCategoryDataset dataset, final String[] fields, String axis, MinMax lims) {
        /* The data is stored in a weird way. It gives a list of keys, but then does'nt give you the 
        ability to access them (I.E with a get or something for the data structure). Instead it stores the 
        data by column, instead of by row (where all the data is for the same thing). Thus to access
        this data, we have to find the indecies of the corresponding ID and access it column by column.
        */
        final int indexOfFirstDataKey = 4; // This is how data is set up.
        
        try {
            // Get the raw data from the current runfile.
            TFile dataFileFromCurrentRun = new TFile(c_runFile, "gui1_data.out");
            DetailData currentRunData = new DetailData();
            currentRunData.load(this, dataFileFromCurrentRun); // Now currentRunData has all the runFile data in it.
            
            // get the total list of keys, populate string array with keys
            String[] keys = new String[currentRunData.getKeys().size()];
            currentRunData.getKeys().toArray(keys);
            
            int[] indicies = new int[fields.length];
            // dirty nested for-loop, could use something fancier but doesn't run often
            for (int keyIndex = indexOfFirstDataKey; keyIndex < keys.length; keyIndex++){
                for (int fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
                    if (keys[keyIndex].equals(fields[fieldIndex])) {
                        indicies[fieldIndex] = keyIndex; // Save the index for accessing.
                    }
                }
            }
            
            // This data tructure is a an array of Objects, where each object is an array of Objects.
            // the first 4 items in each sub-array (0-3) are labels. The rest SHOULD be data.
            Object[] dataRows = new Object[currentRunData.getDataList().size()];
            currentRunData.getDataList().toArray(dataRows); // dataRows is now and Object[][] essentially
            
            // we need to get the filterset, to get the allowed row filters to populate the graph.
            FilterSet filterSet = getSelectedFilterSet();
            RowFilter[] rowFilters = filterSet.rowFilters();
            
            String[] acceptableFilters = new String[rowFilters.length]; // for storing what keys are allowed
            
            /* This for loop rolls through the current applyied filter set and gets the keys that determine
            what data is displayed. */
            for (int i = 0; i < rowFilters.length; i++) {
                if (rowFilters[i] instanceof RowKeyFilter) {
                    acceptableFilters[i] = ((RowKeyFilter) rowFilters[i]).getKeyFilter();
                }
            }
            
            // if this has any field in it, the data in it should be converted from a fraction (x/100)
            // to a percent 0-100%
            String[] toPercentList = c_qplotFilterSets.get(c_currentPlotIndex).getFractionToPercentIDs();
            
            // wrote this one a for-each. This is to address the correct field for display. All the numbers will still
            // be zero because it runs down the zeroth row in 'gui1_data.out' file, and they're all strings, which in the 
            // following code will be set to zero. A little dirty, but good for now. 
            int counter = 0;
            
            /* This for loop is intended to loop through all the rows and get the data for each column
            and add it to the dataset not very effiecient, but it's how all the previous quickplots were
            generated */
            for (int columnIndex : indicies) {
                // Uncommenting this line will not include *AT ALL* fields that can't be found.
                // if (columnIndex < indexOfFirstDataKey) continue;
                
                // This boolean denotes if the current field on the inner for loop should be turned to a percent.
                boolean isPercent = false;
                    for (String percentField : toPercentList) {
                        if (percentField.equals(keys[columnIndex])) {
                            isPercent = true;
                            break;
                        }
                }
                    
                // we need this to apply the proper 'adjustments' to the data for use/display
                Column column = g_wepsTable.getMeta().getColumn(fields[counter]);
                
                for (int rowIndex = 0; rowIndex < dataRows.length; rowIndex++) {
                    Object[] currentRow = dataRows[rowIndex] instanceof Object[] ? (Object[])dataRows[rowIndex] : null;
                    
                    if (currentRow == null) {
                        continue;
                    }
                    
                    String key = currentRow[0] instanceof String ? (String) currentRow[0] : null;
                    // test if key is acceptable
                    boolean isAcceptable = false;
                    for (String k : acceptableFilters) {
                        if (k.equals(key)) {
                            isAcceptable = true; // if key is in list of acceptable keys
                            break;
                        }
                    }
                    
                    // if no match was found, we don't want to include the data.
                    if (!isAcceptable)
                        continue;
                    
                    Object num = currentRow[columnIndex]; // get the object at current field's indicie
                    
                    Number dataValue = null;
                    
                    /* Uncommenting this code and applying else will skip null points. Also included is 
                    ability to configure other non Number entires in data sat for different behavior.
                    *** Needs to be applied in similar way to all 'fill dataset' functions for full coverage.
                    if (num instanceof String) {
                        String dataStandIn = (String) num;
                        if (dataStandIn.length() == 0) {
                            // This is a null value
                        } else if (dataStandIn.equals("**********")) {
                            // something that isn't a blank space, or 'N/A' likey a data model error '
                        } else { // N/A available data 
                     }} else { place code all the way up to 'Object dateObj' in this else } 
                    */

                    if (!(num instanceof Number)) num = 0; // make a 'Number' object if null or some erroneous value


                    // Data must be adjusted for display use. This is done on a column basis.
                    num = column.applyAdjustments(num, ConfigData.getDefault().getData(ConfigData.Units));
                    num = column.applyDisplayUnits(num, ConfigData.getDefault().getData(ConfigData.Units));

                    dataValue = (Number) num; // create number, thi is what dataset takes
                    
                    if (isPercent) { // convert from fraction to percent
                        dataValue = dataValue.doubleValue() * 100;
                    }

                    // this adjusts the maximum displayed values on graph according to data
                    if(dataValue.doubleValue() > lims.getMax()) lims.setMax(dataValue.doubleValue());
                    if(dataValue.doubleValue() < lims.getMin()) lims.setMin(dataValue.doubleValue());
                    
                    // get the data associated with this data point
                    Object dateObj = currentRow[1]; // index one is where in the row the data string lives
                    
                    String dateString = dateObj instanceof String ? (String) dateObj : "";
                    
                    // get unit abreviation for display
                    ConversionUnit units = column.getDisplayUnits(g_wepsTable.getUnitsSystem());
                    String unitStr = units == null ? "" : units.getAbbreviation();
                    
                    
                    // get legend string and add to dataset
                    dataset.addValue(dataValue, getLegendString(fields[counter], column, unitStr, axis), dateString);
  
                } // end inner for loop
                counter++;
            } // end outer for-each loop
        } catch (Exception e) {
            System.err.println("Error in retrieving raw data for quickplots.");
        }      
    }
    
    /**
     * This code is for filling a dataset with values based off the fields passed in.
     * Separated from charActionPerformed method to reduce copying error.
     * @param dataset
     * @param fields 
     */
    private void fillDataset(DefaultCategoryDataset dataset, final String[] fields, String axis, MinMax lims) { 
        
        for (String field : fields) {
//            TimeSeries series = new TimeSeries(field);
            //fill the series with data

            /* This inner for loop iterates over the number of rows in a data table. Instead of making a nested
            for loop, it just takes the height (rows) checks if its a label row, tests the filter allows the row
            to be added to list (this is ignored by quickplots) and then each data item is added to the dataset.
            */
            for (int row = 0; row < g_wepsTable.getDataView().getNumRows(); row++) {
                //make sure it's not a row label, a row label is a descriptive row, (such as the top 5 our so rows)
                if (g_wepsTable.getDataView().isLabelRow(row)) { // label row holds no data
                    continue;
                }
               
                //test that the row is allowed by the filter in the quick plot
                Object ko = g_wepsTable.getDataView().getObject(row, "key"); 
                String key = ko instanceof String ? (String) ko : null;
                if (!isRowQuickPlot(key)) {
                    continue; // This is only hit when the key is not 'P'?
                }

                // goes to data view and gets data asociated with field. 
                Object no = g_wepsTable.getDataView().getObject(row, field);

                // should be part of the Number class. If null or otherwise, set to 0 (Number)
                if (!(no instanceof Number)) no = 0;
                
                Column column = g_wepsTable.getMeta().getColumn(field);    
                no = column.applyAdjustments(no, ConfigData.getDefault().getData(ConfigData.Units));
                no = column.applyDisplayUnits(no, ConfigData.getDefault().getData(ConfigData.Units));
                Number n = (Number) no;
                if(n.doubleValue() > lims.getMax()) lims.setMax(n.doubleValue());               
                if(n.doubleValue() < lims.getMin()) lims.setMin(n.doubleValue());
                String po = g_wepsTable.getDataView().getObject(row, "sd ed mo yr").toString();
                
                ConversionUnit units = column.getDisplayUnits(g_wepsTable.getUnitsSystem());
                String unitStr = units == null ? "" : units.getAbbreviation();

                dataset.addValue(n, getLegendString(field, column, unitStr, axis), po);
            }

        }
    }
    
    /**
     * This code is for filling a dataset with values based off the fields passed in.
     * Separated from charActionPerformed method to reduce copying error.
     * @param dataset
     * @param fields 
     */
    private void fillSubsets(ArrayList<DefaultCategoryDataset> collection, final String[] fields, String axis)
    {
        if(fields.length != 0)
        {
            String field = fields[0];
            initializeYear(collection, field, axis);
        }
        for (int index = 1; index < fields.length; index ++) {
            //We need to have number indexes, so we can initialize years the first go around
            String field = fields[index];
            int count = 0;
            DefaultCategoryDataset dataset = collection.get(0);
            for (int row = 0; row < g_wepsTable.getDataView().getNumRows(); row++) {
                //make sure it's not a row label
                if (g_wepsTable.getDataView().isLabelRow(row)) {
                    continue;
                }

                //test that the row is allowed by the filter in the quick plot
                Object ko = g_wepsTable.getDataView().getObject(row, "key");
                String key = ko instanceof String ? (String) ko : null;
                
                    if (!isRowQuickPlot(key)) {
                        if("Y".equals(key))
                        {
                            count ++;
                            if(count == collection.size()) continue;
                            dataset = collection.get(count);
                        }
                        continue;
                    }
                
                Object no = g_wepsTable.getDataView().getObject(row, field);

                if (!(no instanceof Number)) no = 0; //If it's a null value, we want to stick it on the axis
                                                    // so we don't get wierd date behavior.
                                    
                Column column = g_wepsTable.getMeta().getColumn(field);
                no = column.applyAdjustments(no, ConfigData.getDefault().getData(ConfigData.Units));
                no = column.applyDisplayUnits(no, ConfigData.getDefault().getData(ConfigData.Units));
                Number n = (Number) no;

                String po = g_wepsTable.getDataView().getObject(row, "sd ed mo yr").toString();

                ConversionUnit units = column.getDisplayUnits(g_wepsTable.getUnitsSystem());
                String unitStr = units == null ? "" : units.getAbbreviation();

                dataset.addValue(n, getLegendString(field, column, unitStr, axis), po);             
            }
        }
    }
    
    private void initializeYear(ArrayList<DefaultCategoryDataset> collection, String field, String axis)
    {
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        for (int row = 0; row < g_wepsTable.getDataView().getNumRows(); row++) {
                //make sure it's not a row label
                if (g_wepsTable.getDataView().isLabelRow(row)) {
                    continue;
                }

                //test that the row is allowed by the filter in the quick plot
                Object ko = g_wepsTable.getDataView().getObject(row, "key");
                String key = ko instanceof String ? (String) ko : null;
                if (!isRowQuickPlot(key)) {
                    if("Y".equals(key))
                    {
                        collection.add(dataset);
                        dataset = new DefaultCategoryDataset();
                    }
                    continue;
                }
                
                Object no = g_wepsTable.getDataView().getObject(row, field);
                
                if (no instanceof Number) { 

                    Column column = g_wepsTable.getMeta().getColumn(field);
                    no = column.applyAdjustments(no, ConfigData.getDefault().getData(ConfigData.Units));
                    no = column.applyDisplayUnits(no, ConfigData.getDefault().getData(ConfigData.Units));
                    Number n = (Number) no;

                    String po = g_wepsTable.getDataView().getObject(row, "sd ed mo yr").toString();

                    ConversionUnit units = column.getDisplayUnits(g_wepsTable.getUnitsSystem());
                    String unitStr = units == null ? "" : units.getAbbreviation();
                    
                    dataset.addValue(n, getLegendString(field, column, unitStr, axis), po);
                }
            }
    }
    
    // helper method to create legend string for dataset.
    private String getLegendString(String field, Column column, String unitStr, String axis) {
        // Get what columns need to turn to percent
        String[] toPercentList = c_qplotFilterSets.get(c_currentPlotIndex).getFractionToPercentIDs(); 
        if (toPercentList != null) {
            for (String f : toPercentList) {
                if (field.equals(f) && unitStr.equals("fraction")) { // don't change if not fraction
                    unitStr = "%";
                }
            }
        } 
        
        String en_or_si;
        try { // dirty but might throw exception, don't know edge cases
            en_or_si = ConfigData.getDefault().getData(ConfigData.Units);
        } catch (Exception e) {
            en_or_si = "";
        }
        // checks if unit string needs to get converted based on US or SI units
        if (en_or_si.equals("US")) {
            Map <String, String> alternate_EN = c_qplotFilterSets.get(c_currentPlotIndex).getFieldsFor_EN_Conversion();
            if (alternate_EN.containsKey(field)) {
                unitStr = alternate_EN.get(field);
            }
        } else if (en_or_si.equals("SI")) {
            Map <String, String> alternate_SI = c_qplotFilterSets.get(c_currentPlotIndex).getFieldsFor_SI_Conversion();
            if (alternate_SI.containsKey(field)) {
                unitStr = alternate_SI.get(field);
            }
        }
        
        // Build String
        if (column.getqplotcol() != null) {
            return column.getqplotcol() + "\n" + unitStr + "\n" + axis;
        } else {
            return field + "\n" + unitStr + "\n" + axis;
        }
    }
    
    /**
     * This code will fill in the first dataset with null values to the right and the second
     * with null values to the right.  It will give names to the null "rows" that start with empty.
     * This will directly mutate dataset, and return the mutated dataset2.
     * @param dataset
     * @param dataset2 
     */
//    private DefaultCategoryDataset nullIn(DefaultCategoryDataset dataset, DefaultCategoryDataset dataset2) {
//        DefaultCategoryDataset temp = new DefaultCategoryDataset();
//        for (int data = 0; data < dataset.getRowCount(); data++) {
//            for (Object column : dataset.getColumnKeys()) {
//                String colStr = "";
//                if (column instanceof String) {
//                    colStr = (String) column;
//                }
//                temp.addValue(null, "emptyL" + data, colStr);
//            }
//        }
//        for (Object row : dataset2.getRowKeys()) {
//            for (Object column : dataset2.getColumnKeys()) {
//                String colStr = "";
//                String rowStr = "";
//                if (column instanceof String) {
//                    colStr = (String) column;
//                }
//                if (row instanceof String) {
//                    rowStr = (String) row;
//                }
//                temp.addValue(dataset2.getValue(rowStr, colStr), rowStr,
//                        colStr);
//            }
//        }
//        for (int data = 0; data < dataset2.getRowCount(); data++) {
//            for (Object column : dataset2.getColumnKeys()) {
//                String colStr = "";
//                if (column instanceof String) {
//                    colStr = (String) column;
//                }
//                dataset.addValue(null, "emptyR" + data, colStr);
//            }
//        }
//        return temp;
//    }

    /*private JRExpression expression(String text, Class c){
     JRDesignExpression result = new JRDesignExpression();
     result.setText(text);
     result.setValueClass(c);
     return result;
     }*/
    
    /**
     * We need this class to pass around the min and max of the datasets
     * That's all it does.
     * 
     * It's a stupid class.
    */
    private class MinMax {

        double min = 0.0;
        double max = 0.0;

        double getMin() {
            return min;
        }

        double getMax() {
            return max;
        }

        void setMax(double input) {
            max = input;
        }

        void setMin(double input) {
            min = input;
        }
    }
    
    public BoundaryBorder instanceofBoundaryBorder(int input)
    {
        return new BoundaryBorder(input);
    }
    
    public BoundaryFinder instanceofBoundaryFinder()
    {
        return new BoundaryFinder();
    }
    
    
    Set<Integer> columnEndings = new HashSet<>();
    
    public class BoundaryBorder extends JCCellBorder
    {
        private static final long serialVersionUID = 1L;
        public BoundaryBorder(int input)
        {
            super(input);
        }
        
        @Override
        public void drawBackground(Graphics gc, int size, int sides,
                           int x, int y, int width, int height, Color top_color,
                           Color bottom_color, Color plain_color)
        {
            super.drawBackground(gc, size, sides, x, y, width, height, top_color, bottom_color, plain_color);
            int end = x + width;
            if(columnEndings.contains(end))
            {
                gc.setColor(Color.BLACK);
                gc.drawLine(x + width - 1, y, x + width - 1, y + height);
            }
        }
    }
    
    public class BoundaryFinder extends JCWordWrapCellRenderer 
    {
        private static final long serialVersionUID = 1L;

        @Override
        public void draw(Graphics gc, JCCellInfo cellInfo, Object o, boolean selected) {
            super.draw(gc, cellInfo, o, selected);
            if(g_wepsTable.getMeta().columnIsBold((String) o))
            {
                Point origin = g_wepsTable.getCellPosition(((JCTableCellInfo) cellInfo).getRow(), ((JCTableCellInfo) cellInfo).getColumn());
                int end = origin.x;
                end += cellInfo.getDrawingArea().x;
                end += cellInfo.getDrawingArea().width;
                end += cellInfo.getBorderInsets().left;
                end += cellInfo.getMarginInsets().left;
                columnEndings.add(end);
            }
        }
    }
    
    private static final String[] borderedFields = { "Wind Erosion", "Mass of Soil"
            + " Passing Indicated Field Boundary", 
            "Within Field", "Weather Information", "Crop/"
            + "Soil Water Information", "Crop Vegetation", "Crop Residue", "Vegetation"
            + " and Residue Biomass", "Average Soil Surface Conditions on Date" };
}