/* Title:		OperationObject.java
 * Version	
 * Author		Sada
 * Company: USDA-ARS
 * Date:    July, 2003
 * Description: This class holds data of an operation (through actions).
 * 
 */
package usda.weru.mcrew;

import de.schlichtherle.truezip.file.TFile;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.apache.log4j.Logger;
import org.openide.util.Exceptions;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.TreeWalker;
import java.util.Hashtable;

import usda.weru.util.*;

/**
 * This class holds data of an operation (through actions).
 */
public class OperationObject extends DataObject implements Cloneable {

    private static final Logger LOGGER = Logger.getLogger(OperationObject.class);
    private Vector<Action> mActions; //Holds as many Actions (with data values) as required
    /**
     * The name of the operation whose data this object holds.
     */
    public String mOperationName; // Name of this particular operation

    private boolean mCropPresent;

    /** This Vector stores the identites of all actions for quick reference.
     * Also,  the vector is used to maintain the order of actions in this operationObject. Note that, Hashtables
     * do not maintain the order */
    Vector<Identity> mActionIdVec;
    
    /**
     * Instance of CropMeta to store limits and datatypes.
     */
    OperationMeta mOprnMeta = (OperationMeta) MCREWConfig.getObjectMeta(XMLConstants.soperation);
    /**
     * Stores the various identities that have out of range values;
     */
    Hashtable<Identity, InputLimits.TableStatus> actLim = new Hashtable<Identity, InputLimits.TableStatus>();

    /**
     * Returned if the file is read successfully.
     */
    public static final int kSuccess = 1;

    /**
     * When the errors are not defined or exceptions are not caught specifically then this
     * value is returned.
     */
    public static final int kUnknown_Error = -1;

    /**
     * If the file is missing or doesn't exist in that path then this value is returned.
     */
    public static final int kFile_NotFound = -2;

    /**
     * The default constructor that instantiates the various data structures to 
     * store the operation related meta data.
     */
    public OperationObject() {
        mActions = new Vector<Action>();
        mCropPresent = false; //no crop as default
        mObjectName = XMLConstants.soperation;
        mOperationName = XMLConstants.sNoName;
        mActionIdVec = new Vector<>();
    }

    /**
     * Single argument constructor that instantiates the various data structures to 
     * store the operation related meta data.
     * @param pObjectName The name of the object which is being created.
     */
    public OperationObject(String pObjectName) {
        mActions = new Vector<Action>();
        mObjectName = pObjectName;
        mCropPresent = false; //no crop as default
        mActionIdVec = new Vector<>();
    }

    /**
     * Two argument constructor that instantiates the various data structures to 
     * store the operation related meta data.
     * @param pObjectName The name of the object which is being created.
     * @param pOperationName The name of the operation for which this operation 
     * object is being created.
     */
    public OperationObject(String pObjectName, String pOperationName) {
        mActions = new Vector<Action>();
        mObjectName = pObjectName;
        mOperationName = pOperationName;
        mCropPresent = false; //no crop as default
        mActionIdVec = new Vector<>();
    }

    /**
     * Two argument constructor that instantiates the various data structures to 
     * store the operation related meta data.
     * @param pActions The vector of action objects that holds this operation object.
     * @param pObjectName The name of the object which is being created.
     */
    public OperationObject(Vector<Action> pActions, String pObjectName) {
        mActions = pActions;
        mObjectName = pObjectName;
        mCropPresent = false; //no crop as default
        mActionIdVec = new Vector<>();
    }

    /**
     *  Sets the operation name of this operation object to the value passed as argument.
     * @param pOperationName The new name to be set as operation name.
     */
    public void setOperationName(String pOperationName) {
        mOperationName = pOperationName;
    }

    /**
     * This method add the to the existing vector of actions for an operation. If its
     * a planting operation then a crop is present for that operation.
     * @param pAction The action object to be added.
     */
    public void addAction(Action pAction) {
        if (mActions == null) {
            mActions = new Vector<Action>();
        }

        mActions.add(pAction);
        mActionIdVec.add(pAction.getIdentity());

        if (pAction.isCrop()) //returns true if its a planting action (identity = G:03). The next action contains all param values
        {
            mCropPresent = true;
        }

    }

    /**
     * Fetches the action object from the vector sitting at index pIndex.
     * @param pIndex The index of the object in the vector.
     * @return The actin object being seeked for data retrieval or updation.
     */
    public Action getAction(int pIndex) {
        //System.out.println("OperationObject : getAction() :pIndex Value is : =========== : " + pIndex );
        return mActions.get(pIndex);
    }

    /**
     * Fetches the object that stores all the actions for an operation or a crop.
     * @return A vector object that stores all the action objects for the operation
     * being reffered. 
     */
    public Vector<Action> getAllActions() {
        return mActions;
    }

    /**
     * Method unused presently.
     * Fetches the operation action. There is only one operation action in any operation object. 
     * All other actions are process or group actions
     * @return The sole action object associated with the operation.
     */
    public Action getOperationAction() {
        for (Action action : mActions) {
            if (action.isOperation() == true) {
                return action;
            }
        }
        return null;
    }
    
    /**
     * This method fetches the CropMeta object itself
     * @return CropMeta
     */
    public OperationMeta getMeta()
    {
        return mOprnMeta;
    }
    
    public Hashtable<Identity, InputLimits.TableStatus> getActLim()
    {
        return actLim;
    }

    /**
     * Fetches the name of the current object .. operation/crop
     * @return The object name.
     */
    @Override
    public String getObjectName() {
        return mObjectName;
    }

    /**
     * Fetches the name of the current operation object being reffered
     * @return The operation name.
     */
    public String getOperationName() {
        return mOperationName;
    }

    /**
     * Fetches the datastructure that stores all the identity objects.
     * @return The vector that sores these identities associated with the
     * operation object.
     */
    public Vector<Identity> getAllIds() {
        return mActionIdVec;
    }

    /**
     * This method initializes the data or helps to acquire the required data for
     * initializing the various class level variables.
     * @param pNode The node whose data needs to be initialized.
     */
    @Override
    public void initialize(Node pNode) {
        DocumentTraversal traversable;

        if (pNode.getOwnerDocument() == null) {
            traversable = (DocumentTraversal) pNode;
        } else {
            traversable = (DocumentTraversal) pNode.getOwnerDocument();
        }

        TreeWalker walker = traversable.createTreeWalker(pNode, NodeFilter.SHOW_ALL, null, false);

        String nodeName = pNode.getNodeName();

        if (nodeName.equals(XMLConstants.soperationDB)) //it has to be ... just checking for confirmation
        {
            Node nodeChild = walker.firstChild(); // the node is one of the action (operationdefn) or paramdefn (crop_defn) from 
            while (nodeChild != null) {
                String nodeChildName = nodeChild.getNodeName();
                if (nodeChildName.equals(XMLConstants.soperationname)) {
                    String operationName = XMLDoc.getTextData(nodeChild);
                    //String operationName = walker.firstChild().getNodeValue();
                    //walker.parentNode(); //move walker to the operationName level

                    setOperationName(operationName);
                } else if (nodeChildName.equals(XMLConstants.sactionvalue)) {
                    Action action = new Action();
                    action.initialize(nodeChild);
                    if (action.isCrop()) {
                        //returns true if its a planting action (identity = G:03). 
                        //The next action contains all param values
                        mCropPresent = true;
                    }

                    addAction(action);

                }
                nodeChild = walker.nextSibling();
            }
        }//end if nodeName.equals("operationDB")

    }

    /**
     * Tells whether its a planting operation which will have a crop.
     * @return True if the crop is present forhtat operation else false.
     */
    public boolean hasCrop() {
        return mCropPresent;
    }

    /**
     * The method that fetches the name of the crop associated with the operation
     * or operation object.
     * @return The value of the crop name.
     */
    public String getCropName() {

        for (int i = 0; i < mActionIdVec.size(); i++) {
            Action action = mActions.get(i);

            if (action.isCrop()) {
                String cropName = (String) (action.getParameter(XMLConstants.sgcropname)).getValue();

                return cropName;

            }
        }
        return null;
    }

    /** 
     * This function is called when a new crop is inserted. then the action G:03 needs to be changed
     * @param pCrop The new crop that is to be assigned.
     */
    public void changeCrop(CropObject pCrop) {
        /* Search for the action G:03, change the crop name
         * The Action after action G03 would be the one with all the parameter values which are needed to be changed
         * It's not good to depned upon the order of action, but presently the data in operation and management files are 
         * in this manner. Theres no other way to identiy the crop with its action and parameter values
         */
        boolean ActionIsCropData = false; // to check if next action's parameters needs to be changed

        for (int i = 0; i < mActionIdVec.size(); i++) {
            Action cropAction = mActions.get(i);
            if (cropAction.isCrop()) {
                // parameter to be changed is crop name
                cropAction.changeParameterValue(XMLConstants.sgcropname, pCrop.getCropName());
                // Change all the parameter values of actions until the next action with a crop

                for (int j = i + 1; j < mActionIdVec.size(); j++) {
                    Action processAction = mActions.get(j);

                    if (processAction.isCrop()) {
                        break;
                    }
                    processAction.changeAllParameterValues(pCrop);
                }

                return;
            }
        }
    }

    /** 
     * This function constructs a cropObjct from the crop values (Action G:03 and P50 or P51)
     * Can call this function, when the crop values has to be copied from one OperationObject
     * to other or is needed for cross reference.
     * @return The orop object associated with an opration object.
     */
    public CropObject getCrop() {
        CropObject cropObj = new CropObject();

        String cropName = getCropName();
        cropObj.setCropName(cropName);

        /* Copy all the parameter values from P50 /P51 to the new CropObject
         */
        for (int i = 0; i < mActionIdVec.size(); i++) {
            Action action = mActions.get(i);
            if (action.isCrop()) {
                // get all the parameter values of next action which can be P510 or P51
                Action processAction = mActions.get(i + 1);

                cropObj.setCropParameters(processAction);

                return cropObj;
            }
        }

        return null;
    }

    /**
     *
     * @param pParamName
     * @param pUnit
     * @return
     */
    public String[] getValues(String pParamName, int pUnit) {
        if (pParamName.equals(XMLConstants.soperationname)) {
            return new String[]{mOperationName};
        }

        List<String> temp = new ArrayList<String>();

        //Loop over each action added the value of the parameter if it exists
        for (Action action : mActions) {
            Parameter parameter = action.getParameter(pParamName);
            if (parameter != null) {
                String value = parameter.get(XMLConstants.svalue);

                //Convert units if needed.
                if (value != null && pUnit == XMLConstants.kAlternateUnit) {
                    ActionMeta actionMeta = MCREWConfig.getObjectMeta(XMLConstants.soperation).getActionMeta(action.getIdentity());
                    if (actionMeta != null) {
                        ParameterMeta paramMeta = actionMeta.getParameterMeta(pParamName);
                        value = paramMeta.convertToAlternateUnits(value);
                    }
                }

                //Add the value to the temp list
                temp.add(value);
            }
        }

        //return as an array of Strings
        return temp.toArray(new String[temp.size()]);
    }

    /**
     *
     * @param pParamName
     * @param values
     * @param pUnit
     */
    public void setValues(String pParamName, String[] values, int pUnit) {
        //Loop over each action set the value of the parameter if it exists
        int i = 0;
        for (Action action : mActions) {
            Parameter parameter = action.getParameter(pParamName);
            if (parameter != null) {
                String value = values[i];
                //Next value
                i++;

                ActionMeta actionMeta = MCREWConfig.getObjectMeta(XMLConstants.soperation).getActionMeta(action.getIdentity());
                if (actionMeta != null) {
                    ParameterMeta paramMeta = actionMeta.getParameterMeta(pParamName);
                    //Convert units if needed.
                    if (value != null && pUnit == XMLConstants.kAlternateUnit) {
                        value = paramMeta.convertToPrimaryUnits(value);
                    }
                    value = paramMeta.convertToProperType(value);
                }
                parameter.setValue(value);

                if (i == values.length) {
                    break;
                }
            }
        }
    }

    /**
     * Fetches the value of an operation parameter with name pParamName.
     * @param pParamName The name of the parameter whose value is being seeked.
     * @return The string value being returned.
     */
    public String getValue(String pParamName) {
        if (pParamName.equals(XMLConstants.soperationname)) {
            return mOperationName;
        }
        for (Action action : mActions) {
            String colValue = action.getValue(pParamName);
            if (colValue != null) {
                return colValue;
            }
        }
        return null;
    }

    /**
     * Fetches the value of an operation parameter with name pParamName.
     * @param pParamName The name of the parameter whose value is being seeked.
     * @param pUnit The units in which the parameter is supposed to hold its value.
     * @return The string value being returned.
     */
    @Override
    public String getValue(String pParamName, int pUnit) {
        if (pParamName.equals(XMLConstants.soperationname)) {
            return mOperationName;
        }

        ////System.out.println("OperationObject" + "Trying to get value for col " + pColName);  	
		/* If pColName is not operation name, then its one of the parameters in one of the Actions
         * Search all the actions until you get the first parameterwith name = pColName
         */
        String returnValue = null;
        Identity actionId = null;
        for (Action action : mActions) {
            String colValue = action.getValue(pParamName);
            if (colValue != null) {
                returnValue = colValue;
                actionId = action.getIdentity();
                break;
            }
        }

        if ((returnValue != null) && (pUnit == XMLConstants.kAlternateUnit)) // i.e conversion is required
        {
            ActionMeta actionMeta = MCREWConfig.getObjectMeta(XMLConstants.soperation).getActionMeta(actionId);
            if (actionMeta != null) {
                ParameterMeta paramMeta = actionMeta.getParameterMeta(pParamName);

                returnValue = paramMeta.convertToAlternateUnits(returnValue);

            }
        }
        return returnValue;
    }

    /**
     * This method sets the value of the operation object's parameter for a specific
     * action object to values passed as argument
     * @param pParamName The name of the parameter whose value is to be changed 
     * private set to a new value.
     * @param pNewValue Tne new value to be assigned.
     * @param pUnit The units in which the new value will be stored.
     */
    @Override
    public void setValue(String pParamName, String pNewValue, int pUnit) {
        Identity actionId = null;
        Action action = null;
        String oldValue = null;

        for (Iterator<Action> it = mActions.iterator(); it.hasNext();) {
            action = it.next();
            oldValue = action.getValue(pParamName);
            if (oldValue != null) {
                /* First, get to know which action is it */
                actionId = action.getIdentity();
                break;
            }
        }
        ////System.out.println("OperationObject:setValue():pNewValue:"+pNewValue);
        String valueToSet = pNewValue;
        if (actionId != null) {
            Parameter parameter = action.getParameter(pParamName);
            oldValue = parameter.get(XMLConstants.svalue);

            ActionMeta actionMeta = MCREWConfig.getObjectMeta(XMLConstants.soperation).getActionMeta(actionId);
            if (actionMeta != null) {
                ParameterMeta paramMeta = actionMeta.getParameterMeta(pParamName);
                if (paramMeta != null) {
                    /* Values are always stored in Primary unit, convert it if displayed values are in Alternate unit */
                    if (pUnit == XMLConstants.kAlternateUnit) {
                        valueToSet = paramMeta.convertToPrimaryUnits(pNewValue);

                    }

                    if (!(oldValue.equals(valueToSet))) {
                        /* Values should be written out in proper type i,e if user types in 5.00 for an integer value, 
                         * then it should be converted to 5 before putting the into the parameter
                         */
                        String properTypeValue = valueToSet;
                        properTypeValue = paramMeta.convertToProperType(properTypeValue);

                        //System.out.println("OprnObj:setValue() " + "setting modified value " + pNewValue + " to " + valueToSet);	
                        //System.out.println("OperationObject:setValue():pNewValue:"+pNewValue+"  properTypeValue:"
                        //+properTypeValue+"  valueToSet:"+valueToSet);
                        parameter.setValue(properTypeValue);
                    }
                } // if(parameterMeta != null)
            } else {
                LOGGER.error("Could not find the actionMeta or parameterMeta. Setting new value anyway");

                parameter.setValue(pNewValue);
            }
        } else {
            //LOGGER.error("Could not find the action to which this parameter belongs. Discarding new value");
        }

    }

    /**
     * This method checks for parameter consistency and validity of the each action
     * based on its parameters' validity to XML's well formedness etc. 
     * @return Vector that holds all the invalid and inconsistent actions & parameters.
     */
    public Vector<Action> checkParameterConsistency() {
        Vector<Action> invalidActions = null;
        for (Action action : mActions) {
            Vector<String> invalidParams = action.checkParameterConsistency();

            if (invalidParams != null) {
                if (invalidActions == null) {
                    invalidActions = new Vector<>();
                }

                invalidActions.add(action);
            }
        }

        return invalidActions;
    }

    /** 
     * Fetches an operationDB node.
     * @param doc The document from where the node data or the node object is being retrieved. 
     * @return THe node object that holds or will hold the re-formatted data.
     */
    @Override
    public Node getNode(Document doc) {
        /**
         * Only this class knows the structure of an operationDB node and
         * so both reading from a (operaitonDB)Node and creating
         * new node are done in this class
         */

        Node operationDBNode = doc.createElement(XMLConstants.soperationDB);

        setNodeContents(operationDBNode, doc);

        return operationDBNode;
    }

    /* sets a given operationDBNode with the presnet Object contents
     */
    private void setNodeContents(Node pNode, Document pDoc) {
        Node operationNameNode = pDoc.createElement(XMLConstants.soperationname);
        XMLDoc.setTextData(operationNameNode, mOperationName, pDoc);

        pNode.appendChild(operationNameNode);

        for (int i = 0; i < mActionIdVec.size(); i++) {
            Action action = mActions.get(i);
            Node actionvalueNode = action.getNode(pDoc);

            pNode.appendChild(actionvalueNode);

        }
        /** Old implementation. Replaced by above code on May 30, 2003
         Enumeration enum = mActions.elements();
         while(enum.hasMoreElements())
         {
         Action action = (Action)enum.nextElement();
         Node actionvalueNode = action.getNode(doc);
			
         operationDBNode.appendChild(actionvalueNode);
         } */

    }

    private void setNodeContents(Node pNode, Document pDoc, String newFileName) {
        Node operationNameNode = pDoc.createElement(XMLConstants.soperationname);

        if (XMLConstants.soperationname.equals(operationNameNode.getNodeName())
                && (!operationNameNode.getNodeName().equals(newFileName))) {
            XMLDoc.setTextData(operationNameNode, newFileName, pDoc);
        }

        pNode.appendChild(operationNameNode);

        for (int i = 0; i < mActionIdVec.size(); i++) {
            Action action = mActions.get(i);
            Node actionvalueNode = action.getNode(pDoc);
            pNode.appendChild(actionvalueNode);

        }

    }

    /**
     * Makes a copy of the current object.
     * @return The new copy of the existing object.
     */
    @Override
    public Object clone() {
        //try
        {
            OperationObject copy = (OperationObject) super.clone();

            //System.err.println("OperationObject:clone" + " Cloning "+ mOperationName);
            copy.mOperationName = mOperationName;
            copy.mCropPresent = mCropPresent;

            copy.mActions = new Vector<>();
            for (Action action : mActions) {
                Action actionCopy = (Action) action.clone();
                copy.mActions.add(actionCopy);
            }

            // Now clone the mActionIdVec also
            // Direct cloning of a vector will only give a shallow copy
            copy.mActionIdVec = new Vector<>();
            for (Identity id : mActionIdVec) {
                Identity idCopy = (Identity) id.clone();
                copy.mActionIdVec.add(idCopy);
            }

            return copy;
        }
        /*catch(CloneNotSupportedException e) // raises the error 'Exception never thrown' in the preceeding body of try
         {
         System.err.println("OperationObject:Clone();" + "Clone Not Supported!");
         }
         return null; */
    }

    /** Read new operation xml file (*.oprn). Note that initialize() is called directly when reading from management
     * files. This function is called to read from operation files while adding new operations.
     * @param pFileName The XML file from which we get the data in a particular format.
     * @return positive integer if success else negative error code
     */
    @Override
    public int readXMLFile(String pFileName) {
        DataObject dataObject = null;
        Node root, node;
        Document doc;
        RowInfo row;
        doc = XMLDoc.getDocument(pFileName);
        if (doc == null) {
            return kUnknown_Error;
        }

        doc.normalize();
        initialize(doc.getDocumentElement());

        //handle the fix up of the ofuel from older versions
        Action action = getOperationAction();
        if (action.getParameter("ofuel") == null) {
            if (action.getIdentity().id == 3 || action.getIdentity().id == 4) {
                action.addParameter(new Parameter("ofuel", " "));
            }
        }

        if (checkForRequiredGroups(true)) {
            return kUnknown_Error;
        } else {
            return kSuccess;
        }
    }

    /**
     * Writes a new operation xml file. This function is called when user selcts to save a new operaition from Operation_Dlg.
     * @param pFileName The name of the file to which the data will be written.
     * @param hasCrop - true if the operation has a crop
     * @param withCrop - true if this new saved operation should also retain that crop
     * @return Returns a positive integer if the file was written successfully else
     * negative.
     */
    public int writeXMLFile(String pFileName, boolean hasCrop, boolean withCrop) {
        boolean writeFile = true;
        //If the operation is to be saved with no crop, then defer 
        // writing the operation file - Neha.
        if (hasCrop == true && withCrop == false) {
            writeFile = false;
        }
        String newOperFileName = null;
        if (!pFileName.endsWith(".oprn")) {
            pFileName = pFileName + ".oprn";
        }
        int index = pFileName.lastIndexOf("\\");
        if(index != -1) newOperFileName = pFileName.substring(index + 1, pFileName.lastIndexOf("."));
        else newOperFileName = pFileName.substring(pFileName.lastIndexOf("/") + 1, pFileName.lastIndexOf("."));
        //System.out.println("OperationObject : writeXMLFile : Filename OUTSIDE is - - -  " + newOperFileName );

        Node root;
        String nonEscapingElements[] = {XMLConstants.soperationname, XMLConstants.sname};

        try {

            /* createDocument function cretes a new Document with appropriate Doctype and stylesheet attached */
            Document operationDoc = XMLDoc.createDocument(XMLConstants.soperation);
            if (operationDoc == null) {
                System.err.println("OprnObject:writerXMLFile(): " + "operationDoc is null");
                return kUnknown_Error;
            }

            root = operationDoc.getDocumentElement();
            setNodeContents(root, operationDoc, newOperFileName);

            if (writeFile == true) {
                //BufferedWriter bw = new BufferedWriter(new FileWriter(pFileName));
                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(operationDoc, output);
            }
            /* If the Operation is to be saved with No Crop,then create an OperationObject 
             * that has the same properties as this operation, and then change its crop
             * to no_crop.crop and then write an XML file with the given file name - neha
             */
            if (hasCrop == true && withCrop == false) {
                OperationObject opObject = new OperationObject();
                opObject.initialize(root);
                String fullCropName = new TFile("mcrew_cfg", XMLConstants.sno_cropFileName).getAbsolutePath();
                Node cropNode = SkelImportPanel.loadXMLCropFile(fullCropName);
                CropObject crop = new CropObject();
                crop.initialize(cropNode);
                opObject.changeCrop(crop);
                opObject.writeXMLFile(pFileName, false, false);
            }
        } catch (DOMException e) {
            System.err.println("DomException");
        } catch (java.io.IOException e) {
            e.printStackTrace();
        } catch (InstantiationException | IllegalAccessException | ClassCastException | ClassNotFoundException ex) {
            Exceptions.printStackTrace(ex);
        }
        return kSuccess;
    }

    /**
     *
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final OperationObject other = (OperationObject) obj;
        if (this.mActions != other.mActions && (this.mActions == null || !this.mActions.equals(other.mActions))) {
            return false;
        }
        if (this.mOperationName != other.mOperationName && (this.mOperationName == null
                || !this.mOperationName.equals(other.mOperationName))) {
            return false;
        }
        if (this.mActionIdVec != other.mActionIdVec && (this.mActionIdVec == null
                || !this.mActionIdVec.equals(other.mActionIdVec))) {
            return false;
        }
        return true;
    }

    /**
     *
     * @return
     */
    @Override
    public int hashCode() {
        int hash = 3;
        hash = 37 * hash + (this.mOperationName != null ? this.mOperationName.hashCode() : 0);
        return hash;
    }

    /**
     * Checks the loaded operation processes for required groups.
     * @param showMessage Indicates if a WepsMessageDialog box should be shown with any errors
     * found.  The dialog is not displayed if there are no errors.
     * @return True when there were errors.  Returns false when there are no missing
     * groups.
     */
    public boolean checkForRequiredGroups(boolean showMessage) {
        WepsMessageLog log = new WepsMessageLog();
        boolean firstError = true;
        for (Object o : getAllActions()) {
            Action action = (Action) o;
            Identity id = action.getIdentity();
            ActionMeta actionMeta = MCREWConfig.getObjectMeta(XMLConstants.soperation).getActionMeta(id);
            Vector<Identity> applicableGroupIds = actionMeta.getGroupId();
            String applicableGroupList = "";

            if (applicableGroupIds != null) {
                for (Object q : applicableGroupIds) {
                    Identity qId = (Identity) q;
                    if (applicableGroupList.length() == 0) {
                        applicableGroupList = "Groups: [" + qId.toString() + "]";
                    } else {
                        applicableGroupList = applicableGroupList + ", [" + qId.toString() + "]";
                    }
                }

                //This action requires a group                    
                //Start at the current action and work our way up the list.
                boolean foundGroup = false;
                for (int actionIndex = getAllActions().indexOf(action); actionIndex >= 0 && !foundGroup; actionIndex--) {
                    Action testAction = this.getAction(actionIndex);
                    Identity testId = testAction.getIdentity();
                    for (Object p : applicableGroupIds) {
                        Identity testGroup = (Identity) p;
                        if (testGroup.equals(testId)) {
                            foundGroup = true;
                            break;
                        }
                    }
                }

                if (!foundGroup) {
                    if (firstError) {
                        log.logMessage(WepsMessage.errorMessage("Errors were found in the operation file.\n"));
                        firstError = false;
                    }
                    log.logMessage(WepsMessage.errorMessage(action.getActionName() + " [" + id.toString()
                            + "] requires a group. " + applicableGroupList));
                }
            }
        }

        if (log.hasMessage()) {
            if (showMessage) {
                //Show a warning message.                   
                WepsMessageDialog.showMessageList(null, "Missing Groups", MCREWConfig.getConfigDir(), log.getMessages());
            }
            return true;
        } else {
            return false;
        }

    }
    
    /**
     * Checks all of the data contained within the object.
     * @return tableStatus
     */
    @Override
    public InputLimits.TableStatus checkData()
    {
        InputLimits.TableStatus overall = InputLimits.TableStatus.OKAY;
        for(Action act : mActions)
        {
            InputLimits.TableStatus actVal = InputLimits.TableStatus.OKAY;
            for(String paramName : act.getParameterNames())
            {
                Parameter param = act.getParameter(paramName);
                String value = param.get("value");
                if(value == null) continue;
                ActionMeta actionMeta = mOprnMeta.getActionMeta(act.getIdentity());
                ParameterMeta limits = actionMeta.getParameterMeta(paramName);
                if(limits == null) continue;
                if(limits.getGroupAttribute(paramName, XMLConstants.svalueattribute).
                        equals(XMLConstants.schoice))
                {
                    lim.put(paramName, InputLimits.TableStatus.CHOICE);
                    continue;
                }
                if(limits.getGroupAttribute(paramName, XMLConstants.svalueattribute).
                        equals(XMLConstants.smultiline))
                {
                    lim.put(paramName, InputLimits.TableStatus.CHOICE);
                    continue;
                }
                InputLimits limiter = limits.getLimits();
                InputLimits.TableStatus currentCellStatus = limiter.evaluateInput(value);
                if(act.getIdentity().equals(new Identity(3, "G")))
                {
                    CropObject tempCrop = getCrop();
                    currentCellStatus = tempCrop.checkData();
                }
                lim.put(paramName, currentCellStatus);
                if(overall.lessThan(currentCellStatus))
                {
                    overall = currentCellStatus;
                }
                if(actVal.lessThan(currentCellStatus))
                {
                    actVal = currentCellStatus;
                }
            }
            actLim.put(act.getIdentity(), actVal);
        }
        return overall;
    }
    
    public ParameterMeta getParameterMeta(String paramName)
    {
        for(Action act : mActions)
        {
            ActionMeta actionMeta = mOprnMeta.getActionMeta(act.getIdentity());
            ParameterMeta parm = actionMeta.getParameterMeta(paramName);
            if(parm != null)
            {
                return parm;
            }
        }
        return null;
    }
    
    public String getNotes()
    {
        for(Action act : mActions)
        {
            if(act.getIdentity().code.equals("O"))
            {
                String poll = "op_notes" + act.getIdentity().id;
                return act.getValue(poll);
            }
        }
        return "Failed to find operation notes";
    }
    
    public void setNotes(String input)
    {
        for(Action act : mActions)
        {
            if(act.getIdentity().code.equals("O"))
            {
                String poll = "op_notes" + act.getIdentity().id;
                Parameter parm = act.getParameter(poll);
                parm.setValue(input);
            }
        }
    }
}
