/* ManageData
 *
 * @version 1.1
 *
 * @date 03-25-03
 *
 * @author Sada, Manmohan
 *
 */
package usda.weru.mcrew;

import com.klg.jclass.table.EditableTableDataModel;
import com.klg.jclass.table.data.AbstractDataSource;
import com.klg.jclass.table.JCCellRange;

import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.file.TFileReader;
import de.schlichtherle.truezip.file.TFileWriter;
import de.schlichtherle.truezip.file.TVFS;
import de.schlichtherle.truezip.fs.FsSyncException;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Hashtable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

import javax.swing.JOptionPane;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; 
import org.openide.util.Exceptions;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.TreeWalker;
import usda.weru.util.ConfigData;
import usda.weru.util.Util;

import usda.weru.util.WepsFileTypes;
import usda.weru.weps.Weps;


public class ManageData extends AbstractDataSource implements EditableTableDataModel {

    private static final long serialVersionUID = 1L;

    private static final Logger LOGGER = LogManager.getLogger(ManageData.class);

    public enum WriteFileMode {
        NORMAL,
        UPDATE,
        FROM_NRCS
    }

    List<Integer> initOp;

    public static final String S_START = "START";
    public static final String S_END = "END";
    public static final String S_VERSION = "Version:";
    public static final String S_EOF = "EOF";
    public static final String S_IGNOREVERSION = "IGNORE";
    public static final String S_COMMENTSTR = "#------------";
    
    public static final double VERSION_MINIMUM = 1.40;       //minimum version the code can gracefully load
    public static final double VERSION_FUELADDED = 1.50;
    public static final double VERSION_CURRENT = 1.70;
    public static final double VERSION_ARCHIVE = 1.60;
    
    public static final boolean NRCS_MODE = (ConfigData.checkParmValue("CD-defaultrunmode", "NRCS"));

    //Indicators
    public static final char C_NULL_LINE = '?';
    public static final char C_START_END_LINE = '*';
    public static final char C_VERSION_LINE = 'V';
    public static final char C_MAN_FILE_NOTES = 'N'; //notes section for .man file
    public static final char C_COMMENT_LINE = '#';
    public static final char C_DATE_LINE = 'D';
    public static final char C_OPERATION_LINE = 'O';
    public static final char C_PROCESS_LINE = 'P';
    public static final char C_GROUP_LINE = 'G';
    public static final char C_PARAMETER_LINE = '+'; //indicator for additonal params
    public static final char C_USER_NOTES_LINETEXT = 'T';
    public static final char C_DEV_NOTES_LINETEXT = 't';
    public static final char C_PIVOT_LINE = 'B'; //pivot point - should be first line in NRCS mode
    public static final char C_END_OF_LINE = '<'; //end of line for data
    public static final char C_END_OF_ROW_DATALINE = '-';

    //Constants for reading a file
    public static final int K_SUCCESS = 1;
    public static final int K_SUCCESS_OLD = 2;
    public static final int K_SUCCESS_UPDATED = 69; //read successfully/old version but updated
    public static final int K_CANCELED_UPDATE = -4;
    public static final int K_FAILURE_OLD = -51;
    public static final int K_UNKNOWN_ERROR = -1;
    public static final int K_WRONG_VERSION = -2;
    public static final int K_FILE_NOT_FOUND = -3;
    public static final int K_CORRUPTED_FILE = -666;

    public int kRotationYears = 1;
    public String notestr = ""; //Temp string holds the text for any note lines in .man file
    private static int action_index = 0;
    public String parmstr = ""; // Temp string holds content of any param lines in .man file
    
    private static int mwepsmanfilenoteslines = 0;
    public String mwepsmanfilenotes = ""; //temp string for notes
    
    private static int mwepsusernoteslines = 0;
    public String mwepsusernotes = ""; //temp string operation and crop/residue notes
    
    private static int mwepsdevnoteslines = 0;
    public String mwepsdevnotes = ""; //dev notes

    private static int mwepsparamlines = 0;
    /**
     * The pivot point of the file for use when converting from WEPS View to
     * NRCS View. This is the line number from Rotation View that will become
     * the first line of the Interval view. Defaulted to the first line of MCREW
     * 0 is the first listed operation in the rotation (by calendar year date order).
     */
    private int pivot = 0;
    
    private boolean isInt = false;

    private int order = 0; //The integer value representing the subregion.

    /**
     * Holds an array containing data out of hard limits [row, column]
     */
    private ArrayList<int[]> outHard;
    /**
     * Holds an array containing data out of soft limits [row, column]
     */
    private ArrayList<int[]> outSoft;
    /**
     * The current table status; tells whether saves are allowed.
     */
    private InputLimits.TableStatus allowSave = InputLimits.TableStatus.OKAY;

    private ArrayList<CropIntervalInfo> intervals = null;

    private CropIntervalInfo outlier = null;


    public double version = VERSION_CURRENT;
    private List<RowInfo> mRows;

    private boolean writeOld = false;
    private double oldVersion = -1.0;

    private boolean updated = false;

    public TFile manFile;

    private boolean objectIsUpgm = false;

    public boolean getUpdateStatus() {
        return updated;
    }
    public void setUpdateStatus(boolean b){
     updated = b;
     
    }

    public ManageData() {
        mRows = new ArrayList<>();
        outHard = new ArrayList<>();
        outSoft = new ArrayList<>();
    }

    public int size() {
        return mRows.size();
    }

    public void clear() {
        mRows.clear();
        kRotationYears = 1;
        pivot = 0;
        version = VERSION_CURRENT;
        fireDataReset();
    }

    public List<RowInfo> getRows() {
        return mRows;
    }

    public ArrayList<int[]> getHard() {
        return outHard;
    }

   public ArrayList<int[]> getSoft() {
        return outSoft;
    }

    public InputLimits.TableStatus getSave() {
        return allowSave;
    }

    public RowInfo getRow(int index) {
        if (mRows == null) {
            return null;
        }
        return mRows.get(index);
    }

    /**
     * This method fetches the notes associated with a management file i:e the
     * file level notes on who, what, etc. was modified from previous working
     * copy of the file and what new changes would help in accomplishing the
     * task.
     *
     * @return The text string that is stored as file level notes.
     */
    public String getWepsManFileNotes() {

        return mwepsmanfilenotes;
    }

    public void setWriteVersionToOld(boolean version) {
        writeOld = version;
    }
    public void setWriteVersionToOld(boolean version, double versionNum) {
        writeOld = version;
        oldVersion = versionNum;
    }

    /**
     * Adds a new Row to the hashtable of rows
     *
     * @param pRowNum The place at which the new row would be added.
     * @param pRow RowInfo Object holding the information for that row.
     */
    public void addRow(int pRowNum, RowInfo pRow) {
        clearDeleted();
        mRows.add(pRowNum, pRow);
        fireRowsAdded(pRowNum, 1);
    }

    /**
     * The new date object that will be added to the table for the row numbered
     * pRowNum.
     *
     * @param pRowNum The row to which the new date will be added.
     * @param pDate The date object needed to be set for row pRowNum.
     */
    public void addDate(int pRowNum, JulianCalendar pDate) {
        RowInfo row;

        if (pRowNum >= mRows.size()) {
            row = new RowInfo();
            row.setDisplayDate(pDate);
            insertRow(pRowNum, row);
            return;
        }
        row = mRows.get(pRowNum);
        row.setDisplayDate(pDate);
    }

    /**
     * Fetches the date object associated with the row passed as argument to
     * this method.
     *
     * @param pRowNum The row cropNumber whose date object is being requested.
     * @return Tbe date object associated with the row cropNumber pRowNum
     */
    public JulianCalendar getDisplayDate(int pRowNum) {
        if (pRowNum >= mRows.size()) {
            return new JulianCalendar(01, 00, 01);
        }
        if (pRowNum < 0) {
            return new JulianCalendar(01, 00, 01);
        }
        return (mRows.get(pRowNum)).getDisplayDate();
    }

    /**
     * Fetches the date object associated with the row passed as argument to
     * this method.
     *
     * @param pRowNum The row cropNumber whose date object is being requested.
     * @return Tbe date object associated with the row cropNumber pRowNum
     */
    public JulianCalendar getDate(int pRowNum) {
        if (pRowNum >= mRows.size()) {
            return new JulianCalendar(01, 00, 01);
        }
        if (pRowNum < 0) {
            return new JulianCalendar(01, 00, 01);
        }
        return (mRows.get(pRowNum)).getDate();
    }

    /**
     * Method that is used for changing the dates associated with multiple rows
     * assigning same values to all the row objects sitting in the vector
     * holding the row that needs change of dates.
     *
     * @param scv The vector that holds all the row objects whose date needs to
     * be changed.
     * @param dc The new DateChange object that holds the new values for the
     * date to be set.
     * @return
     */
    public boolean changeDates(List<JCCellRange> scv, DateChange dc) {
        //test only
        for (JCCellRange jccr : scv) {
            int sRow = jccr.start_row;
            int eRow = jccr.end_row;

            if (sRow > eRow) {
                int pivotPoint = sRow;
                sRow = eRow;
                eRow = pivotPoint;
            }
            if (!changeDates(sRow, eRow, dc.type, dc.delta, true)) {
                return false;
            }
        }

        //Actually update
        scv.forEach(jccr -> {
            int sRow = jccr.start_row;
            int eRow = jccr.end_row;

            if (sRow > eRow) {
                int pivotPoint = sRow;
                sRow = eRow;
                eRow = pivotPoint;
            }
            changeDates(sRow, eRow, dc.type, dc.delta);
        });
        return true;
    }

    /**
     * Method that is used for changing the dates associated with multiple rows
     * assigning same values to all the row objects sitting in the vector
     * holding the row that needs change of dates.
     *
     * @param scv The vector that holds all the row objects whose date needs to
     * be changed.
     * @param jc The date object to be assigned to all the rows sitting in the
     * vector scv.
     */
    public void changeDates(List<JCCellRange> scv, JulianCalendar jc) {
        for (JCCellRange jccr : scv) {
            int sRow = jccr.start_row;
            int eRow = jccr.end_row;

            if (sRow > eRow) {
                int pivotPoint = sRow;
                sRow = eRow;
                eRow = pivotPoint;
            }

            for (int rdx = sRow; rdx <= eRow; rdx++) {
                RowInfo ri;
                try {
                    ri = mRows.get(rdx);
                } catch (ArrayIndexOutOfBoundsException e) {
                    System.err.println("ManageData:ChangeDates(List, dateChange): "
                            + " Theres no row " + rdx);
                    continue;
                }
                ri.setDisplayDate((JulianCalendar) jc.clone());
                if (this.isInt && (jc.get(Calendar.YEAR) > kRotationYears)) {
                    ri.setYearOffset(kRotationYears);
                    ri.resynchDates();
                }
                fireRowChanged(ri);
            }
        }
    }

    /**
     * Method that is used for changing the dates associated with multiple rows
     * assigning same values to all the row objects sitting in the vector
     * holding the row that needs change of dates.
     *
     * @param scv The vector that holds all the row objects whose date needs to
     * be changed.
     * @param field The field or table cell which holds the date values which
     * need to be modified.
     * @param value The new value to be assigned as date to the row fields.
     * @return
     */
    public boolean changeDates(List<JCCellRange> scv, int field, int value) {
        //Test only
        for (JCCellRange jccr : scv) {
            int sRow = jccr.start_row;
            int eRow = jccr.end_row;

            if (sRow > eRow) {
                int pivotPoint = sRow;
                sRow = eRow;
                eRow = pivotPoint;
            }

            if (!changeDates(sRow, eRow, field, value, true)) {
                return false;
            }
        }

        //Actually update
        scv.forEach(jccr -> {
            int sRow = jccr.start_row;
            int eRow = jccr.end_row;

            if (sRow > eRow) {
                int pivotPoint = sRow;
                sRow = eRow;
                eRow = pivotPoint;
            }

            changeDates(sRow, eRow, field, value);
        });
        return true;
    }

    public void changeDates(int startRow, int endRow, int field, int value) {
        changeDates(startRow, endRow, field, value, false);
    }

    public boolean changeDates(int startRow, int endRow, int field, int value, boolean testonly) {
        for (int rdx = startRow; rdx <= endRow; rdx++) {
            RowInfo ri;
            try {
                ri = mRows.get(rdx);
            } catch (ArrayIndexOutOfBoundsException e) {
                //No row info, go to the next one
                continue;
            }

            JulianCalendar jd = ri.getDisplayDate();
            jd.add(field, value);
            if (jd.get(JulianCalendar.ERA) < 1 || jd.get(JulianCalendar.YEAR) < 1 || jd.get(JulianCalendar.YEAR) > 99) {
                JOptionPane.showMessageDialog(null, "Invalid rotation year.\nMinimum: 1\nMaximum: 99",
                        "Error changing dates", JOptionPane.ERROR_MESSAGE);
                if (testonly) {
                    jd.add(field, -value);
                }
                return false;
            }

            if (testonly) {
                jd.add(field, -value);
            } else {
                fireRowChanged(ri);
            }
            if (this.isInt && (jd.get(Calendar.YEAR) > kRotationYears)) {
                ri.setYearOffset(kRotationYears);
            }
            ri.resynchDates();
        }
        return true;
    }

    /**
     * This function is not really worth. If Collections.max(mRows) yield null,
     * then it means all dates are null. This is because null date preecedes the
     * valid dates. This is taken care in the compareTo functions of RowInfo
     * class and JulianCalendar class. This function is provided for completion
     * sake as getMinCal() is a required function.
     */
    private JulianCalendar getMaxCal() {

        JulianCalendar maxCal = (Collections.max(mRows)).getDate();

        return maxCal;
    }

    /**
     * This 'min' function on Collections interface ( RowInfo objects implement
     * Comparable Interface) will not yield correct results always because few
     * Rows may not have any Dates at all. Since all the null dates preceded the
     * one with valid dates, if minCal == null, then it means that some of the
     * dates are null, but there can be few rows with valid dates
     */
    private JulianCalendar getMinCal() {
        JulianCalendar minCal = (Collections.min(mRows)).getDate();

        if (minCal != null) {
            return minCal;
        }
        for (RowInfo mRow : mRows) {
            JulianCalendar tempCal = (mRow).getDate();
            if (tempCal != null) {
                if (minCal == null) {
                    minCal = tempCal;
                } else if (minCal.compareTo(tempCal) > 0) {
                    // i.e. tempCal preeceds minCal
                    minCal = tempCal;
                }
            }
        }
        return minCal;
    }

    /**
     * Moves all the dates to "rotYears" year forward making sure they dont
     * cross the maxYear i.e. it just rotates them 1 yr ahead
     *
     * @param rotYears The cropNumber of years after which the rotation of crops
     * occur.
     */
    public void cycleForward(int rotYears) {
        JulianCalendar maxCal = getMaxCal();
        JulianCalendar minCal = getMinCal();

        if ((minCal == null) || (maxCal == null)) {
            return;
        }

        //This means its a one-year rotation, dont do anything.
        if (rotYears == 1) {
            return;
        }
        //	//System.out.println("ManageData:cycleFwd->"+ "Maxyear = "+ maxYear + "MinYear =" +minYear);
        for (int i = 0; i < mRows.size(); i++) {
            JulianCalendar cal = (mRows.get(i)).getDate();
            if (cal == null) {
                //Date is not yet set for this row
                continue;
            }
            int yearValue = cal.get(Calendar.YEAR);
            //	//System.out.println("yearValue:"+yearValue);
            yearValue += 1; // cycle forward one year
            if (yearValue > rotYears) {
                yearValue = yearValue % rotYears;
            }

            cal.set(Calendar.YEAR, yearValue);
            mRows.get(i).enforceActualDate();
            //System.out.println("CycleFwd "+ yearValue);
            fireRowChanged(i);
        }
    }

    /**
     * Moves all the dates by "rotYears" year backward making sure they dont
     * cross the minYear i.e. it just rotates them then to the current yr
     *
     * @param rotYears The cropNumber of years by which the rotation of crops is
     * reduced.
     */
    public void cycleBackward(int rotYears) {
        JulianCalendar maxCal = getMaxCal();
        JulianCalendar minCal = getMinCal();

        if ((minCal == null) || (maxCal == null)) {
            return;
        }

        //This means its a one-year rotation, dont do anything.
        if (rotYears == 1) {
            return;
            //System.out.println("ManageData:cycleBackwd->"+ "Maxyear = "+ maxYear + "MinYear =" +minYear);
        }
        for (int i = 0; i < mRows.size(); i++) {
            JulianCalendar cal = (mRows.get(i)).getDate();
            if (cal == null) {
                // No date is set for this row
                return;
            }
            int yearValue = cal.get(Calendar.YEAR);

            yearValue -= 1; //cycle backward one year
            if (yearValue <= 0) {
                yearValue = rotYears;
            }
            /*
             if(yearValue < minYear)
             yearValue = maxYear - (minYear - yearValue  - 1); //rotate it back
             //if its 1 lesser than minYear, it has to go back to maxYear not maxYear - 1
             */

            cal.set(Calendar.YEAR, yearValue);
            mRows.get(i).enforceActualDate();
            fireRowChanged(i);
        }
    }

    /**
     * This method fetches the value currently assigned to the row pRowNum cell
     * of\ the column with column name pColName.
     *
     * @param pRowNum The row cropNumber whose cell value is being requested for
     * column pColName.
     * @param pObjectName The name of the operation or crop object whose data
     * will
     * @param pColName The name of the column which is usually the tagname from
     * the crop or operation files from where the data is pulled to populate
     * these column cells.
     * @return The value currently assigned to cell of row pRowNum and colum
     * whose name is pColName.
     */
    public String getColumnValueAsString(int pRowNum, String pObjectName, String pColName) {
        Object o = getColumnValue(pRowNum, pObjectName, pColName);
        if (o != null) {
            return o.toString();
        } else {
            return null;
        }
    }

    public Object getColumnValue(int pRowNum, String pObjectName, String pColName) {
        if (pRowNum < 0 || pRowNum >= size()) {
            return null;
        }

        try {

            RowInfo rowInfo = mRows.get(pRowNum);
            if (pObjectName.equals("date")) {
                return rowInfo.getDisplayDate();
            }

            String[] values = rowInfo.getValues(pObjectName, pColName, MCREWConfig.getDisplayUnit());
            if (values == null || values.length == 0) {
                return null;
            } else if (values.length == 1) {
                return values[0];
            } else {
                return values;
            }

        } catch (ArrayIndexOutOfBoundsException e) {
            System.err.println("ManageData:getColumnValue: Array Index out of Bounds, RowNum = " + pRowNum);
        }
        return null;
    }

    /**
     * This method sets new value to the cell whose column name is pColName &
     * row is pRowNum and the object name is pObjectName.
     *
     * @param pRowNum The row cropNumber whose cell value is being requested for
     * column pColName.
     * @param pObjectName The name of the operation or crop object whose data is
     * being reset to a new value
     * @param pColName The name of the column which is usually the tagname from
     * the crop or operation files from where the data is pulled and is inserted
     * in these column cells.
     * @param pValue The new value that needs to be set for the cell whose
     * column name is pColName and row cropNumber is pRowNum.
     */
    public void setColumnValue(int pRowNum, String pObjectName, String pColName, Object pValue) {
        if (pValue == null) {
            return;
        }
        try {
            RowInfo rowInfo = mRows.get(pRowNum);
            if (rowInfo != null) {
                if (pValue instanceof String) {
                    rowInfo.set(pObjectName, pColName, (String) pValue, MCREWConfig.getDisplayUnit());
                } else if (pValue.getClass().isArray()) {
                    rowInfo.setValues(pObjectName, pColName, (String[]) pValue, MCREWConfig.getDisplayUnit());
                }
                fireRowChanged(pRowNum);
            }
        } catch (Exception e) {
            System.err.println("ManageData:setColumnValue:Exception " + e);
        }
    }

    public void fireRowChanged(RowInfo... rows) {
        for (RowInfo row : rows) {
            int i = mRows.indexOf(row);
            fireRowChanged(i);
        }
    }

    /**
     * Get the required Data object of the row at pRowNum
     *
     * @param pRowNum The row cropNumber in the main MCREW table where the data
     * object resides.
     * @param pObjectName The name of the object like the operation or crop
     * names that are assigned to the data object.
     * @return The data object associated with that row that was requested.
     */
    public DataObject getDataObject(int pRowNum, String pObjectName) {
        if (mRows.size() <= pRowNum) {
            return null;
        }
        return (mRows.get(pRowNum)).getDataObject(pObjectName);
    }

    /**
     * Reads a single Operation Object or CropObject and puts it into the given
     * row
     *
     * @param pRowNum The row cropNumber in the main MCREW table where the data
     * object resides.
     * @param pObjectName The name of the object like the operation or crop
     * names that are assigned to the data object whose data is being read.
     * @param pFileName The file from whom the dataobjects are being populated.
     */
    public void readDataObject(int pRowNum, String pObjectName, String pFileName) {
        
        DataObject dataObject = null;
        RowInfo row;

        // -ihaas- Because the column name for the crop/residue/upgm is crop, 
        //         I am going to use a temporary fix by checking the extension
        //         of the file name to create an upgm object.
        switch (pObjectName) {
            case XMLConstants.soperation:
                dataObject = new OperationObject(pObjectName);
                break;
            case XMLConstants.scrop:
                if (getFileExtension(pFileName).equalsIgnoreCase("upgm")){
                    dataObject = new UpgmObject(pObjectName);
                    objectIsUpgm = true;
                }
                else {
                    dataObject = new CropObject(pObjectName);
                }
                break;
        }
        int result = 0;
        if (dataObject != null) {
            result = dataObject.readXMLFile(pFileName);
        }
        if ((result < 0) || (dataObject == null)) {
            System.err.println("ManageData:readDataObject:" + " Data reading unsuccessful for " + pObjectName);
            return;
        }

        if ((pRowNum < 0) || (dataObject == null)) {
            return;
        }
        // -ihaas- If it is an upgm we don't need to check for residue? We will update this in future.
        if (!objectIsUpgm && pObjectName.equals(XMLConstants.scrop)) { 
            OperationObject opObj = (OperationObject) getDataObject(pRowNum, XMLConstants.soperation);
            if (opObj.getAllIds().contains(new Identity(51, "P"))) {
                if (((CropObject) dataObject).getParameter("idc") != null) {
                    if (((CropObject) dataObject).getParameter("idc").getValue().equals("0")) {
                        JOptionPane.showMessageDialog(null, "Unable to insert crop:\n"
                                + "A planting operation cannot use a residue.",
                                "Data Load Error.", JOptionPane.WARNING_MESSAGE);
                        return;
                    }
                }
            }
        }
        if (pRowNum >= mRows.size()) {

            row = null;
            if (pRowNum < 1) {
                row = new RowInfo();
            } else {
                JulianCalendar beforeDate = getDisplayDate(pRowNum - 1);
                row = new RowInfo((JulianCalendar) beforeDate.clone());
                row.setYearOffset(mRows.get(pRowNum - 1).getYearOffset());
            }
            row.addDataObject(pObjectName, dataObject);
            insertRow(pRowNum, row);
        } else {
            row = mRows.get(pRowNum);
            //this is where the new crop is being added into the operation
            if (row != null) {
                
                row.addDataObject(pObjectName, dataObject);
                removeExtraParameters();
                fireRowChanged(pRowNum);
            }
        }
    }
    //Above function taken from ReadData

    /**
     * Reads the data file ( XML or .MAN ) containing the operation or crop data
     * for various crops.
     *
     * @param pFileName The name of the file that contains this data.
     * @return If read successfully returns true else false.
     */

    public int readDataFile(String pFileName) {
    	  // 2nd arg is whether a mgt update has already occurred (or been explicitly rejected)
    	  // If bypassUpdate == true, then skip mgt updating (assumed it was already done previously or we don't want to do it)
    	  // If bypassUpdate == false, then do an update (or at least ask the user via the popup msg)
    	  // 3rd arg determines if the WEPS main interface dropdown selection was used to pick the mgt file (WEPSselectMgtFile == true)
    	  // or if the mgt file is selected from within MCREW (WEPSselectMgtFile == false)
    	  // 3rd arg may also need to distinguish between the main WEPS screen P and T buttons' filechooser selection (WEPSselectMgtFile == true)
    	  // and the filechooser actions within MCREW as well (WEPSselectMgtFile == false)
        //return readDataFile(pFileName, false, false);
        
        int result;
        String s = Weps.getManName();
        //Weps.setManName(pFileName);
        TFile manFileNew = new TFile(new TFile(pFileName).getAbsoluteFile());
//        Weps.setManName(manFileNew.getAbsolutePath());
        if (pFileName.toLowerCase().endsWith(XMLConstants.sXMLFileExtension)) {
            result = readXMLFile(pFileName);
        } else if (pFileName.toLowerCase().endsWith(XMLConstants.sMANXFileExtension)) {
            result = readXMLFile(pFileName);
        } else if (WepsFileTypes.Management.accept(manFileNew) || WepsFileTypes.Rotation.accept(manFileNew)) {
            List<String> filesNames = new ArrayList<>();
            filesNames.add(pFileName);
            result = readManFile(filesNames, Weps.checkOldMan());
            if(result == K_CANCELED_UPDATE){
                
                System.out.println(s);
                return K_CANCELED_UPDATE;
            }
            if (result == K_SUCCESS_UPDATED) {
            	Weps.setOldMan(true);
            	result = readManFile(filesNames, Weps.checkOldMan());
            }            	
            if (result == K_SUCCESS_OLD) {
            	Weps.setOldMan(true);
            	result = readManFile(filesNames, Weps.checkOldMan());
            }         
            if(manFile != null) {
                pFileName = manFile.getAbsolutePath();
            }
            manFileNew = new TFile(new TFile(pFileName).getAbsoluteFile());
        } else {
            return K_UNKNOWN_ERROR;
        }
        
        if (result == K_SUCCESS || result == K_SUCCESS_UPDATED || result == K_SUCCESS_OLD) {
            Weps.setManName(manFileNew.getAbsolutePath());
            manFile = manFileNew;
            fixup();
            fireDataReset();
        }
        return result;
    }
    public int readDataFile(String pFileName, boolean bypassUpdate) {
        return readDataFile(pFileName, bypassUpdate, false);
    }
    
    public int readDataFile(String pFileName, boolean bypassUpdate, boolean WEPSselectMgtFile) {
        LOGGER.info("bypassUdate and WEPSselectMgtFile are: " + bypassUpdate + ", " + WEPSselectMgtFile);
        int result;
        TFile manFileNew = new TFile(new TFile(pFileName).getAbsoluteFile());
        
        if (pFileName.toLowerCase().endsWith(XMLConstants.sXMLFileExtension)) {
            result = readXMLFile(pFileName);
        } else if (pFileName.toLowerCase().endsWith(XMLConstants.sMANXFileExtension)) {
            result = readXMLFile(pFileName);
        } else if (WepsFileTypes.Management.accept(manFileNew) || WepsFileTypes.Rotation.accept(manFileNew)) {
            List<String> filesNames = new ArrayList<>();
            filesNames.add(pFileName);
            result = readManFile(filesNames, bypassUpdate); //NOT THREADSAFE
            if(manFile != null) {
                pFileName = manFile.getAbsolutePath();
            }
            manFileNew = new TFile(new TFile(pFileName).getAbsoluteFile());
        } else {
            return K_UNKNOWN_ERROR;
        }
        
        if (result == K_SUCCESS || result == K_SUCCESS_UPDATED || result == K_SUCCESS_OLD) {
            manFile = manFileNew;
            fixup();
            fireDataReset();
        }
        return result;
    }

    private void fixup() {
        try {
            if (version < VERSION_FUELADDED) {    //1.5
                for (RowInfo row : getRows()) {
                    OperationObject op = (OperationObject) row.getDataObject(XMLConstants.soperation);
                    Action action = op.getOperationAction();
                    if (action.getIdentity().id == 3 || action.getIdentity().id == 4) {
                        action.addParameter(new Parameter("ofuel", " "));
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("Unable to fixup management file by adding fuel parms "
                    + "for this mgt file version (" + version + ")." + manFile.getAbsolutePath(), e);
        }
    }

    private int readXMLFile(String pFileName) {
        int returnCode = K_SUCCESS;
        Document doc;
        DocumentTraversal traversable;
        TreeWalker walker;
        Node root;
        Node node;
        mwepsmanfilenotes = "";
        List<RowInfo> rowList;

        rowList = new ArrayList<>();
        doc = XMLDoc.getDocument(pFileName);
        if (doc == null) {
            mRows = null;
            return K_FILE_NOT_FOUND;
        }
        doc.normalize();

        root = doc.getDocumentElement();
        if (root.getOwnerDocument() == null) {
            traversable = (DocumentTraversal) root;
        } else {
            traversable = (DocumentTraversal) root.getOwnerDocument();
        }
        walker = traversable.createTreeWalker(root, NodeFilter.SHOW_ALL, null, false);

        node = walker.firstChild();
        while (node != null) {
            String nodeName = node.getNodeName();
            if (nodeName.equals(XMLConstants.swepsmanvalue)) {
                RowInfo row = new RowInfo();
                row.initialize(node);

                rowList.add(row);

            } else if (nodeName.equals(XMLConstants.sversion)) {

                try {
                    String versionText = XMLDoc.getTextData(node);
                    versionText = versionText != null ? versionText.trim() : "0.0";
                    version = Double.parseDouble(versionText);
                } catch (NumberFormatException e) {
                    LOGGER.error("Unable to parse management version number", e);
                    version = 0;
                }

                if (version < VERSION_MINIMUM) {
                    LOGGER.error("Management version: " + version + " is less than minimum: " + VERSION_MINIMUM);
                    returnCode = K_WRONG_VERSION;
                }
                if (version > VERSION_CURRENT) {
                    LOGGER.error("Management version: " + version + " is greater than current: " + VERSION_CURRENT);
                    returnCode = K_WRONG_VERSION;
                }
            } else if (nodeName.equals(XMLConstants.srotationyears)) {
                try {
                    kRotationYears = Integer.parseInt(XMLDoc.getTextData(node));
                } catch (NumberFormatException e) {
                    System.err.println("ManageData:readXMLfile()->" + " Rotation years is not a number in the data file");
                    kRotationYears = 1;
                }
            } else if (nodeName.equals(XMLConstants.spivot)) {
                try {
                    pivot = Integer.parseInt(XMLDoc.getTextData(node));
                } catch (NumberFormatException e) {
                    System.err.println("ManageData:readXMLfile()->" + " Pivot could not be converted to int");
                    pivot = 0;
                }
            } else if (nodeName.equals(XMLConstants.swepsmanfilenotes)) {
                try {
                    mwepsmanfilenotes = XMLDoc.getTextData(node);

                    if (mwepsmanfilenotes == null) {
                        mwepsmanfilenotes = "";
                    } else {
                        mwepsmanfilenotes = mwepsmanfilenotes.trim();
                    }
                } catch (Exception e) {
                    System.err.println("ManageData:readXMLfile()->" + " There are NO Management file level notes ");
                    mwepsmanfilenotes = "";
                }
            }

            node = walker.nextSibling();
        } // end while(node!= null)
        mRows = rowList; // Update the meber variable
        return returnCode;
    }

    
    private int writeXMLFile(String pFileName, WriteFileMode mode) {
        Node root;
        String[] nonEscapingElements = {XMLConstants.soperationname, XMLConstants.sname};

        RowInfo rowInfo;

        try {

            Document wepsmanDoc = XMLDoc.createDocument(XMLConstants.smanagement_template);
            /* createDocument function cretes a new Document with appropriate Doctype and stylesheet attached */
            root = wepsmanDoc.getDocumentElement();

            Node versionNode = wepsmanDoc.createElement(XMLConstants.sversion);
            version = VERSION_CURRENT;

            XMLDoc.setTextData(versionNode, String.valueOf(version), wepsmanDoc);
            root.appendChild(versionNode);

            Node rotyearsNode = wepsmanDoc.createElement(XMLConstants.srotationyears);
            XMLDoc.setTextData(rotyearsNode, Integer.toString(kRotationYears), wepsmanDoc);
            root.appendChild(rotyearsNode);

            Node pivotNode = wepsmanDoc.createElement(XMLConstants.spivot);
            XMLDoc.setTextData(pivotNode, Integer.toString(pivot), wepsmanDoc);
            root.appendChild(pivotNode);

            //Write the notes.  We check here for the type of writing we're doing.
            if (mwepsmanfilenotes == null || mwepsmanfilenotes.length() == 0) {
                mwepsmanfilenotes = "";
            } else {
                mwepsmanfilenotes = mwepsmanfilenotes.trim();
            }
            switch (mode) {
                case NORMAL:
                    //Normal write, most likely a save.  We don't add anything to the notes.
                    break;
                case FROM_NRCS:
                    //The file was converted from an NRCS skel file.  We add a line to the notes.
                    Format dateFormat1 = new SimpleDateFormat("MMM dd, yy");
                    mwepsmanfilenotes = mwepsmanfilenotes + "Management Conversion From [NRCS Standard XML Format] on "
                            + dateFormat1.format(new Date()) + "\n";
                    break;
                case UPDATE:
                    Format dateFormat2 = new SimpleDateFormat("MMM dd, yy"); // May need to do internal leap year adjustments if one wants to use the actual 4 digit year in rotation files when we only allow "yy" here - LEW
                    mwepsmanfilenotes = mwepsmanfilenotes + "Management Updated on " + dateFormat2.format(new Date()) + "\n";
                    break;
            }

            Node wepsmanfilenotesNode = wepsmanDoc.createElement(XMLConstants.swepsmanfilenotes);
            XMLDoc.setTextData(wepsmanfilenotesNode, mwepsmanfilenotes.trim(), wepsmanDoc);
            root.appendChild(wepsmanfilenotesNode);

            int totalRows = mRows.size();
            for (int row = 0; row < totalRows; row++) {
                rowInfo = mRows.get(row);

                Node wepsmanvalueNode = rowInfo.getNode(wepsmanDoc);
                root.appendChild(wepsmanvalueNode);
            }

            DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
            DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
            if (impl == null) {
                System.out.println("No DOMImplementation found !");
                return K_UNKNOWN_ERROR;
            }

            LSSerializer serializer = impl.createLSSerializer();
            serializer.getDomConfig().setParameter("format-pretty-print", true);
            LSOutput output = impl.createLSOutput();
            output.setEncoding(XMLConstants.sEncoding);
            output.setCharacterStream(new java.io.FileWriter(pFileName));
            serializer.write(wepsmanDoc, output);
        } catch (DOMException | IOException e) {
            return K_UNKNOWN_ERROR;
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | ClassCastException ex) {
            Exceptions.printStackTrace(ex);
        }
        return K_SUCCESS;
    }

    public int readRotationLength(String pFileName) {
        if (pFileName.toLowerCase().endsWith(XMLConstants.sXMLFileExtension)) {
            return readRotationLengthFromXML(pFileName);
        } 
        else if (pFileName.toLowerCase().endsWith(XMLConstants.sMANXFileExtension)) {
            return readRotationLengthFromXML(pFileName);
        }
        
        BufferedReader br = null;
        Integer years = null;
        try {
            br = new BufferedReader(new TFileReader(new TFile(pFileName)));
            String line = br.readLine();
            //System.out.println("Reading line: " + line);
            while (line != null) {
                if (line.startsWith("*START")) {
                    String[] pieces = line.split(" ");
                    try {
                        years = Integer.parseInt(pieces[1]);
                        //System.out.println("Years in rotation: " + years);
                        kRotationYears = years;
                        return years;
                    } catch (NumberFormatException e) {
                        LOGGER.error("Incorrect year format.", e);
                    }
                } else {
                    line = br.readLine();
                }
            }
        } catch (FileNotFoundException e) {
            LOGGER.warn("File \"" + new TFile(pFileName).getAbsolutePath() + "\" does not exist.");
            return K_FILE_NOT_FOUND;
        } catch (IOException e) {
            LOGGER.warn("Unable to read management file.", e);
            return K_UNKNOWN_ERROR;
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                LOGGER.error("Error reading management file.", e);
            }
        }
        return years;
    }
    
    public int readRotationLengthFromXML(String pFileName) {
        int years = 1;
        Document doc;
        DocumentTraversal traversable;
        TreeWalker walker;
        Node root;
        Node node;

        doc = XMLDoc.getDocument(pFileName);
        if (doc == null) {
            return years;
        }
        doc.normalize();

        root = doc.getDocumentElement();
        if (root.getOwnerDocument() == null) {
            traversable = (DocumentTraversal) root;
        } else {
            traversable = (DocumentTraversal) root.getOwnerDocument();
        }
        walker = traversable.createTreeWalker(root, NodeFilter.SHOW_ALL, null, false);

        node = walker.firstChild();
        
        while (node != null) {
            String nodeName = node.getNodeName();
            if (nodeName.equals(XMLConstants.srotationyears)) {
                years = Integer.parseInt(XMLDoc.getTextData(node));
                break;
            }
            
            node = walker.nextSibling();
        }
        return years;
    }

    public double readVersion(String pFileName) {
        BufferedReader br = null;
        Double tempVersion = null;
        try {
            br = new BufferedReader(new TFileReader(new TFile(pFileName)));
            String line = br.readLine();
            System.out.println("Reading line: " + line);
            while (line != null) {
                if (line.startsWith("Version:")) {
                    String[] pieces = line.split(" ");
                    try {
                        tempVersion = Double.parseDouble(pieces[1]);
                        System.out.println("Man File Version: " + tempVersion);
                        version = tempVersion;
                        return tempVersion;
                    } catch (NumberFormatException e) {
                        LOGGER.error("Incorrect version format.", e);
                    }
                } else {
                    line = br.readLine();
                }
            }
        } catch (FileNotFoundException e) {
            LOGGER.warn("File \"" + new TFile(pFileName).getAbsolutePath() + "\" does not exist.");
            return K_FILE_NOT_FOUND;
        } catch (IOException e) {
            LOGGER.warn("Unable to read management file.", e);
            return K_UNKNOWN_ERROR;
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                LOGGER.error("Error reading management file.", e);
            }
        }
        return tempVersion;
    }
    
    public String updateRestoredManFile(String m_fileName){
        List<String> updatedFiles = null;
        try {
            ArrayList<String> manFiles = new ArrayList<>();
            manFiles.add(m_fileName);
            TFile fs = new TFile(MCREWConfig.getDirectoryName(XMLConstants.smanagement_skeleton));
            if ((!fs.exists()) || (!fs.isDirectory())) {
                JOptionPane.showMessageDialog(null, "Skeleton directory is not valid: \n\n"
                        + MCREWConfig.getDirectoryName(XMLConstants.smanagement_skeleton),
                        "Error", JOptionPane.INFORMATION_MESSAGE);
                return null;
            }
            //Give the doUpdate() function the vector containing the names of the man files to be updated.
            XMLManagementUpdater updateMan = new XMLManagementUpdater();
            //updateMan.doUpdate(null, manFiles);
            
            
            System.out.println("File to update: " + m_fileName);
//            if(m_fileName.contains("Project.wpj")) {
                System.out.println("Doing update in-place");
                updatedFiles = updateMan.doUpdateWaitInPlace(null, manFiles);
//            } else {
//                System.out.println("Doing update with move");
//                updatedFiles = updateMan.doUpdateWait(null, manFiles);
//            }
            TVFS.umount();
        } catch (FsSyncException ex) {
            //java.util.logging.LogManager.getLogger(TablePanel.class.getName()).log(Level.SEVERE, null, ex);
            LOGGER.error(ex);
        }
        String retVal = "";
        if (updatedFiles != null && updatedFiles.size() > 0) {
            retVal = updatedFiles.get(0);
        }
        return retVal;
    }

    public String updateManFile(String m_fileName) {
        List<String> updatedFiles = null;
        try {
            ArrayList<String> manFiles = new ArrayList<>();
            manFiles.add(m_fileName);
            TFile fs = new TFile(MCREWConfig.getDirectoryName(XMLConstants.smanagement_skeleton));
            if ((!fs.exists()) || (!fs.isDirectory())) {
                JOptionPane.showMessageDialog(null, "Skeleton directory is not valid: \n\n"
                        + MCREWConfig.getDirectoryName(XMLConstants.smanagement_skeleton),
                        "Error", JOptionPane.INFORMATION_MESSAGE);
                return null;
            }
            //Give the doUpdate() function the vector containing the names of the man files to be updated.
            XMLManagementUpdater updateMan = new XMLManagementUpdater();
            //updateMan.doUpdate(null, manFiles);
            System.out.println("File to update: " + m_fileName);
            
//            System.out.println((Util.getProperty("project.directory")+ File.separator+m_fileName.substring(m_fileName.lastIndexOf(".wjr")+5)).equals(m_fileName));
//            System.out.println(m_fileName.contains("Project.wpj"));
//m_fileName.contains(Util.getProperty("project.directory")))&&
            TFile tempRoot = new TFile(m_fileName);
            String m_parent = tempRoot.getParent();
 //           if((m_fileName.equals(Util.getProperty("project.directory")))){  //LEW - Should be using m_fileNamePath here
           // if((m_fileName.startsWith(Util.getProperty("project.directory")))){
            if((m_parent.equals(Util.getProperty("project.directory")))){
                System.out.println("Doing update in-place");
                updatedFiles = updateMan.doUpdateWaitInPlace(null, manFiles);
            }else{
                System.out.println("Doing update with move");
                updatedFiles = updateMan.doUpdateWait(null, manFiles);
            }
//            if(m_fileName.contains("Project.wpj")) {
//                System.out.println("Doing update in-place");
//                updatedFiles = updateMan.doUpdateWaitInPlace(null, manFiles);
//            } else {
//                System.out.println("Doing update with move");
//                updatedFiles = updateMan.doUpdateWait(null, manFiles);
//            }
            TVFS.umount();
        } catch (FsSyncException ex) {
            //java.util.logging.LogManager.getLogger(TablePanel.class.getName()).log(Level.SEVERE, null, ex);
            LOGGER.error(ex);
        }
        String retVal = "";
        if (updatedFiles != null && updatedFiles.size() > 0) {
            retVal = updatedFiles.get(0);
        }
        return retVal;
    }
    
    //EL - not threadsafe
    private int readManFile(List<String> names, boolean bypassUpdate) {
        String pFileName = names.get(0);
        int returnCode = K_SUCCESS;
        int readmanfileCount = 0;
        List<RowInfo> rowList = null;
        int rowCount = 0;
        OperationObject operationObject = null;
        RowInfo row = null;
        String dataLine;
        char code;
        String delimiter = " "; // delimiter is single space
        String actionCode = null;
        String actionId = null;
        Action action = null;
        mwepsmanfilenotes = "";
        mwepsusernotes = "";
        mwepsdevnotes = "";

        // intialize
        if (Weps.checkOldMan()) {
            bypassUpdate = true;
        }
        Weps.setOldMan(false);
                                        
        StringTokenizer tokenizer;
        boolean EOF = false;
        BufferedReader br = null;
        try {
            Path path = Paths.get(pFileName);
            br = new BufferedReader(new TFileReader(new TFile(path.toFile())));
            dataLine = br.readLine();
            readmanfileCount++;
            LOGGER.info("(1) MCREW file: " + pFileName + " Mgt line_no is: " + readmanfileCount + " Line is: [" + dataLine + "]");

            while (dataLine != null) {
                
                // Safety check for a blank line.
                // This is essentially making blank lines with no initial char in them become comment lines.
                if (dataLine.length() < 1) {
                    code = C_COMMENT_LINE;  // Set to comment character and continue
                } else {
                    code = dataLine.charAt(0); // get the first char in the line
                }

                if ((code != C_MAN_FILE_NOTES) && (mwepsmanfilenoteslines > 0)) { // Check to see if we've completed reading a set of management file note (T) lines
                    mwepsmanfilenoteslines = 0;  // reset management file notes line counter
                }
                // "action.initialize()" all user note lines as a single entity here
                if ((code != C_USER_NOTES_LINETEXT) && (mwepsusernoteslines > 0)) { // Check to see if we've completed reading a set of user note (T) lines
                    if (action != null){
                        action.initialize(mwepsusernotes, action_index++); // Since we've finished concatenating the user notes, initialize the data and increment index counter
                    }
                    mwepsusernoteslines = 0;  // reset user notes line counter
                }
                // "action.initialize()" all dev note lines as a single entity here
                if ((code != C_DEV_NOTES_LINETEXT) && (mwepsdevnoteslines > 0)) { // Check to see if we've completed reading a set of dev note (t) lines
                    if (action != null){
                        action.initialize(mwepsdevnotes, action_index++);  // Since we've finished concatenating the dev notes, initialize the data and increment index counter
                    }
                    mwepsdevnoteslines = 0;  // reset dev notes line counter
                }
                // All Parameter lines are "action.initialized" line by line elsewhere, just clear the line counter(s) here
                if ((code != C_PARAMETER_LINE) && (mwepsparamlines > 0)) { // Check to see if we've completed reading a set of parameter (+) lines
                    mwepsparamlines = 0;  // reset parameters line counter
                }

                switch (code) {

                    case C_START_END_LINE:  // "*" - Read the "*Start xx", "*End" and "*EOF" lines - "Start" line contains length of rotation in years
                        if (dataLine.toLowerCase().startsWith("*" + S_EOF.toLowerCase())) { // "*EOF" line
                            EOF = true;
                            dataLine = null;
                            break;
                        }
                        if (dataLine.toLowerCase().startsWith("*" + S_START.toLowerCase())) { // "*START" line (with rotation length in yrs)
                            rowList = new ArrayList<>(); // What is the purpose of this line? Can/should this initialization be moved elsewhere? - LEW
                            // Get the number of rotation years value
                            int rotationIndex = dataLine.toLowerCase().indexOf(S_START.toLowerCase()) + S_START.length();
                            String rotationStr = dataLine.substring(rotationIndex).trim();
                            try {
                                kRotationYears = Integer.parseInt(rotationStr);
                            } catch (NumberFormatException nfe) {
                                LOGGER.error(rotationStr + " Unable to parse and/or convert mgt rotation length string to an integer" + "[" + rotationStr + "]");
                            }
                            break;
                        }
                        // The "*END" line was originally intended to signal the end of a single mgt rotation sequence.
                        // However, multiple rotation sequences in a single file was never implemented,
                        // since we ultimately chose to have multiple single rotation sequence mgt files
                        // rather than multiple rotation sequences specified in a single mgt file. - LEW
                        if (dataLine.toLowerCase().contains(S_END.toLowerCase())) {  // "END" line
                            // Not sure why this check is here. The code should never reach this point with the "*" first char lines (only empty mgt file?) - LEW
                            // I think it is created to allow MCREW to always start with and contain any empty row (at the bottom of the table) in its table view
                            //check if there has been any previous operationObject (and thus "row") added
                            if (operationObject != null) {
                                row.addDataObject(operationObject.getObjectName(), operationObject);
                                rowList.add(row);
                                rowCount++;
                                operationObject = null;
                            }
                            mRows = rowList; // Final count (total) of rows, since we have read the entire mgt file - LEW
                            break;
                        }
                        break; // Should never get here - lEW

                    case C_VERSION_LINE:    // "V" - Read the management file version value
                        int versionIndex = dataLine.toLowerCase().indexOf(S_VERSION.toLowerCase()) + S_VERSION.length();
                        String versionText = dataLine.substring(versionIndex).trim();
                        try {
                            version = Double.parseDouble(versionText);
                        } catch (NumberFormatException e) {
                            LOGGER.error("Unable to parse and/or convert the management file version number string into a double " + "[" + versionText + "]", e);
                            version = 0;
                        }

                        // Must be a version between VERSION_MINIMUM and VERSION_CURRENT, or we can't deal with it
                        if (version < VERSION_MINIMUM) {
                            LOGGER.error("Management file version " + version + " is less than minimum version: " + VERSION_MINIMUM);
                            returnCode = K_WRONG_VERSION;
                            break;
                        }
                        if (version > VERSION_CURRENT) {
                            LOGGER.error("Management file version " + version + "is greater than current version: " + VERSION_CURRENT);
                            returnCode = K_WRONG_VERSION;
                            break;
                        }

                        // Check to see if mgt file is up to date and what to do if it isn't, based upon special flags, etc.
                        if (version < VERSION_CURRENT) {  // Current version is 1.7 at this time
                            LOGGER.warn("Management file version being loaded is not current. " + "Mgt file version is: " + version + " Current version is: " + VERSION_CURRENT);

                            if (bypassUpdate == true) { //This flag is for the WEPS multiple run manager function - We don't want to auto-update mgt files when reading mgt files for WMRM display
                                LOGGER.warn("Bypassing man file update.");
                                returnCode = K_SUCCESS_OLD;
                                Weps.setOldMan(true);
                                break;
                            }

                            // Check to see if we are displaying the auto-update mgt file message or not
                            boolean displayChoice = ConfigData.checkParmValue(ConfigData.oldMessageDisplay, "1");
                            if (displayChoice == false) { // Not displaying the message to the user and will do whatever has been configured as the default response
                                LOGGER.warn("Using default man file behavior.");
                                // skip user input, use default
                                Integer defaultBehavior = ConfigData.getIntParm(ConfigData.oldMessageDefault, 1);
                                switch (defaultBehavior) {
                                    case 0: // Load old mgt file as is
                                        returnCode = K_SUCCESS_OLD;
                                        Weps.setOldMan(true);
                                        break;
                                    case 1: // Auto-update old mgt file and load updated file
                                        returnCode = K_SUCCESS_UPDATED;
                                        break;
                                    case 2: // Cancel loading or updating the selected mgt file
                                        returnCode = K_FAILURE_OLD;
                                        break;
                                }
                            } else { // Providing user with auto-update mgt file message for them to select what to do

                                if (Weps.checkOldMan()) {
                                    
                                    return K_SUCCESS_OLD;
                                }
                                Integer availableOptions = ConfigData.getIntParm(ConfigData.oldMessageOptions, 1);
                                if (availableOptions == 0) { // get user input, NRCS options (auto-update or cancel loading)
                                    Object[] options = {"Update Man File", "Cancel Loading"};
                                    int loadOption = JOptionPane.showOptionDialog(null,
                                            "Management file is out-of-date. \nTo bring all operation and crop/residue records in selected WEPS"
                                                    + " \nmgt file with current CRLMOD data, select \"Update Man File\"",
                                            "Warning: Out of Date Management File",
                                            JOptionPane.YES_NO_CANCEL_OPTION,
                                            JOptionPane.QUESTION_MESSAGE,
                                            null,
                                            options,
                                            options[0]
                                    );
                                    if (loadOption == 0) { // Auto-update old mgt file and load updated file
                                        returnCode = K_SUCCESS_UPDATED;
                                    } else { // Cancel loading or updating the selected mgt file
                                        returnCode = K_FAILURE_OLD;
                                    }
                                } else { // get user input (load old mgt file as is, auto-update or cancel loading)
                                    int loadOption = 0;
                                    if(bypassUpdate == false){
                                        Object[] options = {"Load As Is", "Update Man File", "Cancel Loading"};
                                        loadOption = JOptionPane.showOptionDialog(null,
                                                "Management file is out-of-date. \nTo bring all operation and crop/residue records in selected WEPS"
                                                        + " \nmgt file with current CRLMOD data, select \"Update Man File\". "
                                                        + "\nTo load with current data, select \"Load As Is\".", "Warning: Out of Date Management File",
                                                        JOptionPane.YES_NO_CANCEL_OPTION,
                                                        JOptionPane.QUESTION_MESSAGE,
                                                        null,
                                                        options,
                                                        options[0]
                                        );
                                    }
                                    switch (loadOption) {
                                        case 0: // Load old mgt file as is
                                            returnCode = K_SUCCESS_OLD;
                                            Weps.setOldMan(true);
                                            break;
                                        case 1: // Auto-update old mgt file and load updated file
                                            returnCode = K_SUCCESS_UPDATED;
                                            break;
                                        case 2: // Cancel loading or updating the selected mgt file
//                                            returnCode = K_FAILURE_OLD;
                                            returnCode = K_CANCELED_UPDATE;
                                            break;
                                    }
                                }
                            }
                            if (returnCode == K_SUCCESS_UPDATED) {
                                pFileName = updateManFile(pFileName);
                            }
                            if (returnCode == K_WRONG_VERSION) {
                                LOGGER.error("Management file version: " + version + " is either less than minimum version: " + VERSION_MINIMUM + "or is greater than current version: " + VERSION_CURRENT);
                            }
                        }
                        break;

                    case C_MAN_FILE_NOTES:  // "N" - Read the management file notes content
                        if (dataLine.length() < 2) { // Ensure that we have a valid line of "data" - we can cheat though and make it a "blank" line
                            notestr = "";
                        } else {
                            notestr = dataLine.substring(2);
                        }
                        try {
                            if (mwepsmanfilenoteslines == 0) {
                                mwepsmanfilenotes = notestr;
                                mwepsmanfilenoteslines++; // cManNotesLineText line counter
                            } else {
                                mwepsmanfilenotes = mwepsmanfilenotes + "\n" + notestr;
                                mwepsmanfilenoteslines++; // cManNotesLineText line counter
                            }
                        } catch (Exception e) {
                            mwepsmanfilenotes = ""; // Clear out the manfilenotes str
                        }
                        break;

                    case C_USER_NOTES_LINETEXT:  // "T" - Read the management file operations and crops/residue record user notes (T) content
                        // Ensure that we have a valid line of "data"
                        if (dataLine.length() < 2) {
                            notestr = "";
                        } else {
                            notestr = dataLine.substring(2);
                        }
                        try {
                            if (mwepsusernoteslines == 0) {
                                mwepsusernotes = notestr;
                                mwepsusernoteslines++; // cUserNotesLineText line counter
                            } else {
                                mwepsusernotes = mwepsusernotes + "\n" + notestr;
                                mwepsusernoteslines++; // cUserNotesLineText line counter
                            }
                        } catch (Exception e) {

                            mwepsusernotes = ""; // Clear out the usernotes str
                        }
                        break;

                    case C_DEV_NOTES_LINETEXT:  // "t" - Read the management file operations and crops/residue record development notes (t) content
                        // Ensure that we have a valid line of "data"
                        if (dataLine.length() < 2) {
                            notestr = "";
                        } else {
                            notestr = dataLine.substring(2);
                        }
                        try {
                            if (mwepsdevnoteslines == 0) {
                                mwepsdevnotes = notestr;
                                mwepsdevnoteslines++; // cDevNotesLineText line counter
                            } else {
                                mwepsdevnotes = mwepsdevnotes + "\n" + notestr;
                                mwepsdevnoteslines++; // cDevNotesLineText line counter
                            }
                        } catch (Exception e) {
                            mwepsdevnotes = ""; // Clear out the devnotes str
                        }
                        break;

                    case C_COMMENT_LINE:  // "#" - Skip the comment lines
                        // nothing to do with the comment line
                        break;

                    case C_PIVOT_LINE:  // "B" - pivot point line
                        // Ensure that we have a valid line of "data"
                        if (dataLine.length() < 3) {
                            pivot = 0;
                        } else {
                            String[] parts = dataLine.split("|");
                            try {
                                pivot = Integer.parseInt(parts[2]);
                            } catch (NumberFormatException nfe) {
                                //("Incorrectly formatted pivot line in management file." + pFileName);
                            }
                        }
                        break;

                    case C_DATE_LINE:  // "D" - Read the "date" line in "dd/mm/yy" format
                        //check if there has been any previous row
                        if (operationObject != null) {
                            row.addDataObject(operationObject.getObjectName(), operationObject);
                            rowList.add(row);
                            rowCount++;
                            operationObject = null;
                        }
                        row = new RowInfo();
                        tokenizer = new StringTokenizer(dataLine.substring(1), delimiter); //leave out the first char
                        String date = null;
                        if (tokenizer.hasMoreElements()) {
                            date = tokenizer.nextToken();
                        }

                        try {
                            row.setDate(date);
                        } catch (Exception e) {
                            returnCode = K_CORRUPTED_FILE;
                        }

                        operationObject = new OperationObject();
                        break;

                    case C_OPERATION_LINE: // "O"
                        String operationName = null;
                        action_index = 0;  // Initialize the index ptr for the new "action" here for subsequent parameter and/or notes entries
                        try {
                            if (dataLine.length() < 6) {
                                LOGGER.error("ManageData:readManFile: " + "Operation line is short the necessary 6 characters: " + dataLine.length() + " [" + dataLine + "]");
                                returnCode = K_CORRUPTED_FILE;
                                break;
                            } else {
                                actionCode = dataLine.substring(0, 1);
                                // A string tokenizer cannot be used as there is no delimiter.
                                // Space cannot be used as operation and action names cannot contain spaces.
                                actionId = dataLine.substring(2, 4);
                                operationName = dataLine.substring(5).trim(); // Every line has '<' at the end (doesn't appear to be doing what one would expect here, based on the original comment - LEW)
                            }
                        } catch (IndexOutOfBoundsException e) {
                        }
                        operationObject.setOperationName(operationName);
                        /* Note: the 'action' is not yet initialized with values.
                         * Action parameter(s) is initialized when you get the next Parameter (and/or user/dev notes) Line.
                         * This takes care of actions with no parameters as the new action line
                         * would be initialized before next parameter (+) line.
                         */
                        action = new Action(actionId, actionCode);
                        if (operationObject != null) {
                            operationObject.addAction(action);
                        }
                        break;
                    case C_PROCESS_LINE: // "P"
                    case C_GROUP_LINE: // "G"
                        action_index = 0;  // Initialize the index ptr for the new "action" here for subsequent parameter and/or notes entries
                        try {
                            if (dataLine.length() < 6) {
                                LOGGER.error("ManageData:readManFile: " + "Process or Group line is short the necessary 6 characters: " + dataLine.length() + " [" + dataLine + "]");
                                returnCode = K_CORRUPTED_FILE;
                                break;
                            } else {
                                actionCode = dataLine.substring(0, 1);
                                // A string tokenizer cannot be used as there is no delimiter.
                                // Space cannot be used as operation and action names cannot contain spaces.
                                actionId = dataLine.substring(2, 4);
                            }
                        } catch (IndexOutOfBoundsException e) {  // Hopefully it won't get in here anymore with the line length check above
                            LOGGER.error("ManageData:readManFile: " + "Process or Group Line: " + "[" + dataLine + "] is shorter than normal");
                        }

                        // Note: the 'action' is not yet initialized with values.
                        // Action parameter(s) is initialized when you get the next Parameter (and/or user/dev notes) Line.
                        // This takes care of actions with no parameters as the new action line 
                        // would be initialized before the next parameter (+) line.
                        action = new Action(actionId, actionCode);
                        if (operationObject != null) {
                            operationObject.addAction(action);
                        } else {
                            LOGGER.error("ManageData:readManFile: " + "Process or Group line missing initialized operationObject: " + dataLine.length() + " [" + dataLine + "]");
                            returnCode = K_CORRUPTED_FILE;
                            break;
                        }
                        break;

                    case C_PARAMETER_LINE: // "+" - Read the management operation/process/group parameters
                        mwepsparamlines++;  // cParameterLine line counter
                        // Ensure that we have a valid line of "data" (we don't need to concatenate the parameter values like we do with the "notes" data)
                        if (dataLine.length() < 3) {
                            // We seem to have a lot of CRLMOD data files that trip this line check - LEW
                            parmstr = ""; // Set to blank line if this occurs and continue
                        } else {
                            parmstr = dataLine.substring(2).trim();
                        }
                        try {
                            if (action != null) {
                                List<List<String>> lineInfo = MCREWConfig.getManFileFormatInfo(action.getIdentity(), 0);
                                if (lineInfo != null && lineInfo.size() > 0) {
                                    String expectedLineCode = lineInfo.get(0).get(0);
                                    if (expectedLineCode.equals("+")) {
                                        action.initialize(parmstr, action_index++);  // initialize param(s) line and increment index for next param(s) and/or notes lines
                                    }
                                }
                            } else {
                                LOGGER.error("ManageData:readManFile: " + "Parameter line missing initialized operationObject: " + dataLine.length() + " [" + dataLine + "]");
                                returnCode = K_CORRUPTED_FILE;
                                break;
                            }
                        } catch (Exception e) {
                        }
                        break;

                    case 's': // I don't think we can get here anymore - LEW
                        JOptionPane.showMessageDialog(null,
                                "Warning: There is a blank line in the management file\n" + pFileName,
                                "Blank Line Warning",
                                JOptionPane.WARNING_MESSAGE);
                        break;
                    default:
                        if (returnCode != K_WRONG_VERSION && returnCode != K_SUCCESS_UPDATED) {
                            LOGGER.error("ManageData:readManFile: mgt file is corrupt: " + "Return code is: " + returnCode);
                            returnCode = K_CORRUPTED_FILE;
                        }
                        break;
                }
                if ((returnCode == K_FAILURE_OLD) || (returnCode == K_WRONG_VERSION) || (returnCode == K_CORRUPTED_FILE)) {
                    break;
                } else if (returnCode == K_SUCCESS_UPDATED) {
                    // if updated, stop reading.
                    // RunFileData will change the man name and cause the new file to be loaded.
                    break;
                } else if (EOF != true) {
                    dataLine = br.readLine();
                    readmanfileCount++;
                    //LOGGER.debug("(2) MCREW file: " + pFileName + " Mgt line_no is: " + readmanfileCount + " Line is: [" + dataLine + "]");
                }
            }
            mwepsmanfilenoteslines = 0;
            mwepsusernoteslines = 0;
            mwepsdevnoteslines = 0;
            mwepsparamlines = 0;

        } catch (FileNotFoundException e) { // END TRY
            LOGGER.warn("ManageData:readManFile: File \"" + new TFile(pFileName).getAbsolutePath() + "\" does not exist.");
            return K_FILE_NOT_FOUND;
        } catch (IOException e) {
            LOGGER.warn("ManageData:readManFile: Unable to read management file.", e);
            return K_UNKNOWN_ERROR;
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                LOGGER.error("ManageData:readManFile: Error reading management file.", e);
            }
        }

        if (returnCode == K_SUCCESS || returnCode == K_SUCCESS_UPDATED || returnCode == K_SUCCESS_OLD) {
            mRows = rowList;
            Path ppath = Paths.get(pFileName).getParent();
            //if (!pFileName.contains(Util.getProperty("project.directory"))) {
            //if (ppath.toString() != Util.getProperty("project.directory")) {
            if (!ppath.toString().equals(Util.getProperty("project.directory"))) {
                pFileName = updatePath(pFileName);
            }
            manFile = new TFile(new TFile(pFileName).getAbsoluteFile());
            if (returnCode == K_SUCCESS_UPDATED) { //only done if file was updated
                System.out.println("Post Update: Setting management file to\n" + pFileName);
            }
        }
        return returnCode;
    }
    
    private String updatePath(String oldPath) {
        String name = "";
        // System.out.println("oldPath: " + oldPath);
        if(name.length()  < 2) {
            if (Util.isWindows()) { /* os = "Windows"; */
                // System.out.println("oldPath.substring (Windows): " + oldPath.substring(oldPath.lastIndexOf("\\")));
                name = oldPath.substring(oldPath.lastIndexOf("\\"));
            }
            else { /* os = "Linux"; */
                // System.out.println("oldPath.substring (Linux): " + oldPath.substring(oldPath.lastIndexOf("/")));
                name = oldPath.substring(oldPath.lastIndexOf("/"));
            }
        }
        //System.out.println("Separated file name: " + name);
        String projDir = Util.getProperty("project.directory");
        //System.out.println("projDir: " + projDir);
        return projDir + name;
    }
    
    /**
     * If the parameter pFileName has no extension then this function calls save
     * function for saving both xml and man files. If the fileName already has
     * some extension then it just saves for that file
     *
     * @param pFileName The name of the file where the data needs to be written.
     * @return True if the data file is written successfully else false.
     */
    public int writeDataFile(String pFileName) {
        return writeDataFile(pFileName, WriteFileMode.NORMAL);
    }

    public int writeDataFile(String pFileName, WriteFileMode mode) {
        //Safety Check Build directory structure for the file if needed.
        TFile dir = new TFile(pFileName).getParentFile();
        if (!dir.exists()) {
            boolean created = dir.mkdirs();
            if (!created) {
                LOGGER.error("Unable to create required directory structure: " + dir.getAbsolutePath());
                return K_UNKNOWN_ERROR;
            }
        }
        
        //-ihaas- check if we are in upgm mode.
        if (ConfigData.checkParmValue("CD-upgm-manx", "1")) {
            // In upgm mode users can open old man files that we want to convert to manx
            return writeXMLFile(changeFileExtensionToMANX(pFileName), mode);
        }
        else {
            return writeManFile(pFileName, mode);
        }
    }

    private int writeManFile(String pFileName, WriteFileMode mode) {
        String dataLine;

        String symbolParameterLine = Character.valueOf(C_PARAMETER_LINE).toString();
        String symbolUserNotesLineText = Character.valueOf(C_USER_NOTES_LINETEXT).toString();
        String symbolDevNotesLineText = Character.valueOf(C_DEV_NOTES_LINETEXT).toString();

        try (PrintWriter pw = new PrintWriter(new BufferedWriter(new TFileWriter(new TFile(pFileName))))) {
            dataLine = S_VERSION;

            //always write the current version
            if (writeOld) {
                if(oldVersion != -1.0){
                    System.out.println("Version " + oldVersion+  " selected");
                    version = oldVersion;
                }else{
                    System.out.println("Version 1.6 selected");
                    version = VERSION_ARCHIVE; 
                }
                
            } else {
                System.out.println("Version 1.7 selected");
                version = VERSION_CURRENT;
            }

            dataLine = prettyConcat(dataLine, String.valueOf(version));
            pw.write(dataLine);
            pw.write("\n");

            dataLine = ((Character) C_START_END_LINE).toString().concat(S_START);
            dataLine = prettyConcat(dataLine, Integer.toString(kRotationYears));
            pw.write(dataLine);
            pw.write("\n");

            //Write the notes.  We check here for the type of writing we're doing.
            if (mwepsmanfilenotes == null || mwepsmanfilenotes.length() == 0) {
                mwepsmanfilenotes = "";
            } else {
                mwepsmanfilenotes = mwepsmanfilenotes + "\n";
            }
            switch (mode) {
                case NORMAL:
                    //Normal write, most likely a save.  We don't add anything to the notes.
                    break;
                case FROM_NRCS:
                    //The file was converted from an NRCS skel file.  We add a line to the notes.
                    mwepsmanfilenotes = mwepsmanfilenotes
                            + "Management file originally converted from NRCS standard XML format on - "
                            + new Date().toString();
                    break;
                case UPDATE:
                    mwepsmanfilenotes = mwepsmanfilenotes
                            + "Management file updated to contain most current crop "
                            + "and operation records for WEPS on - " + new Date().toString();
                    break;
            }

            dataLine = ((Character) C_MAN_FILE_NOTES).toString();
            dataLine = prettyConcat(dataLine, XMLDoc.getXMLSafeString(mwepsmanfilenotes));
            pw.write(dataLine);
            pw.write("\n");

            dataLine = S_COMMENTSTR;
            pw.write(dataLine);
            pw.write("\n");

            pw.write("B|");
            pw.write(Integer.toString(pivot));
            pw.write("\n");
            int numRows = mRows.size();
            for (int rowidx = 0; rowidx < numRows; rowidx++) {

                RowInfo row = mRows.get(rowidx);

                String date = row.getDate().toString();
                dataLine = ((Character) C_DATE_LINE).toString();
                dataLine = prettyConcat(dataLine, date);

                //System.out.println(dataLine + " Its my Date inserted");
                pw.write(dataLine);
                pw.write("\n");

                //Only operation objects are existing in any row at present
                OperationObject operationObj = (OperationObject) row.getDataObject(XMLConstants.soperation);

                String operationName = operationObj.getOperationName();
                //System.out.println("ManageData:writemanfile()->" + "Writing out parameters for operation: "
                //+ operationName + " dtd " + date);
                List<Identity> actionIdVec = operationObj.getAllIds();
                //Hashtable actions = operationObj.getAllActions();
                int actionVecSize = actionIdVec.size();

                ////System.out.println("cropNumber of action " + actionVecSize);
                //First get the operation action
                for (int actionidx = 0; actionidx < actionVecSize; actionidx++) {
                    Action action = operationObj.getAction(actionidx);

                    Identity actionId = action.getIdentity();
                    String actionName = action.getActionName();
                    //System.out.println("ManageData:writemanfile() "+  "There actionname IS : " + actionName
                    //+ " - IDENTITY is : " + actionId.toString());
                    if (actionName == null) {
                        System.err.println("ManageData:writemanfile()->" + "Theres no actionname for action "
                                + actionId + "in operation_defn.xml");
                        actionName = XMLConstants.sNoName;
                    }
                    dataLine = actionId.toString();
                    /* Note: Operation name is to be appended with first operation action.
                     All other actions (process and group) will be
                     appended with their action name.
                     */
                    if (actionidx == 0) {
                        dataLine = prettyConcat(dataLine, operationName);
                    } else {
                        dataLine = prettyConcat(dataLine, actionName);
                    }
                    pw.write(dataLine);
                    pw.write("\n");

                    /* Get the information from man_fileformat.xml on how to print the data in the .man file */
                    // get the cropNumber of lines in which the params are divided
                    int numLines = MCREWConfig.getManFormatNumLines(actionId);
                    for (int i = 0; i < numLines; i++) {
                        /*List paramFormatLine = (List)ConfigData.getManFileFormat(actionId, i);
                         List paramFormatLine = (List)ConfigData.getManFileFormatInfo(actionId, i); */
                        List<List<String>> paramFormatLineInfo = MCREWConfig.getManFileFormatInfo(actionId, i);
                        String symbol = paramFormatLineInfo.get(0).get(0);
                        String spaceAfterSymbol = "";

                        /* if( symbol.equals( symbolParameterLine ) || symbol.equals(symbolUserNotesLineText) ){
                         spaceAfterSymbol = (String)paramFormatLineInfo.get(1);
                         }*/
                        if (symbol.equals(symbolParameterLine)) {
                            List<String> paramFormatLine = paramFormatLineInfo.get(1);

                            if (paramFormatLine == null) {
                                System.err.println("Action:initialize()"
                                        + "Theres is no man_format.xml data for the dataLine " + dataLine
                                        + " of action" + actionId);
                                continue;
                            }
                            /*Enumeration e1 = paramFormatLine.elements();
                             while( e1.hasMoreElements() ){
                             //System.out.println(" ManangeData : WriteManFile() : Elements in paramFormatLine are : "
                             //+ (String)( e1.nextElement() ));
                             }*/
                            dataLine = symbolParameterLine;

                            for (String paramFormatLine1 : paramFormatLine) {
                                String paramName = paramFormatLine1.trim();
                                String value = action.getValue(paramName);
                                //System.out.println(" ManangeData : WriteManFile() :
                                //Element in paramFormatLine is : == ParamName : " + paramName + " ParamValue : " + value );
                                if (value == null) {
                                    ///System.err.println("\tManageData:writemanfile()->"+
                                    //"Theres no value for parameter '" +paramName +
                                    //        "' in the action "+ actionId);
                                    continue;
                                }
                                dataLine = prettyConcat(dataLine, value);
                                //System.out.println("\t ManageData :
                                //writemanfile() : The dataLine value is : " + dataLine );
                            }

                            if (dataLine.equals("")) {
                                dataLine = symbolParameterLine + "\n";
                            }

                            if (dataLine != null) {
                                pw.write(dataLine);
                                pw.write("\n");
                            }
                        }

                        if (symbol.equals(symbolUserNotesLineText)) {
                            // if( spaceAfterSymbol.equals(" ") ){
                            List<String> paramFormatLine = paramFormatLineInfo.get(1);

                            if (paramFormatLine == null) {
                                System.err.println("Action:initialize()" + "Theres is no man_format.xml data for the dataLine "
                                        + dataLine + " of action" + actionId);
                                continue;
                            }
                            /*Enumeration e1 = paramFormatLine.elements();
                             while( e1.hasMoreElements() ){
                             //System.out.println(" ManangeData : WriteManFile() : Elements in paramFormatLine are : "
                             //+ (String)( e1.nextElement() ));
                             }*/
                            dataLine = symbolUserNotesLineText;
                            String dataLineText = "";

                            for (String paramFormatLine1 : paramFormatLine) {
                                String paramName = paramFormatLine1.trim();
                                String value = action.getValue(paramName);
                                //System.out.println(" ManangeData : WriteManFile() : Element in paramFormatLine is :
                                //== ParamName : " + paramName + " ParamValue : " + value );
                                if (value == null) {
                                    //System.err.println("\tManageData:writemanfile()->"+
                                    //"Theres no value for parameter '" +paramName +
                                    //      "' in the action "+ actionId);
                                    continue;
                                }
                                //String line = value.substring( 0, value.indexOf('\n') );
                                String[] line = value.split("\\n");
                                int lineLength = line.length;
                                for (int x = 0; x < lineLength; x++) {
                                    //System.out.println(" ManageData : writeManFile()
                                    //: Line string value is : " + line[x] + "Number of newLie tokens : " + line.length  );
                                    dataLine = prettyConcat(symbolUserNotesLineText, line[x]);
                                    dataLineText = dataLineText + dataLine;
                                }
                                //if( lineLength == 0 ){
                                if (dataLineText.equals("")) {

                                    dataLineText = symbolUserNotesLineText + "\n";
                                }
                                if (dataLineText != null) {
                                    pw.write(dataLineText);
                                    //pw.write("\n");
                                }
                                dataLine = symbolUserNotesLineText;
                            }
                            /*  }
                             if( !( spaceAfterSymbol.equals(" ") ) ){
                             pw.write(symbolUserNotesLineText);
                             pw.write("\n");
                             } */
                        }
                        if (symbol.equals(symbolDevNotesLineText)) {
                            // if( spaceAfterSymbol.equals(" ") ){
                            List<String> paramFormatLine = paramFormatLineInfo.get(1);

                            if (paramFormatLine == null) {
                                System.err.println("Action:initialize()" + "Theres is no man_format.xml data for the dataLine "
                                        + dataLine + " of action" + actionId);
                                continue;
                            }
                            /*Enumeration e1 = paramFormatLine.elements();
                             while( e1.hasMoreElements() ){
                             //System.out.println(" ManangeData : WriteManFile() : Elements in paramFormatLine are : "
                             //+ (String)( e1.nextElement() ));
                             }*/
                            dataLine = symbolDevNotesLineText;
                            String dataLineText = "";

                            for (String paramFormatLine1 : paramFormatLine) {
                                String paramName = paramFormatLine1.trim();
                                String value = action.getValue(paramName);
                                //System.out.println(" ManangeData : WriteManFile() : Element in paramFormatLine is :
                                //== ParamName : " + paramName + " ParamValue : " + value );
                                if (value == null) {
                                    //System.err.println("\tManageData:writemanfile()->"+
                                    //"Theres no value for parameter '" +paramName +
                                    //      "' in the action "+ actionId);
                                    continue;
                                }
                                //String line = value.substring( 0, value.indexOf('\n') );
                                String[] line = value.split("\\n");
                                int lineLength = line.length;
                                for (int x = 0; x < lineLength; x++) {
                                    //System.out.println(" ManageData : writeManFile()
                                    //: Line string value is : " + line[x] + "Number of newLie tokens : " + line.length  );
                                    dataLine = prettyConcat(symbolDevNotesLineText, line[x]);
                                    dataLineText = dataLineText + dataLine;
                                }
                                //if( lineLength == 0 ){
                                if (dataLineText.equals("")) {

                                    dataLineText = symbolDevNotesLineText + "\n";
                                }
                                if (dataLineText != null) {
                                    pw.write(dataLineText);
                                    //pw.write("\n");
                                }
                                dataLine = symbolDevNotesLineText;
                            }
                        }
                        /*if(dataLine != null){
                         pw.write(dataLine);
                         pw.write("\n");
                         }*/
                    } // end for paramForamtVec
                } // end for actionVec
                //dataLine = (new Character(cCommentLine)).toString();
                dataLine = S_COMMENTSTR;
                pw.write(dataLine);
                pw.write("\n");
            }

            dataLine = ((Character) C_START_END_LINE).toString();
            dataLine = dataLine.concat(S_END);
            pw.write(dataLine);
            pw.write("\n");

            dataLine = ((Character) C_START_END_LINE).toString();
            dataLine = dataLine.concat(S_EOF);
            pw.write(dataLine);
            pw.write("\n");

            //mwepsmanfilenotes = "";
            pw.flush();

        } catch (FileNotFoundException e) {
            return K_FILE_NOT_FOUND;
        }

        return K_SUCCESS;
    }

    int writeOldFile(String pFileName) {
        String dataLine;

        String symbolParameterLine = Character.valueOf(C_PARAMETER_LINE).toString();
        String symbolUserNotesLineText = Character.valueOf(C_USER_NOTES_LINETEXT).toString();
        String symbolDevNotesLineText = Character.valueOf(C_DEV_NOTES_LINETEXT).toString();

        try (PrintWriter pw = new PrintWriter(new BufferedWriter(new TFileWriter(new TFile(pFileName))))) {
            dataLine = S_VERSION;

            double version = VERSION_ARCHIVE;

            dataLine = prettyConcat(dataLine, String.valueOf(version));
            pw.write(dataLine);
            pw.write("\n");

            dataLine = ((Character) C_START_END_LINE).toString().concat(S_START);
            dataLine = prettyConcat(dataLine, Integer.toString(kRotationYears));
            pw.write(dataLine);
            pw.write("\n");

            if (mwepsmanfilenotes == null || mwepsmanfilenotes.length() == 0) {
                mwepsmanfilenotes = "";
            } else {
                mwepsmanfilenotes = mwepsmanfilenotes + "\n";
            }
            mwepsmanfilenotes = mwepsmanfilenotes
                    + "This management file was reverted to version " + VERSION_ARCHIVE + " on - "
                    + new Date().toString();

            dataLine = ((Character) C_MAN_FILE_NOTES).toString();
            dataLine = prettyConcat(dataLine, XMLDoc.getXMLSafeString(mwepsmanfilenotes));
            pw.write(dataLine);
            pw.write("\n");

            dataLine = S_COMMENTSTR;
            pw.write(dataLine);
            pw.write("\n");

            int numRows = mRows.size();
            for (int rowidx = 0; rowidx < numRows; rowidx++) {

                RowInfo row = mRows.get(rowidx);

                String date = row.getDate().toString();
                dataLine = ((Character) C_DATE_LINE).toString();
                dataLine = prettyConcat(dataLine, date);

                pw.write(dataLine);
                pw.write("\n");

                OperationObject operationObj = (OperationObject) row.getDataObject(XMLConstants.soperation);

                String operationName = operationObj.getOperationName();
                List<Identity> actionIdVec = operationObj.getAllIds();
                int actionVecSize = actionIdVec.size();

                for (int actionidx = 0; actionidx < actionVecSize; actionidx++) {
                    Action action = operationObj.getAction(actionidx);

                    Identity actionId = action.getIdentity();
                    String actionName = action.getActionName();
                    if (actionName == null) {
                        System.err.println("ManageData:writemanfile()->" + "Theres no actionname for action "
                                + actionId + "in operation_defn.xml");
                        actionName = XMLConstants.sNoName;
                    }
                    dataLine = actionId.toString();
                    if (actionidx == 0) {
                        dataLine = prettyConcat(dataLine, operationName);
                    } else {
                        dataLine = prettyConcat(dataLine, actionName);
                    }
                    pw.write(dataLine);
                    pw.write("\n");

                    int numLines = MCREWConfig.getManFormatNumLines(actionId);
                    for (int i = 0; i < numLines; i++) {
                        List<List<String>> paramFormatLineInfo = MCREWConfig.getManFileFormatInfo(actionId, i);
                        String symbol = paramFormatLineInfo.get(0).get(0);
                        String spaceAfterSymbol = "";
                        if (symbol.equals(symbolParameterLine)) {
                            List<String> paramFormatLine = paramFormatLineInfo.get(1);

                            if (paramFormatLine == null) {
                                System.err.println("Action:initialize()"
                                        + "Theres is no man_format.xml data for the dataLine " + dataLine
                                        + " of action" + actionId);
                                continue;
                            }
                            dataLine = symbolParameterLine;

                            for (String paramFormatLine1 : paramFormatLine) {
                                String paramName = paramFormatLine1.trim();
                                String value = action.getValue(paramName);
                                if (value == null) {
                                    continue;
                                }
                                dataLine = prettyConcat(dataLine, value);
                            }

                            if (dataLine.equals("")) {
                                dataLine = symbolParameterLine + "\n";
                            }

                            if (dataLine != null) {
                                pw.write(dataLine);
                                pw.write("\n");
                            }
                        }

                        if (symbol.equals(symbolUserNotesLineText)) {
                            List<String> paramFormatLine = paramFormatLineInfo.get(1);

                            if (paramFormatLine == null) {
                                System.err.println("Action:initialize()" + "Theres is no man_format.xml data for the dataLine "
                                        + dataLine + " of action" + actionId);
                                continue;
                            }
                            dataLine = symbolUserNotesLineText;
                            String dataLineText = "";

                            for (String paramFormatLine1 : paramFormatLine) {
                                String paramName = paramFormatLine1.trim();
                                String value = action.getValue(paramName);
                                if (value == null) {
                                    continue;
                                }
                                String[] line = value.split("\\n");
                                int lineLength = line.length;
                                for (int x = 0; x < lineLength; x++) {
                                    dataLine = prettyConcat(symbolUserNotesLineText, line[x]);
                                    dataLineText = dataLineText + dataLine;
                                }
                                if (dataLineText.equals("")) {

                                    dataLineText = symbolUserNotesLineText + "\n";
                                }
                                if (dataLineText != null) {
                                    pw.write(dataLineText);
                                }
                                dataLine = symbolUserNotesLineText;
                            }
                        }
                    }
                }
                dataLine = S_COMMENTSTR;
                pw.write(dataLine);
                pw.write("\n");
            }

            dataLine = ((Character) C_START_END_LINE).toString();
            dataLine = dataLine.concat(S_END);
            pw.write(dataLine);
            pw.write("\n");

            dataLine = ((Character) C_START_END_LINE).toString();
            dataLine = dataLine.concat(S_EOF);
            pw.write(dataLine);
            pw.write("\n");
            pw.flush();

        } catch (FileNotFoundException e) {
            return K_FILE_NOT_FOUND;
        }

        return K_SUCCESS;
    }

    List<String> plantKillDates;

    public boolean checkForPlantKills() {
        plantKillDates = new ArrayList<>();
        this.initializeCropIntervals();
        if (intervals != null && !intervals.isEmpty()) {
            for (CropIntervalInfo inter : intervals) {
                if (inter.plant < inter.end) {
                    JulianCalendar startDate = ManageData.this.getDate(inter.plant);
                    JulianCalendar endDate = ManageData.this.getDate(inter.end);
                    if (startDate.equals(endDate)) {
                        plantKillDates.add(startDate.getDate());
                    }
                }
            }
        } else if (intervals != null && intervals.isEmpty()) {

        }
        return !plantKillDates.isEmpty();
    }

    /**
     * This method individually confirms if date cell in each row has dates
     * entered in it.
     *
     * @return The vector that contains the collection of row objects that have
     * empty date cells
     */
    public List<Integer> checkIfAllDatesPresent() {
        List<Integer> noDateRows = null;
        boolean feb29 = false;
        boolean plantKill = checkForPlantKills();
        
        for (int i = 0; i < mRows.size(); i++) {
            RowInfo row = mRows.get(i);
            JulianCalendar tempJCal = row.getDate();
            ////System.out.println("ManageData:checkIfAllDatesPresent: " + "Era = " + tempJCal.get(Calendar.ERA) );
            /* BC dates are invalid dates */
            if ((tempJCal == null) || (tempJCal.get(Calendar.ERA) == GregorianCalendar.BC)) {
                if (noDateRows == null) {
                    noDateRows = new ArrayList<>();
                }
                noDateRows.add(i);
            } else if (!feb29 && tempJCal.feb29()) {
                feb29 = true;
            }
        }
        if ((feb29 || plantKill)) {
            String plantKillString = "";
            if (plantKill) {
                plantKillString += "This management file contains a planting event immediately followed by a"
                        + " kill event on the same day.\nThis may cause unexpected results from the simulation."
                        + "\nDates with both a plant and a kill are:  ";
                plantKillString += plantKillDates.get(0);
                for (int index = 1; index < Math.min(3, plantKillDates.size() - 1); index++) {
                    plantKillString += ", " + plantKillDates.get(index);
                }
                if (plantKillDates.size() > 4) {
                    plantKillString += ", " + plantKillDates.get(4) + " and "
                            + (plantKillDates.size() - 4) + "others.";
                } else if (plantKillDates.size() == 1) {
                    plantKillString += ".";
                } else {
                    plantKillString += " and " + plantKillDates.get(plantKillDates.size() - 1) + ".";
                }
            }
            JOptionPane.showMessageDialog(null, "" + (feb29 ? "February 29th selected as a date in the Management file."
                    + "\nUsing this date may give unexpeced results from the simulation.\n" : "") + plantKillString, "February 29th",
                    JOptionPane.OK_OPTION);
        }
        return noDateRows;
    }

    /**
     * Checks if the table rows are empty or not.
     *
     * @return The vector that contains a collection of row objects that are
     * empty.
     */
    public List<Integer> checkEmptyRows() {
        //Check for blank lines in the data
        List<Integer> emptyRows = null;

        for (int i = 0; i < mRows.size(); i++) {
            RowInfo row = mRows.get(i);
            if (row.getDataObject(XMLConstants.soperation) == null) {
                if (emptyRows == null) {
                    emptyRows = new ArrayList<>();
                }
                emptyRows.add(i);
            }
        }

        return emptyRows;
    }

    /**
     * This function concatenates the string parameters with a space in between them
     * and returns the concatenated string
     */
    private String prettyConcat(String data, String param) {
        if (data == null || param == null) {
            return "";
        }
        StringBuffer buffer = new StringBuffer(data);
        buffer.append(" "); // Append a single space

        if (data.equals("N")) {
            buffer = new StringBuffer(data);

            String[] result = param.split("\\n");
            for (int i = 0; i < result.length; i++) {
                if (i == 0) {
                    buffer.append(" ").append(result[i]);
                } else {
                    buffer.append("\nN ").append(result[i]);
                }
            }

            return buffer.toString();
        }

        if (data.equals("T") || data.equals("t")) {
            /* Added to allow "t" as well - LEW */
            //  //System.out.println("param String:"+param+" size:"+param.length());
            if (param.equals("") || param.length() == 0) {
                return data + "\n";
            } else if (!param.equals("") && ((param.trim().equals("")))) {
                return data + param + "\n";
            } else if ((param.substring(0, 1).equals(" ")) && (!(param.trim().equals("")))) {
                return data + param + "\n";
            } else if ((param.substring(0, 1).equals(" ")) && ((param.trim().equals("")))) {
                return data + " " + param + "\n";
            } else if (!(param.substring(0, 1).equals(" "))) {
                return data + " " + param + "\n";
            }

            return buffer.toString();
        }

        if (data.equals("+") && !param.equals("") && param.trim().equals("")) {
            return data + param;
        } else {
            buffer.append(param);
        }

        return buffer.toString();
    }

    // ADD ERROR HANDLING
    /**
     * This method deletes the rows that are selected from the table along with
     * the data associated with it.
     *
     * @param scv The vector containing the selected row cropNumber objects to
     * be deleted.
     */
    public void deleteSelectedRows(List<JCCellRange> scv) {
        clearDeleted();
        try {
            for (JCCellRange jccr : scv) {
                int sRow = jccr.start_row;
                int eRow = jccr.end_row;
                if (sRow > eRow) {
                    int pivotPoint = sRow;
                    sRow = eRow;
                    eRow = pivotPoint;
                }
                int max = getRows().size();
                for (int i = sRow; i <= eRow; i++) {
                    if (i >= max) {
                        continue;
                    }
                    deletedRowNums.add(sRow);
                    deletedRows.add(mRows.remove(sRow));
                    fireRowDeleted(sRow, 1);
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            System.err.println("ManageData:deleteRows->" + "Trying to delete non existing row ");
        }
        if (pivot > mRows.size() - 1) {
            pivot = 0;
        }
    }
    List<Integer> deletedRowNums = new ArrayList<>();
    List<RowInfo> deletedRows = new ArrayList<>();

    /**
     * This method deletes the rows that are mentioned in the vector passed as
     * argument to the method from the table.
     *
     * @param pRowNums The vector containing the rows to be deleted.
     */
    public void deleteRows(List<Integer> pRowNums) {
        clearDeleted();
        try {
            for (int i = 0; i < pRowNums.size(); i++) {
                deletedRowNums.add(i);
                int indexToDelete = pRowNums.get(i) - i;
                deletedRows.add(mRows.remove(indexToDelete)); // subtract i from as cropNumber of elements in mRows are decreasing
                fireRowDeleted(indexToDelete, 1);
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            System.err.println("ManageData:deleteRows->" + "Trying to delete non existing row ");
        }
        if (pivot > mRows.size() - 1) {
            pivot = 0;
        }
    }

    private void clearDeleted() {
        deletedRowNums.clear();
        deletedRows.clear();
    }

    public int[] undoDeleteRows() {
        Integer[] r = deletedRowNums.toArray(new Integer[0]);
        int[] rtn = new int[r.length];
        for (int i = 0; i < r.length; i++) {
            rtn[i] = r[i];
        }
        int numReplaced = 0;
        if (deletedRowNums.isEmpty()) {
            return null;
        }
        while (!deletedRowNums.isEmpty()) {
            insertRow(deletedRowNums.get(deletedRowNums.size() - 1), deletedRows.get(deletedRowNums.size() - 1));
            deletedRows.remove(deletedRows.size() - 1);
            deletedRowNums.remove(deletedRowNums.size() - 1);
            numReplaced++;
        }
        clearDeleted();
        return rtn;
    }

    /**
     * This method inserts all the rows from vector "pRows" starting at pRowNum
     * whose row data object is pRow.
     *
     * @param pRowNum The place in the table where this row needs to be
     * inserted.
     * @param pRows The vector that holds all the rows to be inserted.
     */
    public void insertRows(int pRowNum, List<RowInfo> pRows) {
        clearDeleted();
        int addAtRow = pRowNum;
        for (RowInfo pRow : pRows) {
            mRows.add(addAtRow++, pRow);
            fireRowsAdded(addAtRow - 1, 1);
            //System.out.println("ManageData:insertData" + "Inserted row " + ((RowInfo)pRows.get(i)).getDate());
        }
    }

    /**
     * This method inserts a row at pRowNum whose row data object is pRow.
     *
     * @param pRowNum The place in the table where this row needs to be
     * inserted.
     * @param pRow The row info object that holds the data for that row which is
     * being inserted.
     */
    public void insertRow(int pRowNum, RowInfo pRow) {
        mRows.add(pRowNum, pRow);
        fireRowsAdded(pRowNum, 1);
    }

    /**
     * This method inserts a row at "rowNum" in the MCREW table.
     *
     * @param pRowNum
     */
    public void insertRow(int pRowNum) {
        RowInfo pRow = new RowInfo();
        if (pRowNum <= 0) {
            //Will be first row, nothing before
            JulianCalendar afterCal = (JulianCalendar) getDisplayDate(0).clone();
            pRow.setDisplayDate(afterCal);
        } else {
            //Somewhere between, something before and after.
            JulianCalendar beforeCal = (JulianCalendar) getDisplayDate(pRowNum - 1).clone();
            pRow.setYearOffset(mRows.get(pRowNum - 1).getYearOffset());
            pRow.setDisplayDate(beforeCal);
        }
        insertRow(pRowNum, pRow);
    }
    List<RowInfo> clipBoardRows = new ArrayList<>();

    /**
     * This method copies all the selected rows to be replicated in the same
     * table but elsewhere.
     *
     * @param scv The selection of rows that need to be replicated.
     */
    public void copyRows(List<JCCellRange> scv) {
        clearDeleted();
        clipBoardRows.clear();

        for (JCCellRange jccr : scv) {
            int sRow = jccr.start_row;
            int eRow = jccr.end_row;

            if (sRow > eRow) {
                int pivotPoint = sRow;
                sRow = eRow;
                eRow = pivotPoint;
            }

            //Patch to allow users to copy bottom row
            if ((sRow == getRows().size()) && (eRow == getRows().size())) {
                RowInfo pRow = new RowInfo();
                JulianCalendar beforeCal = (JulianCalendar) getDisplayDate(sRow - 1).clone();
                pRow.setYearOffset(mRows.get(sRow - 1).getYearOffset());
                pRow.setDisplayDate(beforeCal);
                clipBoardRows.add(pRow);
            } //End of patch
            else {
                for (int i = sRow; i <= eRow; i++) {
                    try {
                        clipBoardRows.add(mRows.get(i));
                    } catch (ArrayIndexOutOfBoundsException e) {
                        System.err.println("ManageData:cutRows()"
                                + "Trying to cut row with no data (might be last row), row num: " + i);
                    }
                }
            }
        }
    }

    /**
     * This method cuts the selected rows from the table whose IDs are stored in
     * the vector passed as argument to it.
     *
     * @param scv The vector that holds the row IDs that need to be removed.
     */
    public void cutRows(List<JCCellRange> scv) {
        clearDeleted();
        clipBoardRows.clear();
        for (JCCellRange jccr : scv) {
            int sRow = jccr.start_row;
            int eRow = jccr.end_row;

            if (sRow > eRow) {
                int pivotPoint = sRow;
                sRow = eRow;
                eRow = pivotPoint;
            }

            //Patch to allow users to copy bottom row
            if ((sRow == getRows().size()) && (eRow == getRows().size())) {

                JOptionPane.showMessageDialog(null, "Unable to cut last row",
                        "Error cutting rows", JOptionPane.ERROR_MESSAGE);
                return;
            } //End of patch
            else {
                for (int i = sRow; i <= eRow; i++) {
                    try {
                        clipBoardRows.add(mRows.remove(sRow));
                        fireRowDeleted(sRow, 1);
                    } catch (ArrayIndexOutOfBoundsException e) {
                        System.err.println("ManageData:cutRows()"
                                + "Trying to cut row with no data (might be last row), row num: " + i);
                    }
                }
            }
        }
    }

    /**
     * The method pastes the rows that were copied and stored in the vector with
     * their IDs.
     *
     * @param scv The vector that holds the IDs of all the rows to be pasted in
     * the table.
     */
    public void pasteRows(List<JCCellRange> scv) {
        clearDeleted();
        if (scv.isEmpty()) {
            //System.out.println("MD_pR: paste faild; no scv");
            return;
        }
        Collections.sort(clipBoardRows);
        //System.out.println("MD_pR: " + scv);
        JCCellRange jccr = scv.get(0);
        int rowNum = jccr.start_row;
        for (RowInfo current : clipBoardRows) {
            RowInfo clone;
            try {
                clone = (RowInfo) current.clone();
                mRows.add(rowNum++, clone);
            } catch (CloneNotSupportedException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
    }

    /**
     * This method pastes the row at a place in the table specified by the
     * argument.
     *
     * @param rowNum The place where the row will be pasted.
     * @return True if the row was successfully pasted else false.
     */
    public int pasteRows(int rowNum) {
        clearDeleted();
        Collections.sort(clipBoardRows);
        //System.out.println("MD_pR: " + rowNum);
        int rowCnt = 0;
        for (RowInfo current : clipBoardRows) {
            RowInfo clone;
            try {
                clone = (RowInfo) current.clone();
                insertRow(rowNum++, clone);
                rowCnt++;
            } catch (CloneNotSupportedException ex) {
                Exceptions.printStackTrace(ex);
            }
        }
        return rowCnt;
    }

    public void sortData() {
        Collections.sort(mRows, (RowInfo r1, RowInfo r2) -> {
            if (r1.getDisplayDate().equals(r2.getDisplayDate())) {
                int r1Index = mRows.indexOf(r1);
                int r2Index = mRows.indexOf(r2);
                return r1Index - r2Index;
                
            } else {
                //Compare by dates.
                return r1.getDisplayDate().compareTo(r2.getDisplayDate());
            }
        });
        fireDataReset();
    }

    public boolean checkSort() {
        if (mRows.size() == 2) {
            RowInfo r1 = mRows.get(0);
            RowInfo r2 = mRows.get(1);
            return r1.getDisplayDate().compareTo(r2.getDisplayDate()) != 1;
        }
        if (mRows.size() < 2) {
            return true;
        }
        for (int index = 0; index < mRows.size() - 2;) {
            RowInfo r1 = mRows.get(index);
            RowInfo r2 = mRows.get(++index);
            if (r1.getDisplayDate().compareTo(r2.getDisplayDate()) == 1) {
                return false;
            }
        }
        return true;
    }
    //CHECKS

    /**
     *
     */
    public static final int CHECK_PASSED = 0;

    /**
     *
     */
    public static final int CHECK_FAILED = 1;

    public static final int CHECK_WARNING = 2;
    /**
     * This method makes sure that the rotation years are set right.
     *
     * @return True if set correct else false
     */
    public int checkRotationYears() {
        //Implement natural ordering according to dates
        if (mRows.isEmpty()) {
            JOptionPane.showMessageDialog(null, "CANNOT SAVE : Check for either empty rows or empty file itself.",
                    " Cannot Save file ", JOptionPane.INFORMATION_MESSAGE);
            return -1;
        }
        RowInfo maxRow = Collections.max(mRows);

        int maxYear = ((Calendar) maxRow.getDate()).get(Calendar.YEAR);

        //return maxYear - minYear + 1;
        /* We will be returning the maximum year in the data-rather than the
         * cropNumber of years between the maximum year and minimum year-added by Neha.
         */
        return maxYear;
    }

    /**
     * Checks if all the parameters are consistent with the way we want them in
     * the model so that the application has no problem reading and writing the
     * data from those parameters when requested.
     *
     * @return This vector contains all the parameters that are inconsistent with
     * the model.
     */
    public List<OperationObject> checkParameterConsistency() {
        List<OperationObject> invalidOprns = null;

        for (int i = 0; i < size(); i++) {
            OperationObject obj = (OperationObject) getDataObject(i, XMLConstants.soperation);
            List<Action> invalidActions = obj.checkParameterConsistency();
            if (invalidActions != null) {
                if (invalidOprns == null) {
                    invalidOprns = new ArrayList<>();
                }
                invalidOprns.add(obj);
            }
        }
        return invalidOprns;

        //return null;
    }

    /**
     *
     * @param msgs
     * @return
     */
    public int checkAllConditions(List<String> msgs) {
        int results = 0;
        
        try {
            results |= checkVersion(msgs);
            
            results |= checkIfAllDatesPresent(msgs);

            results |= checkIfSorted(msgs);

            results |= checkEmptyRows(msgs);

            results |= checkRotYears(msgs);

            results |= checkParameterConsistency(msgs);

            
        } catch (Exception e) {
            msgs.add("An Unknown error occured attempting to validate the file.");
            return CHECK_FAILED;
        }

        //If any of the tests failed, return a failed status
        if ((results & CHECK_FAILED) == CHECK_FAILED) {
            return CHECK_FAILED;
        } else if ((results & CHECK_WARNING) == CHECK_WARNING){
            return CHECK_WARNING;
        }
        else {
            return CHECK_PASSED;
        }
    }

    public int checkVersion(List<String> msgs) {
        if (version < VERSION_MINIMUM) {
            msgs.add("Management file " + manFile.getAbsolutePath() + " is of version: " + version
                    + ", whereas the minimum version is: " + VERSION_MINIMUM);
            return CHECK_FAILED;
        }
        if (version > VERSION_CURRENT) {
            msgs.add("Management file " + manFile.getAbsolutePath() + "\nis of version: " + version
                    + ", whereas the current version is: " + VERSION_CURRENT);
            return CHECK_FAILED;
        }
        return CHECK_PASSED;
    }

    /**
     * This method makes sure if rows exist in a table then their date cells are
     * populated.
     *
     * @param msgs
     * @return True, if date is populated for every row in a table else false.
     */
    public int checkIfAllDatesPresent(List<String> msgs) {
        List<Integer> noDateRows = checkIfAllDatesPresent();

        if (noDateRows == null) {
            return CHECK_PASSED;
        }
        String noDateRowStr = null;
        msgs.add("There are following rows with no dates in them.");

        for (Integer noDateRow : noDateRows) {
            if (noDateRowStr == null) {
                noDateRowStr = noDateRow.toString();
            } else {
                noDateRowStr += ", " + noDateRow.toString();
            }
        }

        msgs.add(noDateRowStr);

        return CHECK_FAILED;
    }

    /**
     * Check if the rows are in increasing order (of dates)
     *
     * @param msgs
     * @return True if the rows are in increasing order else false
     */
    public int checkIfSorted(List<String> msgs) {
        int lastIndex = mRows.size() - 1;
        for (int i = 0; i < lastIndex; i++) {
            RowInfo presentRow = mRows.get(i);
            RowInfo nextRow = mRows.get(i + 1);

            if (presentRow.compareTo(nextRow) > 0) {
                msgs.add("The data is not sorted according to the dates.");
                return CHECK_FAILED;
            }
        }
        return CHECK_PASSED;
    }

    public int checkEmptyRows(List<String> msgs) {
        List<Integer> emptyRows = checkEmptyRows();

        if (emptyRows == null) {
            return CHECK_PASSED;
        }
        StringBuffer buffer = null;
        msgs.add("There are some empty rows in the management file as given below.");

        for (Integer emptyRow : emptyRows) {
            if (buffer == null) {
                buffer = new StringBuffer(emptyRow.toString());
            } else {
                buffer.append(", ");
                buffer.append(emptyRow.toString());
            }
        }
        if (buffer != null) {
            msgs.add(buffer.toString());
        }

        return CHECK_FAILED;
    }

    public int checkRotYears(List<String> msgs) {
        int correctYears = checkRotationYears(); // returns the correct year value
        int rotYears = getRotationYears();

        if (rotYears < correctYears) {
            msgs.add("The numbers of rotation years is incorrectly set.");
            return CHECK_FAILED;
        } else {
            return CHECK_PASSED;
        }
    }

    public int checkParameterConsistency(List<String> msgs) {
        List<OperationObject> invalidOprns = checkParameterConsistency();
        if (invalidOprns == null) {
            return CHECK_PASSED;
        }
        int check = 0;
        msgs.add("The following operations have parameters which might be having incorrect values "
                + "due to wrong management file version");
        for (OperationObject invalidOprn : invalidOprns) {
            String operationName = invalidOprn.getOperationName();
            msgs.add(operationName);

            List<Action> invalidActions = invalidOprn.checkParameterConsistency();
            for (Action invalidAction : invalidActions) {
                String actionId = invalidAction.getIdentity().toString();
                String messageLine = "\t" + actionId + ": ";

                List<String> invalidParams = invalidAction.checkParameterConsistency();
                check |= invalidAction.getCheckForParameterConsistency();
                for (Iterator<String> kt = invalidParams.iterator(); kt.hasNext();) {
                    messageLine = messageLine.concat(kt.next());
                    messageLine = messageLine.concat(", ");
                }

                msgs.add(messageLine.substring(0, messageLine.length() - 2)); // Remove the last  ", "
            }
        }

        return check;
    }

    public int checkCalibration(List<String> msgs) {

        int results = 0;

        results |= checkCropSelectedForCalibration(msgs);
        results |= checkCropHasHarvestForCalibration(msgs);

        //If any of the tests failed, return a failed status
        if ((results & CHECK_FAILED) == CHECK_FAILED) {
            return CHECK_FAILED;
        } else {
            return CHECK_PASSED;
        }
    }

    public int checkCropSelectedForCalibration(List<String> msgs) {

        for (RowInfo row : getRows()) {
            DataObject o = row.getDataObject(XMLConstants.soperation);
            if (o instanceof OperationObject) {
                OperationObject op = (OperationObject) o;

                //is this a planting op?
                if (OperationClassification.Planting.is(op)) {
                    //is the calibration flag set?
                    String value = op.getValue("cbaflag");

                    if (value != null && Integer.valueOf(value.trim()) == 1) {
                        return CHECK_PASSED;
                    }
                }
            }
        }

        msgs.add("No crops selected for calibration.");

        return CHECK_FAILED;
    }

    public int checkCropHasHarvestForCalibration(List<String> msgs) {

        int result = CHECK_PASSED;

        for (RowInfo row : getRows()) {
            DataObject o = row.getDataObject(XMLConstants.soperation);
            if (o instanceof OperationObject) {
                OperationObject op = (OperationObject) o;

                //is this a planting op?
                if (OperationClassification.Planting.is(op)) {
                    //is the calibration flag set?
                    String cbaflag = op.getValue("cbaflag");

                    String cropname;
                    if (cbaflag != null && Integer.valueOf(cbaflag.trim()) == 1) {
                        //this crop is set to calibrate, store the name in case we need it later
                        cropname = op.getCropName();

                        int startIndex = getRows().indexOf(row);

                        //collect all the harvests for this crop, aka every harvest op until the next planting.
                        List<OperationObject> harvests = new LinkedList<>();

                        //loop
                        for (int i = startIndex + 1; i < getRows().size() + startIndex; i++) {
                            Object o2 = getRow(i % getRows().size()).getDataObject(XMLConstants.soperation);
                            if (o instanceof OperationObject) {
                                OperationObject op2 = (OperationObject) o2;
                                if (OperationClassification.Harvest.is(op2)) {
                                    harvests.add(op2);
                                }
                                if (OperationClassification.Planting.is(op2)) {
                                    //reached another planting, break out of the harvest seach
                                    break;
                                }
                            }
                        }

                        //check that only one harvest is set to calibrate
                        int foundCalibratedHarvest = 0;
                        for (OperationObject harvest : harvests) {
                            String harv_calib_flg = harvest.getValue("harv_calib_flg");
                            if (harv_calib_flg != null && Integer.valueOf(harv_calib_flg.trim()) == 1) {
                                foundCalibratedHarvest++;
                            }
                        }

                        if (foundCalibratedHarvest == 0) {
                            msgs.add("No harvest operation with the calibration flag enable found for " + cropname);
                            result = CHECK_FAILED;
                        } else if (foundCalibratedHarvest > 1) {
                            msgs.add("More than one harvest operation with the calibration flag enable found for " + cropname);
                            result = CHECK_FAILED;
                        }

                    }
                }
            }
        }

        return result;
    }

    /**
     * This method fetches the value assigned to the rotation years of the
     * operation/crop
     *
     * @return The year value when the operation/crops will be rotated.
     */
    public int getRotationYears() {
        return kRotationYears;
    }

    public double getVersion() {
        return version;
    }

    /**
     * This method assigns the value of the rotation years of the operation/crop
     *
     * @param pYears The cropNumber of years when the operation/crop will be
     * rotated.
     */
    public void setRotationYears(int pYears) {
        kRotationYears = pYears;
    }

    /**
     * This method sets the notes written in the notes text area of the .MAN
     * file to the variable that stores it for future references.
     *
     * @param notes The notes string to be stored.
     */
    public void setWepsManFileNotes(String notes) {

        mwepsmanfilenotes = notes != null ? notes.trim() : null;
    }
    // check the rotation year: if the date is null, check all.
    // otherwise, check the date is greater than the current year value

    public static final Object EMPTY_OBJECT = new Object();

    //JCTableModel code
    @Override
    public Object getTableDataItem(int row, int column) {

        String colName = MCREWConfig.getTagName(column);
        String objectName = MCREWConfig.getObjectName(column);
        Object result = getColumnValue(row, objectName, colName);
        if (result == null) {
            if (colName.equals("Date") || colName.equals("operationname")) {
                return EMPTY_OBJECT;
            }

        }
        return result;
    }

    @Override
    public boolean setTableDataItem(Object o, int row, int column) {
        if (row > size() - 1) {
            addRow(size(), new RowInfo());
            return setTableDataItem(o, row, column);
        }

        String colName = MCREWConfig.getTagName(column);
        String objectName = MCREWConfig.getObjectName(column);
        try {
            setColumnValue(row, objectName, colName, o);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public int getNumRows() {
        //TODO: Cleanup
        int count = size() + 1;
        return count;
    }

    @Override
    public int getNumColumns() {
        //TODO: Cleanup
        int count = MCREWConfig.getNumCols();
        return count;
    }

    @Override
    public Object getTableRowLabel(int row) {
        return getCropIntervalInfo(row);
    }

    /**
     *
     * @param row
     * @return
     */
    public CropIntervalInfo getCropIntervalInfo(final int row) {
//        int crop = 0;
//        for (int i = 0; i < row; i++) {
//            if (isLastHarvest(i)) {
//                crop++;
//            }
//        }
//
//        //wrap code.  Make sure the is another harvest before the end of the management file
//        boolean anotherHarvestLater = false;
//        for (int i = row; i < getRows().size(); i++) {
//            if (isLastHarvest(i)) {
//                anotherHarvestLater = true;
//                break;
//            }
//        }
        initializeCropIntervals();
        for (CropIntervalInfo inf : intervals) {
            if (inf.start >= inf.end) {
                outlier = inf;
            }
            if ((inf.start <= row) && (row < inf.end)) {
                return inf;
            }
        }
        if (!intervals.isEmpty()) {
            if (outlier != null) {
                return outlier;
            } else {
                return intervals.get(0);
            }
        } else {
            return new CropIntervalInfo(0, 0, 0, 0);
        }

    }

    /**
     * Initializes the row ranges for the crop interval side bar.
     */
    @SuppressWarnings("fallthrough")
    private void initializeCropIntervals() {
        intervals = new ArrayList<>();
        //If there are no rows, there are no crops, thus there are is no crop interval.
        //Set it to row zero.  Hoping this doesn't count as an error.
        if (mRows.isEmpty()) {
            intervals.add(new CropIntervalInfo(0, 0, 0, 0));
        }
        //We need to find all P51s and P31s so that we can find all kill rows for crops.
        ArrayList<Integer> plants = new ArrayList<>();
        ArrayList<Integer> kills = new ArrayList<>();
        for (int index = 0; index < mRows.size(); index++) {
            RowInfo row = mRows.get(index);
            OperationObject op = (OperationObject) row.getDataObject(XMLConstants.soperation);
            if (op == null) {
                continue;
            }
            if (op.mActionIdVec.contains(new Identity(51, "P"))) {
                plants.add(index);
            }
            //-ihaas- checking for upgm
            if (op.mActionIdVec.contains(new Identity(100, "P"))) {
                plants.add(index);
            }
            if (op.mActionIdVec.contains(new Identity(31, "P"))) {
                kills.add(index);
            }
        }
        //If there are no crops, there is no crop interval.  Set it to row zero.
        //Hoping this doesn't count as an error.
        if (plants.isEmpty()) {
            intervals.add(new CropIntervalInfo(0, 0, 0, 0));
        } else if (plants.size() == 1) {
            //If we only have one plant, we need to treat it differently, as the
            //start and end should be the same row, and both be placed on the termination
            //of the crop.
            if (kills.isEmpty()) {
                intervals.add(new CropIntervalInfo(plants.get(0), plants.get(0), 0, plants.get(0)));
            } else {
                int plant = plants.get(0);
                int term = -1;
                OperationObject plantOp = (OperationObject) mRows.get(plant).getDataObject(XMLConstants.soperation);
                String tempVal = (String) plantOp.getCrop().getParameter("idc").getValue();
                int cropType = Integer.parseInt(tempVal);
                for (int kill : kills) {
                    if (kill <= plant) {
                        continue;
                    }
                    OperationObject killOp = (OperationObject) mRows.get(kill).getDataObject(XMLConstants.soperation);
                    int index = killOp.getAllIds().indexOf(new Identity(31, "P"));
                    if (index == -1) {
                        continue;
                    }
                    Action act = killOp.getAction(index);
                    //If it's a Kill op, we need to see what the  kill flag is
                    //to determine whether it killed the crop.
                    tempVal = act.getValue("kilflag");
                    int killType = Integer.parseInt(tempVal);
                    switch (killType) {
                        case 2:
                            term = kill;
                            break;
                        case 1:
                            //If the kill is an annual, we need to see if the corresponding
                            //crop is an annual.  If it is, this is the kill we're looking for.
                            //If not, we don't care.
                            switch (cropType) {
                                case 1:/*FALLTHROUGH*/
                                case 2:/*FALLTHROUGH*/
                                case 4:/*FALLTHROUGH*/
                                case 5:
                                    term = kill;
                                    break;
                                default:
                            }
                            break;
                        default:
                    }
                }
                if (term == -1) {
                    for (int kill : kills) {
                        if (kill > plant) {
                            term = plant;
                            break;
                        }
                        OperationObject killOp = (OperationObject) mRows.get(kill).getDataObject(XMLConstants.soperation);
                        int index = killOp.getAllIds().indexOf(new Identity(31, "P"));
                        if (index == -1) {
                            continue;
                        }
                        Action act = killOp.getAction(index);
                        //If it's a Kill op, we need to see what the  kill flag is
                        //to determine whether it killed the crop.
                        tempVal = act.getValue("kilflag");
                        int killType = Integer.parseInt(tempVal);
                        switch (killType) {
                            case 2:
                                term = kill;
                                break;
                            case 1:
                                //If the kill is an annual, we need to see if the corresponding
                                //crop is an annual.  If it is, this is the kill we're looking for.
                                //If not, we don't care.
                                switch (cropType) {
                                    case 1:/*FALLTHROUGH*/
                                    case 2:/*FALLTHROUGH*/
                                    case 4:/*FALLTHROUGH*/
                                    case 5:
                                        term = kill;
                                        break;
                                    default:
                                }
                                break;
                            default:
                        }

                    }
                }
                if (term == -1) {
                    term = plant;
                }
                intervals.add(new CropIntervalInfo(term, term, 0, plant));
            }
        } else {
            // MEH 
            //boolean carefulWrap = kills.get(kills.size() - 1) < plants.get(plants.size() - 1);
            boolean carefulWrap;
            if (kills.size() > 0) {
                carefulWrap = kills.get(kills.size() - 1) < plants.get(plants.size() - 1);
            } else {
                carefulWrap = false;
                LOGGER.debug("DEBUG carefulWrap set to default false: kills size=" + kills.size() + " plants size=" + plants.size());
            }
            //Now we want to see where the first crop is killed.  That is the start for the next crop
            //interval and so on.  The last number extracted should be the start of the first crop interval.
            int prevTerm = -1;
            for (int plantInd = 0; plantInd < plants.size(); plantInd++) {
                int plant = plants.get(plantInd);
                OperationObject plantOp = (OperationObject) mRows.get(plant).getDataObject(XMLConstants.soperation);
                //We need to know whether the plant is an annual or anything else, to determine what kills it.
                String tempVal = (String) plantOp.getCrop().getParameter("idc").getValue();
                int cropType = Integer.parseInt(tempVal);
                int nextPlant = plantInd != (plants.size() - 1) ? plants.get(plantInd + 1) : mRows.size();
                int currTerm = -1;
                if (plant != mRows.size() - 1) {
                    for (int kill : kills) {
                        //If the kill is before the plant (with the exception of the first crop,
                        //which will be handled elsewhere) it will not end the crop interval.
                        if (kill <= plant) {
                            continue;
                        }
                        //If the next plant happens before a kill that affects this plant,
                        //that kill is the termination of the old plant.
                        if (kill > nextPlant) {
                            currTerm = nextPlant;
                            break;
                        }
                        OperationObject killOp = (OperationObject) mRows.get(kill).getDataObject(XMLConstants.soperation);
                        int index = killOp.getAllIds().indexOf(new Identity(31, "P"));
                        if (index == -1) {
                            continue;
                        }
                        Action act = killOp.getAction(index);
                        //If it's a Kill op, we need to see what the  kill flag is
                        //to determine whether it killed the crop.
                        tempVal = act.getValue("kilflag");
                        int killType = Integer.parseInt(tempVal);
                        switch (killType) {
                            case 2:
                                currTerm = kill;
                                break;
                            case 1:
                                //If the kill is an annual, we need to see if the corresponding
                                //crop is an annual.  If it is, this is the kill we're looking for.
                                //If not, we don't care.
                                switch (cropType) {
                                    case 1:/*FALLTHROUGH*/
                                    case 2:/*FALLTHROUGH*/
                                    case 4:/*FALLTHROUGH*/
                                    case 5:
                                        currTerm = kill;
                                        break;
                                    default:
                                }
                                break;
                            default:
                        }
                        //Somewhere in the switch we found the termination of the plant.
                        //we need to leave the kill loop to setup the plant.
                        if (currTerm != -1) {
                            break;
                        }
                    }
                }
                //If we reached the end of the kill process loop without finding a termination,
                //we need to wrap.
                if (currTerm == -1) {
                    if (carefulWrap && plantInd == plants.size() - 2) {
                        currTerm = nextPlant;
                    } else {
                        //We default to the first plant being the kill for the last crop.
                        currTerm = plants.get(0);
                        for (int kill : kills) {
                            if (kill > currTerm) {
                                break;
                            }
                            OperationObject killOp = (OperationObject) mRows.get(kill).getDataObject(XMLConstants.soperation);
                            int index = killOp.getAllIds().indexOf(new Identity(31, "P"));
                            if (index == -1) {
                                continue;
                            }
                            Action act = killOp.getAction(index);
                            //If it's a Kill op, we need to see what the  kill flag is
                            //to determine whether it killed the crop.
                            tempVal = act.getValue("kilflag");
                            int killType = Integer.parseInt(tempVal);
                            switch (killType) {
                                case 2:
                                    currTerm = kill;
                                    break;
                                case 1:
                                    //If the kill is an annual, we need to see if the corresponding
                                    //crop is an annual.  If it is, this is the kill we're looking for.
                                    //If not, we don't care.
                                    switch (cropType) {
                                        case 1:/*FALLTHROUGH*/
                                        case 2:/*FALLTHROUGH*/
                                        case 4:/*FALLTHROUGH*/
                                        case 5:
                                            currTerm = kill;
                                            break;
                                        default:
                                    }
                                    break;
                                default:
                            }
                        }
                    }
                }
                intervals.add(new CropIntervalInfo(prevTerm, currTerm, intervals.size(), plant));
                prevTerm = currTerm;
            }
            intervals.get(0).start = prevTerm;
        }
    }

    private boolean isLastHarvest(int row) {
        if (getRows().size() <= 1) {
            return false;
        }

        row = modRow(row);
        boolean isLastHarvest = isRowHarvest(row);
        if (isLastHarvest) {
            //This is a crazy edge condition! Some operations are both a harvest and a planting
            if (isRowPlanting(row)) {
                return true;
            }
            for (int i = row + 1; i < getRows().size() + row; i++) {
                int rowIndex = modRow(i);
                if (isRowHarvest(rowIndex)) {
                    //found a later harvest, so we're not last
                    isLastHarvest = false;
                    break;
                }
                if (isRowPlanting(rowIndex)) {
                    break;
                }
            }
        }
        return isLastHarvest;
    }

    private boolean isRowHarvest(int row) {
        if (row < 0 || row >= getRows().size()) {
            return false;
        }
        RowInfo info = getRow(row);
        Object temp = info.getDataObject(XMLConstants.soperation);
        if (temp instanceof OperationObject) {
            OperationObject op = (OperationObject) temp;
            if (OperationClassification.Harvest.is(op)) {
                return true;
            }
        }
        return false;
    }

    private boolean isRowPlanting(int row) {
        if (row < 0 || row >= getRows().size()) {
            return false;
        }
        RowInfo info = getRow(row);
        Object temp = info.getDataObject(XMLConstants.soperation);
        if (temp instanceof OperationObject) {
            OperationObject op = (OperationObject) temp;
            if (OperationClassification.Planting.is(op)) {
                return true;
            }
        }
        return false;
    }

    private int modRow(int row) {
        row = row % getRows().size();
        if (row < 0) {
            row = row + getRows().size();
        }
        return row;
    }

    public CropInterval[] calculateCropIntervals() {
        final List<CropInterval> result = new ArrayList<>();

        JulianCalendar startDate = null;
        CropInterval first = null;
        int cropNumber = 1;
        String name = null;

        for (int i = 0; i < getRows().size(); i++) {

            if (isLastHarvest(i)) {
                JulianCalendar endDate = getRow(i).getDate();
                //add crop interval record
                CropInterval interval = new CropInterval(name, cropNumber,
                        startDate != null ? startDate.getTime() : null, endDate.getTime());
                if (first == null) {
                    first = interval;
                }

                result.add(interval);
                name = null;
                startDate = new JulianCalendar(endDate);
                startDate.add(JulianCalendar.DAY_OF_MONTH, 1);
                cropNumber++;
            }
            if (isRowPlanting(i)) {
                Object o = getRow(i).getDataObject(XMLConstants.soperation);
                if (o instanceof OperationObject) {
                    OperationObject op = (OperationObject) o;
                    name = op.getCropName();
                }
            }
        }

        if (result.size() > 0) {
            //handle the wrap of the crop and start date
            if (first.crop == null && name != null) {
                first.crop = name;
                // handle crop numbering with wrapped crops
                // proper sequence is: (for three crops)
                // end of three
                // one
                // two
                // start of three
                /**
                 * Note: Assertions are not enabled. These will be useless items
                 * unless assertions are enabled. Thus, they will be commented
                 * out unless they are necessary. assert(first.number == 1);
                 */
                first.number = result.size();
                for (int i = 1; i < result.size(); i++) {
                    result.get(i).number -= 1;
                }
            }
            if (first.start == null && startDate != null) {
                first.start = startDate.getTime();
            }
        }

        return result.toArray(new CropInterval[result.size()]);
    }

    public int intervalNumber() {
        return intervals.size();
    }

    public class CropInterval {

        private String crop;
        private int number;
        private Date start;
        private Date end;

        /**
         *
         * @param crop
         * @param number
         * @param start
         * @param end
         */
        public CropInterval(String crop, int number, Date start, Date end) {
            this.crop = crop;
            this.number = number;
            this.start = start;
            this.end = end;
        }

        public String getCrop() {
            return crop;
        }

        public Date getEnd() {
            return end;
        }

        public int getNumber() {
            return number;
        }

       public Date getStart() {
            return start;
        }

    }

    @Override
    public Object getTableColumnLabel(int column) {
        return MCREWConfig.getColumnLabels().get(column);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final ManageData other = (ManageData) obj;
        if (this.kRotationYears != other.kRotationYears) {
            return false;
        }
        if (this.version != other.version) {
            return false;
        }
        if (!this.mwepsmanfilenotes.equals(other.mwepsmanfilenotes) && (this.mwepsmanfilenotes == null
                || !this.mwepsmanfilenotes.trim().equals(other.mwepsmanfilenotes.trim()))) {
            return false;
        }
        return !(this.mRows != other.mRows && (this.mRows == null || !this.mRows.equals(other.mRows)));
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 17 * hash + (this.manFile != null ? this.manFile.hashCode() : 0);
        return hash;
    }

    public static class CropIntervalInfo {

        private int start, end, numb, plant;

        public CropIntervalInfo(int start, int end, int numb, int plant) {
            this.start = start;
            this.end = end;
            this.numb = numb;
            this.plant = plant;
        }

        int getStart() {
            return start;
        }

        int getEnd() {
            return end;
        }

        int getSeriesNumb() {
            return numb;
        }

    }

    public ArrayList<Highlight> checkData() {
        initOp = new ArrayList<>();
        allowSave = InputLimits.TableStatus.OKAY;
        ArrayList<Highlight> highlighter = new ArrayList<>();
        for (int index = 0; index < getRows().size(); index++) {
            RowInfo row = getRows().get(index);
            DataObject obj = row.getDataObject(XMLConstants.soperation);
            if (obj == null) {
                continue;
            }
            InputLimits.TableStatus currentSave = obj.checkData();
            if (allowSave.lessThan(currentSave)) {
                allowSave = currentSave;
            }
            List<Action> actions = ((OperationObject) obj).getAllActions();
            Hashtable<Identity, InputLimits.TableStatus> actionLimits
                    = ((OperationObject) obj).getActLim();
            InputLimits.TableStatus opVal = InputLimits.TableStatus.OKAY;
            boolean cropParent = false;
            for (Action act : actions) {
                Identity id = act.getIdentity();
                if(id.equals(new Identity(66,"P"))){
                    System.out.println("herep66");
                }
                if (cropParent) {
                    if (id.code.equals("G")) {
                        cropParent = false;
                    } else {
                        continue;
                    }
                }
                InputLimits.TableStatus limited = actionLimits.get(id);
                if (id.equals(new Identity(3, "G"))) {
                    if ((limited != InputLimits.TableStatus.CHOICE)) {
                        highlighter.add(new Highlight(index, 2, limited));
                        cropParent = true;
                        continue;
                    }
                }
                if (id.equals(new Identity(0, "O"))) {
                    initOp.add(index);
                    if (!InputLimits.TableStatus.OKAY.lessThan(limited)) {
                        limited = InputLimits.TableStatus.INIT;
                    }

                }
                if (opVal.lessThan(limited)) {
                    opVal = limited;
                }
            }
            if ((opVal != InputLimits.TableStatus.CHOICE)) {
                highlighter.add(new Highlight(index, 1, opVal));
            }

            List<String> column = MCREWConfig.getColumnNames();
            Hashtable<String, InputLimits.TableStatus> limits = obj.getHash();
            CropObject crp = null;
            if ((obj instanceof OperationObject) && ((OperationObject) obj).hasCrop()) {
                crp = ((OperationObject) obj).getCrop();
                crp.checkData();
            }
            for (int colNum = 3; colNum < column.size(); colNum++) {
                InputLimits.TableStatus limited = limits.get(column.get(colNum));
                if (crp != null) {
                    
                    InputLimits.TableStatus crpVal = crp.getHash().get(column.get(colNum));
                    
                    if(colNum == 47){
                        System.out.println("cbaflag");}
                    if (crpVal != null) {
                        if (limited != null && limited.lessThan(crpVal)) {
                            
                            limited = crpVal;
                        }
                    }
                }
                
//                if("cbaflag".equals(column.get(colNum))){
//                    System.out.println("herenull");
//                    limited = null;
//                }
                if ((limited != InputLimits.TableStatus.CHOICE)
                        && (limited != null)) {
                    highlighter.add(new Highlight(index, colNum, limited));
                }
            }

        }
        return highlighter;
    }

    public class Highlight {

        int row;
        int column;
        InputLimits.TableStatus colorKey;

        Highlight() {
            row = 0;
            column = 0;
            colorKey = InputLimits.TableStatus.OKAY;
        }

        Highlight(int inRow, int inColumn, InputLimits.TableStatus inKey) {
            row = inRow;
            column = inColumn;
            colorKey = inKey;
        }

        public int getRow() {
            return row;
        }

        public int getColumn() {
            return column;
        }

        public InputLimits.TableStatus getColor() {
            return colorKey;
        }
    }

    /**
     * Converts the table view from Rotation view to Interval view by rotating
     * around the pivot.
     */
    public void rotToIntSort() {
        if (mRows.isEmpty()) {
            isInt = true;
            return;
        }
        List<RowInfo> temp = new ArrayList<>();
        for (int index = pivot; index < mRows.size(); index++) {
            temp.add(mRows.get(index));
            mRows.get(index).setYearOffset(0);
        }
        for (int index = 0; index < pivot; index++) {
            RowInfo tempRow = mRows.get(index);
            tempRow.getDisplayDate().set(Calendar.YEAR, (tempRow.getDate().get(Calendar.YEAR) + kRotationYears));
            temp.add(tempRow);
            tempRow.setYearOffset(kRotationYears);
        }
        mRows = temp;
        //see if we need to fire an update.
        for (int index = 0; index < mRows.size(); index++) {
            fireRowChanged(index);
        }
        isInt = true;
    }

    /**
     * Converts the table view from Interval View to Rotation view by rotating
     * around the pivot.
     *
     * Exception thrown so wrapping failures can be handled more
     * comprehensively.
     *
     * @throws java.lang.Exception
     */
    public void intToRotSort() throws Exception {
        if (mRows.isEmpty()) {
            fireRowChanged(0);
            pivot = 0;
            isInt = false;
            return;
        }
        JulianCalendar pivotDate = mRows.get(0).getDate();
        JulianCalendar wrapDate = mRows.get(mRows.size() - 1).getDisplayDate();
        JulianCalendar checkDate = wrapDate.cloneDate();
        if (wrapDate.get(Calendar.YEAR) <= kRotationYears) {
            fireRowChanged(0);
            pivot = 0;
            isInt = false;
            return;
        }
        checkDate.set(Calendar.YEAR, (wrapDate.get(Calendar.YEAR) - kRotationYears));
        if (checkDate.after(pivotDate)) {
            throw new Exception();
        }
        int index = mRows.size() - 1;
        pivot = 0;
        while (true) {
            RowInfo tempRow = mRows.get(index);
            if (tempRow.getDisplayDate().get(Calendar.YEAR) <= kRotationYears) {
                break;
            }
            tempRow.enforceActualDate();
            tempRow.setYearOffset(0);
            mRows.add(0, tempRow);
            mRows.remove(index + 1);
            pivot++;
        }
        //see if we need to fire an update.
        for (index = 0; index < mRows.size(); index++) {
            fireRowChanged(index);
        }
        isInt = false;
    }

    public int getPivot() {
        return pivot;
    }

    public int getOrder() {
        return order;
    }

    public void setOrder(int input) {
        order = input;
    }

    public boolean intView() {
        return isInt;
    }

    public void setPivot(int row) {
        pivot = row;
    }

    /**
     * Takes a year, a halfmonth and a placement in the grid.Returns the index
     * in the table corresponding to the operation specified.
     *
     * @param year
     * @param halfmonth
     * @param place
     * @return
     */
    public int decompose(int year, int halfmonth, int place) {
        //NOTE:  If there is an overflow cell, the edge behavior is not handled
        //at this moment.
        if (year > kRotationYears) {
            return -2;
        }
        int passed = 0;
        int index = 0;
        for (; index < mRows.size(); index++) {
            RowInfo temp = mRows.get(index);
            JulianCalendar date = temp.getDate();
            int year2 = date.get(Calendar.YEAR);
            int month2 = date.get(Calendar.MONTH);
            int day = date.get(Calendar.DATE);
            int delim = month2 == 1 ? 14 : 15;
            month2 = (month2 * 2) + (day > delim ? 1 : 0);
            if (!(year2 == year)) {
                continue;
            }
            if ((year2 > year)) {
                break;
            }
            if (!(month2 == halfmonth)) {
                continue;
            }
            if ((month2 > halfmonth)) {
                break;
            }
            if (passed == place) {
                return index;
            } else {
                passed++;
            }
        }
        return - 1;
    }

    /**
     * Takes a year, a halfmonth and a placement in the grid.Returns the index
     * in the table corresponding to the operation specified.This operation is
     * needed to be used with insertion, as the specified date will not be
     * contained in the table.We instead need to return the index before the
     * date.
     *
     * @param year
     * @param halfmonth
     * @param place
     * @return
     */
    public int decomposeForInsert(int year, int halfmonth, int place) {
        //NOTE:  If there is an overflow cell, the edge behavior is not handled
        //at this moment.
        if (year > kRotationYears) {
            return -2;
        }
        int index = 0;
        for (; index < mRows.size(); index++) {
            RowInfo temp = mRows.get(index);
            JulianCalendar date = temp.getDate();
            int year2 = date.get(Calendar.YEAR);
            int month2 = date.get(Calendar.MONTH);
            int day = date.get(Calendar.DATE);
            int delim = month2 == 1 ? 14 : 15;
            month2 = (month2 * 2) + (day > delim ? 1 : 0);
            if (!(year2 == year)) {
                continue;
            }
            if ((year2 > year)) {
                break;
            }
            if (!(month2 == halfmonth)) {
                continue;
            }
            if ((month2 > halfmonth)) {
                break;
            }
            if (0 == --place) {
                return index;
            }
        }
        return index;
    }

    /**
     * Takes a previous rowInfo object and inserts it at the specified row,
     * shifting all other elements back one.
     * @param o
     * @param row
     * @return 
     */
    public boolean setTimelineTableDataItem(Object o, int row) {
        if (o instanceof RowInfo) {
            mRows.add(row, (RowInfo) o);
            return true;
        } else if (o == null && row < mRows.size()) {
            mRows.remove(row);
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returns the rowinfo object of that row.
     * @param row
     * @return 
     */
    public Object getTimelineTableDataItem(int row) {
        return mRows.get(row);
    }

    /**
     * Extracts the rowinfo tied to the specific date
     * @param day
     * @param month
     * @param year
     * @param place
     * @return 
     */
    public RowInfo getRowFromDate(int day, int month, int year, int place) {
        int passed = 0;
        if (year > kRotationYears) {
            return null;
        }
        JulianCalendar input = new JulianCalendar(year, month, day);
        for (RowInfo temp : mRows) {
            JulianCalendar date = temp.getDate();
            if (date.equals(input)) {
                if (passed == place) {
                    return temp;
                } else {
                    passed++;
                }
            }
        }
        return null;
    }

    /**
     * Adds the given row as THE LAST operation on the given date.
     * @param row
     * @param day
     * @param month
     * @param year
     * @return 
     */
    public boolean addRowFromDate(RowInfo row, int day, int month, int year) {
        JulianCalendar input = new JulianCalendar(year, month, day);
        row.setDisplayDate(input);
        for (int index = 0; index < mRows.size() - 1; index++) {
            RowInfo temp = mRows.get(index + 1);
            JulianCalendar date = temp.getDate();
            if (date.after(input)) {
                mRows.add(index, row);
                return true;
            }
        }
        mRows.add(row);
        return true;
    }

    /**
     * Adds the given row operation on the given date in the given place..
     * @param row
     * @param day
     * @param month
     * @param year
     * @param place
     * @return 
     */
    public boolean addRowFromDate(RowInfo row, int day, int month, int year, int place) {
        JulianCalendar input = new JulianCalendar(year, month, day);
        for (int index = 0; index < mRows.size(); index++) {
            RowInfo temp = mRows.get(index + 1);
            JulianCalendar date = temp.getDate();
            if (input.after(date)) {
                continue;
            }
            if (place == 0) {
                mRows.add(index, row);
                return true;
            }
            place--;
            if (date.after(input)) {
                mRows.add(index, row);
                return true;
            }
        }
        mRows.add(row);
        return true;
    }

    public boolean inRotatedArea(int rowNum) {
        return intView() && (rowNum > 0) && (mRows.get(rowNum).getYearOffset() > 0);
    }

    public int illWrap = -77;

    public void addIllWrap(int row) {
        illWrap = row;
    }

    public int getWrap() {
        if (illWrap == -77) {
            illWrap = mRows.size();
        }
        return illWrap;
    }

    public boolean isIllWrap(int row) {
        if (illWrap == mRows.size() || illWrap == -77) {
            return false;
        } else {
            return row >= illWrap;
        }
    }

    public void rotYearChange(int newYears) {
        if (newYears > kRotationYears) {
            for (int index = size() - 1; inRotatedArea(index); index--) {
                RowInfo current = mRows.get(index);
                if (current.getDisplayDate().get(Calendar.YEAR) > newYears) {
                    current.setYearOffset(newYears);
                    current.synchDates();
                } else if (current.getDisplayDate().get(Calendar.YEAR) <= newYears) {
                    current.enforceDisplayDate();
                }
            }
        } else if (newYears < kRotationYears) {
            for (int index = size() - getPivot(); index >= 0; index--) {

            }
        }
    }

    public void incPivot() {
        pivot++;
    }
    
    // -ihaas- Gets the file extension to figure out if it is a crop or upgm
    private String getFileExtension(String fileName) {
        String fileExtension = "";
        int indexOfExtension = fileName.lastIndexOf('.');
        
        if (indexOfExtension > -1) {
            fileExtension = fileName.substring(indexOfExtension+1);
        }
        return fileExtension;
    }
    
    // -ihaas- changes file extension to manx
    private String changeFileExtensionToMANX(String oldFileName) {
        String newFileName = oldFileName;
        int indexOfExtension = oldFileName.lastIndexOf(".");
        
        if (indexOfExtension > -1) {
           newFileName = oldFileName.substring(0, indexOfExtension);
           newFileName += ".manx";
        }
        
        return newFileName;
    }
    
    //-ihaas- Removes unneccessary params
    public void removeExtraParameters() {
        for (int i = 0; i < size(); i++) {
            OperationObject opObj = (OperationObject) getDataObject(i, XMLConstants.soperation);
            opObj.removeExtraParameters();
        }
    }

}
