/* 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 javax.swing.*;
import java.util.*;

import org.w3c.dom.*;
import org.w3c.dom.traversal.*;
import org.w3c.dom.traversal.NodeFilter;
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 java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.Format;
import java.text.SimpleDateFormat;
import org.apache.log4j.Logger;
import org.openide.util.Exceptions;
import usda.weru.util.WepsFileTypes;

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 java.util.Hashtable;
import java.util.ArrayList;
import usda.weru.util.ConfigData;

/**
 * This is the class that holds the data for each row (through RowInfo object). It also
 * has various functions to deal with the operations like cut, copy, paste, read and
 * write data files.
 */
public class ManageData extends AbstractDataSource implements EditableTableDataModel {

    private static final long serialVersionUID = 1L;

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

    /**
     *
     */
    public enum WriteFileMode {

        /**
         *
         */
        NORMAL,
        /**
         *
         */
        UPDATE,
        /**
         *
         */
        FROM_NRCS
    }
    
    List<Integer> initOp;
    /**
     * String that indicates the start of the document.
     */
    public static final String sSTART = "START";
    /**
     * String that indicates the end of the document.
     */
    public static final String sEND = "END";
    /**
     * String that indicates the version cropNumber of the document.
     */
    public static final String sVersion = "Version:";
    /**
     * String that indicates that we reached the end of the file.
     */
    public static final String sEOF = "EOF";
    /**
     * String that preceeds the comments. It tells us where the comments are in
     * a .MAN file.
     */
    public static final String sCommentStr = "#------------";
    /**
     * If the document doesn't have a version number when it is being created then a default
     * version is attached to the document. This valiable store it.
     * 1.50 = Added ofuel to O03 and O04
     */

    /**
     * If the document doesn't have a version cropNumber when it is being created then a default
 version is attached to the document.This valiable store it.
 1.50 = Added ofuel to O03 and O04
     */
    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.60;
    
    /**
     * This holds the version number that will be printed when converting to previous versions.
     */
    public static final double VERSION_ARCHIVE = 1.50;
    /**
     * THis indicates whether or not to ignore this version of the document.
     */
    public static final String sIgnoreVersion = "IGNORE";
    // '#' followed by any other characters of any cropNumber
    //is also the currentVersion (now handles notes in crops and ops and man)
    //sDefaultVersion = "1.30", //is also the currentVersion
    //sOldVersion = "1.10" // ManageData cannot read old management file (New version are 1.2 or above)
    //sVersion = sDefaultVersion,
    /**
     * Indicates null line.
     */
    public static final char cNullLine = '?';
    /**
     * Indicates start (*START in file) or end (*END in file) line.
     */
    public static final char cStartEndLine = '*';
    /**
     * Indicator of version line that starts with character 'V' in .MAN file.
     */
    public static final char cVersionLine = 'V';
    /**
     * Indicator of start of the line with notes section of a .MAN file with character 'N'.
     */
    public static final char cManfileNotes = 'N';
    /**
     * Any line with first character as '#' is ignorable in weps management (.MAN) file as it
     * indicates that it is a comment line.
     */
    public static final char cCommentLine = '#';
    /**
     * This indicates that the line contains the details about the date when these operations
     * were done.
     */
    public static final char cDateLine = 'D';
    /**
     * This character indicates a start of an operation data line.
     */
    public static final char cOperationLine = 'O';
    /**
     * This character indicates a start of a process data line.
     */
    public static final char cProcessLine = 'P';
    /**
     * This character indicates a start of a group data line.
     */
    public static final char cGroupLine = 'G';
    /**
     * This character indicates us about the data that is available for various crop or
     * operations parameters from the .MAN file.
     */
    public static final char cParameterLine = '+';
    /**
     * This character indicates us about the textual description available for various crop
     * or operations parameter data accesssible from a .MAN file.
     */
    public static final char cParameterLineText = 'T';
        /**
     * This character indicates us about the developer textual description available for various crop
     * or operations parameter data accessible from a .MAN file.
     */
    public static final char cDevNotesLineText = 't';
    /**
     * This character indicates a line that will give us our pivot point: the row
     * in MCREW which should be the first line when the table is in NRCS mode.
     */
    public static final char cPivotLine = 'B';
    /**
     * Character that indicates the end of line for a data line.
     */
    public static final char cEndofLine = '<';
    /**
     * A line such as #---..repeated till 72nd column indicates the end of this set (one
     * row) of data.
     */
    public static final char cEndofRowDataLine = '-';
    /**
     * Constant value that is returned when a file is read successfully
     */
    public static final int kSuccess = 1;
    /**
     * Constant integer value returned when the errors occur due to an unknown reason
     * i:e for which and exception is not caught, etc.
     */
    public static final int kUnknown_Error = -1;
    /**
     * If the version of the .MAN file we are reffering to is below the minimum mentioned we
     * return this value to indicate that its a wrong version to pull the data from for our
     * analysis purposes.
     */
    public static final int kWrong_Version = -2;
    /**
     * If the appropriate file is not found we return this cropNumber
     */
    public static final int kFile_NotFound = -3;
    /**
     * Number of lines that are present in the notes section of a weps management file.
     */
    public static final int kCorruptedFile = -666;

    private static int mwepsmanfilenoteslines = 0;
    /**
     * Tells us the cropNumber of years it will take to make that soil arable for the same crop.
     */
    public int kRotationYears = 1;
    /**
     * The current version of the .MAN file.
     */
    public double version;
    /**
     * The text for the notes in a WEPS management file.
     */
    public String mwepsmanfilenotes = "";
    /**
     * Holds the RowInfo objects which in turn contains the data for each row.
     */
    private Vector<RowInfo> mRows;
    
    /**
     * 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
     */
    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;

    /**
     * Default constructor that initialises vector datastructure that stores the row data
     * from .MAN files for future use in the model.
     */
    public ManageData() {
        mRows = new Vector<RowInfo>();
        outHard = new ArrayList<int[]>();
        outSoft = new ArrayList<int[]>();
    }

    /**
     * This method fetches the cropNumber of rows that exist in the MCREW table as data sets.
     * @return The cropNumber of rows in a vector.
     */
    public int size() {
        return mRows.size();
    }

    /**
     * Method that clears all the row information associated with the MCREW table.
     */
    public void clear() {
        mRows.clear();
        kRotationYears = 1;
        version = VERSION_MINIMUM;
        fireDataReset();
    }

    /**
     * This method fetches the vector containing a rowInfo objects that holds all the
     * information about the table rows.
     * @return The vector containing the rowInfo objects that hold the information
     * on each row of the main MCREW table.
     */
    public Vector<RowInfo> getRows() {
        return mRows;
    }
    
    /**
     * This method fetches the list of values out of the hard limits
     * @return
     */
    public ArrayList<int[]> getHard()
    {
        return outHard;
    }
    
    /**
     * This method fetches the list of values out of the soft limits
     * @return
     */
    public ArrayList<int[]> getSoft()
    {
        return outSoft;
    }
    
    /**
     * This method fetches the status of saving on the table
     */
    public InputLimits.TableStatus getSave()
    {
        return allowSave;
    }
    
    /**
     *
     * @param index
     * @return
     */
    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;
    }

    /**
     * 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(Vector<JCCellRange> scv, DateChange dc) {
        //test only
        for (JCCellRange jccr : scv) {
            int sRow = jccr.start_row;
            int eRow = jccr.end_row;

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

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

            if (sRow > eRow) {
                int pivot = sRow;
                sRow = eRow;
                eRow = pivot;
            }
            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(Vector<JCCellRange> scv, JulianCalendar jc) {
        for (JCCellRange jccr : scv) {
            int sRow = jccr.start_row;
            int eRow = jccr.end_row;

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

            for (int rdx = sRow; rdx <= eRow; rdx++) {
                RowInfo ri;
                try {
                    ri = mRows.get(rdx);
                } catch (ArrayIndexOutOfBoundsException e) {
                    System.err.println("ManageData:ChangeDates(Vector, dateChange): "
                            + " Theres no row " + rdx);
                    continue;
                }
                ri.setDisplayDate((JulianCalendar) jc.clone());
                if(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(Vector<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 pivot = sRow;
                sRow = eRow;
                eRow = pivot;
            }

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

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

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

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

    /**
     *
     * @param startRow
     * @param endRow
     * @param field
     * @param value
     */
    public void changeDates(int startRow, int endRow, int field, int value) {
        changeDates(startRow, endRow, field, value, false);
    }

    /**
     *
     * @param startRow
     * @param endRow
     * @param field
     * @param value
     * @param testonly
     * @return
     */
    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(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
     * comapreTo 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 Comaprable Interface)
     * will not yeild correct results always because few Rows may not have any Dates at all.
     * Since all the null dates preeeced 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 thsi 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;
        }
    }

    /**
     *
     * @param pRowNum
     * @param pObjectName
     * @param pColName
     * @return
     */
    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 objectname 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);
        }
    }

    /**
     *
     * @param rows
     */
    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;

        switch (pObjectName) {
            case XMLConstants.soperation:
                dataObject = new OperationObject(pObjectName);
                break;
            case XMLConstants.scrop:
                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 unsuccessfull for " + pObjectName);
            return;
        }

        if ((pRowNum < 0) || (dataObject == null)) {
            return;
        }
        if(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.addDataObject(pObjectName, dataObject);
            insertRow(pRowNum, row);
        } else {
            row = mRows.get(pRowNum);
            if (row != null) {
                row.addDataObject(pObjectName, dataObject);
                fireRowChanged(pRowNum);
            }
        }
    }
    //Above function taken from ReadData

    /**
     * Reads the data file ( XML or .MAN ) contiaing the operation or crop data for
     * various crops.
     * @param pFileName Tne name of the file that contains this data.
     * @return If read successfully returns true else false.
     */
    public int readDataFile(String pFileName) {
        manFile = new TFile(new TFile(pFileName).getAbsoluteFile());
        if (pFileName.toLowerCase().endsWith(XMLConstants.sXMLFileExtension)) {
            int result = readXMLFile(pFileName);
            fixup();
            fireDataReset();
            return result;
        } else if (WepsFileTypes.Management.accept(manFile) || WepsFileTypes.Rotation.accept(manFile)) {
            int result = readManFile(pFileName);
            fixup();
            fireDataReset();
            return result;
        }

        return kUnknown_Error;
    }

    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." + manFile.getAbsolutePath(), e);
        }
    }

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

        rowList = new Vector<>();
        doc = XMLDoc.getDocument(pFileName);
        if (doc == null) {
            mRows = null;
            return kFile_NotFound;
        }
        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 less than " + VERSION_MINIMUM);
                    returnCode = kWrong_Version;
                }
                if (version > VERSION_CURRENT) {
                    LOGGER.error("Management version greater than " + VERSION_CURRENT);
                    returnCode = kWrong_Version;
                }
            } else if (nodeName.equals(XMLConstants.srotationyears)) {
                try {
                    kRotationYears = new Integer(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.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;
    }

    /**
     *
     */
    public TFile manFile;

    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);

            //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");
                    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 kUnknown_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) {
            e.printStackTrace();
            return kUnknown_Error;
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | ClassCastException ex) {
            Exceptions.printStackTrace(ex);
        }
        return kSuccess;
    }

    private int readManFile(String pFileName) {
        //int lineNum = 0;
        int returnCode = kSuccess;
        Vector<RowInfo> rowList = null;
        int rowCount = 0;
        OperationObject operationObject = null;
        RowInfo row = null;

        String dataLine = null;
        String delimeter = " "; // delimeter is single space
        String actionCode = null;
        String actionId = null;
        Action action = null;
        boolean textLine = false;
        String displayText = "";
        mwepsmanfilenotes = "";

        StringTokenizer tokenizer;

        BufferedReader br = null;
        try {
            br = new BufferedReader(new TFileReader(new TFile(pFileName)));
            dataLine = br.readLine();
            while (dataLine != null) {
                //Saftey check for a blank line.
                if (dataLine.length() == 0) {
                    dataLine = br.readLine();
                    continue;
                }

                // Remove the '<' char at the end of each line which is present in old man files
                int endIndex = dataLine.indexOf(cEndofLine); // 72nd char is '<' in older files only
                if (endIndex != -1) {
                    dataLine = dataLine.substring(0, endIndex);
                }
                char code = dataLine.charAt(0); // check the first char in the line
                ////System.out.println("starting char in Mcrew file:"+code);
                switch (code) {
                    case cStartEndLine:
                        if (dataLine.toLowerCase().contains(sSTART.toLowerCase())) {
                            rowList = new Vector<>();
                            //also get the rotation years value
                            int rotationIndex = dataLine.toLowerCase().indexOf(sSTART.toLowerCase()) + sSTART.length();
                            String rotationStr = dataLine.substring(rotationIndex).trim();
                            kRotationYears = new Integer(rotationStr);
                        } else if (dataLine.toLowerCase().contains(sEND.toLowerCase())) {
                            //check if there has bee any previous row
                            if (operationObject != null) {
                                row.addDataObject(operationObject.getObjectName(), operationObject);
                                rowList.add(row);
                                rowCount++;
                                operationObject = null;
                            }
                            mRows = rowList;
                            return returnCode;
                        }
                        break;
                    case cVersionLine:
                        // Read the version value
                        int versionIndex = dataLine.toLowerCase().indexOf(sVersion.toLowerCase()) + sVersion.length();

                        String versionText = dataLine.substring(versionIndex).trim();
                        try {
                            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 less than " + VERSION_MINIMUM);
                            returnCode = kWrong_Version;
                        }
                        if (version > VERSION_CURRENT) {
                            LOGGER.error("Management version greater than " + VERSION_CURRENT);
                            returnCode = kWrong_Version;
                        }

                        // Now substring starts two character after 'Version'.
                        // That's OK as we have ':' after 'Version' in the file.
                        break;
                    case cManfileNotes:
                        // Read the version value
                        try {
                            if (mwepsmanfilenoteslines == 0) {

                                mwepsmanfilenotes = mwepsmanfilenotes + dataLine.substring(2);
                                mwepsmanfilenoteslines++;
                            } else {
                                if (mwepsmanfilenotes.trim().equals("")) {

                                    mwepsmanfilenotes = mwepsmanfilenotes + dataLine.substring(2);
                                    mwepsmanfilenoteslines++;
                                } else {

                                    mwepsmanfilenotes = mwepsmanfilenotes + "\n" + dataLine.substring(2);
                                    mwepsmanfilenoteslines++;
                                }
                            }
                            mwepsmanfilenotes = XMLDoc.getStringFromXMLSafeString(mwepsmanfilenotes);
                            if (mwepsmanfilenotes != null) {
                                mwepsmanfilenotes = mwepsmanfilenotes.trim();
                            }
                        } catch (Exception e) {
                            LOGGER.warn("ManageData:readManFile:" + "No Management File Level Notes ", e);
                            mwepsmanfilenotes = "";
                        }

                        break;
                    case cCommentLine:
                        // nothing to do with the comment line
                        /*if(dataLine.charAt(1) == cEndofRowDataLine)
                         {
                         row.addDataObject(operationObject.getObjectName(), operationObject);
                         rowList.add(row);
                         rowCount++;
                         operationObject = null;
                         }*/
                        break;
                    case cDateLine:
                        //check if there has bee 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), delimeter); //leave out the first char
                        String date = null;
                        if (tokenizer.hasMoreElements()) {
                            date = tokenizer.nextToken();
                        }
                        row.setDate(date);
                        operationObject = new OperationObject();

                        break;
                    case cOperationLine:
                        String operationName = null;
                        try {
                            actionCode = dataLine.substring(0, 1);
                            // a string tokeinzer cannot be use as there no delimeter. Space cannot
                            //be used as a operation/ action name can have spaces
                            actionId = dataLine.substring(2, 4);
                            operationName = dataLine.substring(5).trim(); // Every line has '<' at the end
                        } catch (IndexOutOfBoundsException e) {
                            e.printStackTrace();
                        }

                        operationObject.setOperationName(operationName);

                        action = new Action(actionId, actionCode);
                        operationObject.addAction(action);


                        /* Note: the 'action' is not yet initialized with values. actionis initialized when you
                         * get next Parameter Line.
                         * This takes care of actions with no parameters as the new action line would be initialized before
                         * next parameter line
                         */
                        break;
                    case cProcessLine:
                    case cGroupLine:

                        try {
                            actionCode = dataLine.substring(0, 1);
                            // a string tokeinzer cannot be use as there no delimeter.Space cannot be used
                            // as a operation /action name can have spaces
                            actionId = dataLine.substring(2, 4);
                            String actionname = dataLine.substring(5).trim();
                            // action name not used. Action name can be obtained from actionMetas
                        } catch (IndexOutOfBoundsException e) {
                            e.printStackTrace();
                            System.err.println(" Line " + dataLine + " is shorter than normal" + " @ManageData:readManFile()");
                        }
                        action = new Action(actionId, actionCode);
                        operationObject.addAction(action);
                        /* Note: the 'action' is not yet initialized with values. actionis initialized when you
                         * get next Parameter Line.
                         * This takes care of actions with no parameters as the new action line would be initialized before
                         * next parameter line
                         */

                        break;
                    case cParameterLine:
                    case cParameterLineText: /*Fallthrough*/
                    case cDevNotesLineText:                        
                        int lineNum = 0;
                        //System.out.printf("%s\n", action != null ? action.getIdentity() : "null");
                        // Some actions have parameter values in more than line, so we need a lineNum count.
                        do {
                            if (action != null) {
                                //action is initilized under the case operationline,
                                //processLine or GroupLine in the previous run of the loop

                                if (dataLine.charAt(0) == cParameterLine) {

                                    //test to make sure we're not expecting a T line that is missing in the file
                                    List<Vector<String>> lineInfo
                                            = MCREWConfig.getManFileFormatInfo(action.getIdentity(), lineNum);

                                    if (lineInfo != null && lineInfo.size() > 0) {
                                        String expectedLineCode = lineInfo.get(0).get(0);
                                        if (expectedLineCode.equals("T") || expectedLineCode.equals("t")) { /* Added to allow "t" lines - LEW */
                                            action.initialize("", lineNum++);
                                        }
                                    }

                                    textLine = false;
                                    action.initialize(dataLine.substring(1).trim(), lineNum++);
                                    dataLine = br.readLine();
                                    endIndex = dataLine.indexOf(cEndofLine); // 72nd char is '<' in older files only
                                    if (endIndex != -1) {
                                        dataLine = dataLine.substring(0, endIndex); // Remove '<'
                                    }
                                } else if (dataLine.charAt(0) == cParameterLineText || dataLine.charAt(0) == cDevNotesLineText) { // Added "t" lines here - LEW
                                    StringBuilder bufferT = new StringBuilder();
                                    StringBuilder buffert = new StringBuilder();
                                    do {
                                        if(dataLine.charAt(0) == cParameterLineText)
                                        {
                                            bufferT.append(dataLine.substring(1));
                                            bufferT.append("\n");
                                            dataLine = br.readLine();
                                        }
                                        if(dataLine.charAt(0) ==cDevNotesLineText)
                                        {
                                            buffert.append(dataLine.substring(1));
                                            buffert.append("\n");
                                            dataLine = br.readLine();
                                        }
                                    } while (dataLine.charAt(0) == cParameterLineText || dataLine.charAt(0) == cDevNotesLineText); // Added "t" lines here - LEW

                                    action.initialize(bufferT.toString(), lineNum++);
                                    action.initialize(buffert.toString(), lineNum ++);
                                }
                            }

                        } while ((dataLine != null) && (dataLine.charAt(0) == cParameterLine
                                || dataLine.charAt(0) == cParameterLineText
                                || dataLine.charAt(0) == cDevNotesLineText)); // Added "t" line here - LEW                               

                        //test if there was an expected text line missing in the file
                        try {
                            List<Vector<String>> lineInfo = MCREWConfig.getManFileFormatInfo(action.getIdentity(), lineNum);

                            if (lineInfo != null && lineInfo.size() > 0) {
                                String expectedLineCode = lineInfo.get(0).get(0);
                                if (expectedLineCode.equals(String.valueOf(cParameterLineText)) || expectedLineCode.equals(String.valueOf(cDevNotesLineText))) { // Added "t" lines here - LEW
                                    //add blank text into the notes value.
                                    action.initialize("", lineNum++);
                                }
                            }
                        } catch (ArrayIndexOutOfBoundsException aioobe) {
                            //do nothing
                        }

                        // Now since dataLine is positioned at line after the paramLine, we dont need to read new line
                        continue;

                    case cPivotLine:
                        String[] parts = dataLine.split("|");
                        try { pivot = Integer.parseInt(parts[2]); }
                        catch(NumberFormatException nfe) { LOGGER.error("Incorrectly formatted pivot line in management file." + pFileName); }
                        break;
                        
                    default:
                        LOGGER.error("Unknown line in weps manangement data file: " + pFileName + "\n" + dataLine);
                        if(returnCode != kWrong_Version) returnCode = kCorruptedFile;
                        break;
                } // end switch
                dataLine = br.readLine();
            } // end while
            mwepsmanfilenoteslines = 0;
        } // end try // end try
        catch (FileNotFoundException e) {
            LOGGER.warn("File \"" + new TFile(pFileName).getAbsolutePath() + "\" does not exist.");
            return kFile_NotFound;
        } catch (IOException e) {
            LOGGER.warn("Unable to read manage file.", e);
            return kUnknown_Error;
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                LOGGER.error("Error reading management file.", e);
            }
        }

        mRows = rowList;
        manFile = new TFile(new TFile(pFileName).getAbsoluteFile());
        return returnCode;
    }

    /**
     * If the parameter pFileName has no extension then this function calls save function for saving both xml and
     * man files. If hte fileName alredy 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);
    }

    /**
     *
     * @param pFileName
     * @param mode
     * @return
     */
    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 kUnknown_Error;
            }
        }

        if (pFileName.endsWith(XMLConstants.sXMLFileExtension)) {
            return writeXMLFile(pFileName, mode);
        } else {
            return writeManFile(pFileName, mode);
        }
    }

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

        String symbolParameterLine = Character.valueOf(cParameterLine).toString();
        String symbolParameterLineText = Character.valueOf(cParameterLineText).toString();
        String symbolDevNotesLineText = Character.valueOf(cDevNotesLineText).toString();

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

            //always write the current version
            version = VERSION_CURRENT;

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

            dataLine = (new Character(cStartEndLine)).toString().concat(sSTART);
            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
                            + "This management file was originally converted from the NRCS standard XML format on - "
                            + new Date().toString();
                    break;
                case UPDATE:
                    mwepsmanfilenotes = mwepsmanfilenotes
                            + "This management file was updated to contain the most current crop "
                            + "and operation records for WEPS on - " + new Date().toString();
                    break;
            }

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

            dataLine = sCommentStr;
            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 = (new Character(cDateLine)).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);
                Vector<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++) {
                        /*Vector paramFormatLine = (Vector)ConfigData.getManFileFormat(actionId, i);
                         Vector paramFormatLine = (Vector)ConfigData.getManFileFormatInfo(actionId, i); */
                        Vector<Vector<String>> paramFormatLineInfo = MCREWConfig.getManFileFormatInfo(actionId, i);
                        String symbol = paramFormatLineInfo.get(0).get(0);
                        String spaceAfterSymbol = "";

                        /* if( symbol.equals( symbolParameterLine ) || symbol.equals(symbolParameterLineText) ){
                         spaceAfterSymbol = (String)paramFormatLineInfo.get(1);
                         }*/
                        if (symbol.equals(symbolParameterLine)) {
                            Vector<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(symbolParameterLineText)) {
                            // if( spaceAfterSymbol.equals(" ") ){
                            Vector<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 = symbolParameterLineText;
                            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(symbolParameterLineText, line[x]);
                                    dataLineText = dataLineText + dataLine;
                                }
                                //if( lineLength == 0 ){
                                if (dataLineText.equals("")) {

                                    dataLineText = symbolParameterLineText + "\n";
                                }
                                if (dataLineText != null) {
                                    pw.write(dataLineText);
                                    //pw.write("\n");
                                }
                                dataLine = symbolParameterLineText;
                            }
                            /*  }
                             if( !( spaceAfterSymbol.equals(" ") ) ){
                             pw.write(symbolParameterLineText);
                             pw.write("\n");
                             } */
                        }
                        if (symbol.equals(symbolDevNotesLineText)) {
                            // if( spaceAfterSymbol.equals(" ") ){
                            Vector<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 = sCommentStr;
                pw.write(dataLine);
                pw.write("\n");
            }

            dataLine = (new Character(cStartEndLine)).toString();
            dataLine = dataLine.concat(sEND);
            pw.write(dataLine);
            pw.write("\n");

            dataLine = (new Character(cStartEndLine)).toString();
            dataLine = dataLine.concat(sEOF);
            pw.write(dataLine);
            pw.write("\n");

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

        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return kFile_NotFound;
        }

        return kSuccess;
    }
    
    int writeOldFile(String pFileName) {
        String dataLine;

        String symbolParameterLine = Character.valueOf(cParameterLine).toString();
        String symbolParameterLineText = Character.valueOf(cParameterLineText).toString();
        String symbolDevNotesLineText = Character.valueOf(cDevNotesLineText).toString();

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

            double version = VERSION_ARCHIVE;

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

            dataLine = (new Character(cStartEndLine)).toString().concat(sSTART);
            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 = (new Character(cManfileNotes)).toString();
            dataLine = prettyConcat(dataLine, XMLDoc.getXMLSafeString(mwepsmanfilenotes));
            pw.write(dataLine);
            pw.write("\n");

            dataLine = sCommentStr;
            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 = (new Character(cDateLine)).toString();
                dataLine = prettyConcat(dataLine, date);

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

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

                String operationName = operationObj.getOperationName();
                Vector<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++) {
                        Vector<Vector<String>> paramFormatLineInfo = MCREWConfig.getManFileFormatInfo(actionId, i);
                        String symbol = paramFormatLineInfo.get(0).get(0);
                        String spaceAfterSymbol = "";
                        if (symbol.equals(symbolParameterLine)) {
                            Vector<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(symbolParameterLineText)) {
                            Vector<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 = symbolParameterLineText;
                            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(symbolParameterLineText, line[x]);
                                    dataLineText = dataLineText + dataLine;
                                }
                                if (dataLineText.equals("")) {

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

            dataLine = (new Character(cStartEndLine)).toString();
            dataLine = dataLine.concat(sEND);
            pw.write(dataLine);
            pw.write("\n");

            dataLine = (new Character(cStartEndLine)).toString();
            dataLine = dataLine.concat(sEOF);
            pw.write(dataLine);
            pw.write("\n");
            pw.flush();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return kFile_NotFound;
        }

        return kSuccess;
    }

    List<String> plantKillDates;
    public boolean checkForPlantKills()
    {
        plantKillDates = new ArrayList<String>();
        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 Vector<Integer> checkIfAllDatesPresent() {
        Vector<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 Vector<Integer>();
                }
                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 Vector<Integer> checkEmptyRows() {
        //Check for blank lines in the data
        Vector<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 Vector<Integer>();
                }
                emptyRows.add(i);
            }
        }

        return emptyRows;
    }

    /**
     * This function concates 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(" " + result[i]);
                } else {
                    buffer.append("\n" + "N " + 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(Vector<JCCellRange> scv) {
        clearDeleted();
        try {
            for (JCCellRange jccr : scv) {
                int sRow = jccr.start_row;
                int eRow = jccr.end_row;
                if (sRow > eRow) {
                    int pivot = sRow;
                    sRow = eRow;
                    eRow = pivot;
                }
                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 ");
        }
    }
    Vector<Integer> deletedRowNums = new Vector<>();
    Vector<RowInfo> deletedRows = new Vector<>();

    /**
     * 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(Vector<Integer> pRowNums) {
        clearDeleted();
        try {
            for (int i = 0; i < pRowNums.size(); i++) {
                deletedRowNums.add(i);
                int indexToDelete = pRowNums.get(i).intValue() - 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 ");
        }
    }

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

    /**
     *
     * @return
     */
    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.lastElement(), deletedRows.lastElement());
            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, Vector<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 rowNum The place in the table where this row needs to be inserted.
     */
    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.setDisplayDate(beforeCal);
        }
        if(inRotatedArea(pRowNum)) pivot ++;
        insertRow(pRowNum, pRow);
    }
    Vector<RowInfo> clipBoardRows = new Vector<>();

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

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

            if (sRow > eRow) {
                int pivot = sRow;
                sRow = eRow;
                eRow = pivot;
            }
            
            //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.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(Vector<JCCellRange> scv) {
        clearDeleted();
        clipBoardRows.clear();
        for (JCCellRange jccr : scv) {
            int sRow = jccr.start_row;
            int eRow = jccr.end_row;

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

            //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(Vector<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, new Comparator<RowInfo>() {

            @Override
            public int compare(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);
            if(r1.getDisplayDate().compareTo(r2.getDisplayDate()) == 1) return false; 
            else return true;
        }
        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;

    /**
     * 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 writting the data
     * from those paramenters when requested.
     * @return This vector contains all the parameters that are insonsitent with
     * the model.
     */
    public Vector<OperationObject> checkParameterConsistency() {
        Vector<OperationObject> invalidOprns = null;

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

        //return null;
    }

    /**
     *
     * @param msgs
     * @return
     */
    public int checkAllConditions(List<String> msgs) {

        int results = 0;

        results |= checkVersion(msgs);

        results |= checkIfAllDatesPresent(msgs);

        results |= checkIfSorted(msgs);

        results |= checkEmptyRows(msgs);

        results |= checkRotYears(msgs);

        results |= checkParameterConsistency(msgs);

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

    /**
     *
     * @param msgs
     * @return
     */
    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) {
        Vector<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 incresing 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;
    }

    /**
     *
     * @param msgs
     * @return
     */
    public int checkEmptyRows(List<String> msgs) {
        Vector<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;
    }

    /**
     *
     * @param msgs
     * @return
     */
    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;
        }
    }

    /**
     *
     * @param msgs
     * @return
     */
    public int checkParameterConsistency(List<String> msgs) {
        Vector<OperationObject> invalidOprns = checkParameterConsistency();
        if (invalidOprns == null) {
            return CHECK_PASSED;
        }
        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);

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

                Vector<String> invalidParams = invalidAction.checkParameterConsistency();
                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_FAILED;
    }

    /**
     *
     * @param msgs
     * @return
     */
    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;
        }
    }

    /**
     *
     * @param msgs
     * @return
     */
    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;
    }

    /**
     *
     * @param msgs
     * @return
     */
    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<OperationObject>();

                        //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;
    }

    /**
     * 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.size() != 0) 
        {
            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<CropIntervalInfo>();
        //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.size() == 0) 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<Integer>();
        ArrayList<Integer> kills = new ArrayList<Integer>();
        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);
            }
            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.size() == 0) 
        {
            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.size() == 0) 
            {
                intervals.add(new CropIntervalInfo(plants.get(0), plants.get(0), 0, plants.get(0)));
                return;
            }
            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
        {
            //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;
                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)
                {
                    //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;
    }

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

        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 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;
        }

        /**
         *
         * @return
         */
        public String getCrop() {
            return crop;
        }

        /**
         *
         * @return
         */
        public Date getEnd() {
            return end;
        }

        /**
         *
         * @return
         */
        public int getNumber() {
            return number;
        }

        /**
         *
         * @return
         */
        public Date getStart() {
            return start;
        }

    }

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

    /**
     *
     * @param obj
     * @return
     */
    @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;
        }
        if (this.mRows != other.mRows && (this.mRows == null || !this.mRows.equals(other.mRows))) {
            return false;
        }
        return true;
    }

    /**
     *
     * @return
     */
    @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;

        /**
         *
         * @param row
         * @param crop
         * @param first
         * @param last
         */
        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<Integer>();
        allowSave = InputLimits.TableStatus.OKAY;
        ArrayList<Highlight> highlighter = new ArrayList<Highlight>();
        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;
            }
            Vector<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(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(crpVal != null)
                    {
                        if(limited.lessThan(crpVal)) 
                        {
                            limited = crpVal;
                        }
                    }
                }
                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()
    {
        Vector<RowInfo> temp = new Vector<RowInfo>();
        for(int index = pivot; index < mRows.size(); index ++) temp.add(mRows.get(index));
        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
    {
        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
     * @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 >= mRows.size() - pivot); }
    
    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 ++; }
}
