/* Title:		Action.java
 * Version	
 * Author		Sada, Manmohan
 * Company: USDA-ARS
 * Date:    July, 2003
 * Description:  This class defines an Action. This is the data class which holds the action data for an
 * Operation Object.
 */
package usda.weru.mcrew;

import java.util.*;

import org.w3c.dom.*;
import org.w3c.dom.traversal.*;

/**
 * This class defines an Action. This is the data class which holds the action data for an
 * Operation Object
 * Class that puts the data in a format described in the XML file from Action element.
 * Action nodes are passed and objects are initialized with the data that goes along with
 * the descriptions and stored into a datastructure like vector, hashtable etc.
 */
public class Action implements Cloneable {

    private Identity mActionId;
    Hashtable<String, Parameter> mParameters;
    Vector<String> mParameterNames;
    
    /**
     * Default Constructor that instantiates the Vectors & Hashtables to store the data from
     * the files according to the XML description.
     */
    public Action() {
        mActionId = new Identity();
        mParameters = new Hashtable<String, Parameter>();
        mParameterNames = new Vector<String>();
    }

    /**
     * Constructor that instantiates an identity object & stores the value for that identity.
     * Hashtable storing different parameters and a vector storing those parameter names is
     * instantiated too.
     * @param pId The parameter identity name
     * @param pCode The parameter identity value
     */
    public Action(String pId, String pCode) {
        mActionId = new Identity(pId, pCode);
        mParameters = new Hashtable<String, Parameter>();
        mParameterNames = new Vector<String>();
    }

    /**
     * Constructor for Action object that assignes an existing identity object to itself.
     * Hashtable storing different parameters and a vector storing those parameter names are
     * instantiated too.
     * @param pActionId Identity of the Action object
     */
    public Action(Identity pActionId) {
        mActionId = pActionId;
        mParameters = new Hashtable<String, Parameter>();
        mParameterNames = new Vector<String>();
    }

    /**
     * Constructor that assigns an identity. Hashtable storing different parameters and a 
     * vector storing those parameter names are instantiated too.
     * @param pActionId Identity of the Action object
     * @param pParameter Object storing the list of parameters associated with that Action
     */
    public Action(Identity pActionId, Parameter pParameter) {
        mActionId = pActionId;
        mParameters = new Hashtable<String, Parameter>();
        mParameters.put(pParameter.getName(), pParameter);
        mParameterNames = new Vector<String>();
        mParameterNames.add(pParameter.getName());
    }

    /**
     * Method that adds a new parameter to the existing hashtable or creates a new
     * mParameters hashtable to populate it with the first parameter object.
     * @param pParameter The parameter object to be added
     */
    public void addParameter(Parameter pParameter) {
        if (mParameters == null) {
            mParameters = new Hashtable<>();
        }

        mParameters.put(pParameter.getName(), pParameter);
        mParameterNames.add(pParameter.getName());

    }

    /**
     * The only time this function is called is when there is a need to change
     * the crop name. This is called from change crop in OperationObject class
     * @param pName Name of the parameter whose value needs to be changed
     * @param pValue The new value of the paramenter
     */
    public void changeParameterValue(String pName, String pValue) {
        if (mParameters != null) {
            mParameters.remove(pName); // get the parameter named 'gcropname'
            Parameter parameter = new Parameter();
            parameter.initialize(pName, pValue);
            mParameters.put(pName, parameter); // parameters are stored according to their names
        }
    }

    /**
     * The only time this function is called is when there is a need to change
     * the values of all the crop names and their values. This is called from change crop
     * in OperationObject class
     * @param pCrop The crop object whose parameter names and values need to be changed.
     */
    public void changeAllParameterValues(CropObject pCrop) {
        if (mParameters != null) {
            Enumeration<Parameter> e = mParameters.elements();
            while (e.hasMoreElements()) {
                Parameter oldParameter = e.nextElement();
                Parameter newParameter = pCrop.getParameter(oldParameter.get(XMLConstants.sparamname));
                if (newParameter != null) {
                    oldParameter.changeValue(newParameter);
                }
            }
        }

    }

    /**
     * Returns true if the two actions have the same id and parameters.
     * Returns false otherwise.
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Action other = (Action) obj;
        if (this.mActionId != other.mActionId && (this.mActionId == null || !this.mActionId.equals(other.mActionId))) {
            return false;
        }
        if (this.mParameters != other.mParameters && (this.mParameters == null || !this.mParameters.equals(other.mParameters))) {
            return false;
        }
        if (this.mParameterNames != other.mParameterNames && (this.mParameterNames == null
                || !this.mParameterNames.equals(other.mParameterNames))) {
            return false;
        }
        return true;
    }

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

    /**
     * This method initializes the Action node (from weps .man file) with the values in the text file according to
     * the XML tree structure metioned in the file that is passed.
     * @param pNode The node whose values are being used to populate the datastructure.
     */
    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);
        Node nodeChild = walker.firstChild(); // nodeChild is 'identity' or 'actioname' or 'paramdefn'

        while (nodeChild != null) {
            String nodeChildName = nodeChild.getNodeName();
            
            if (nodeChildName.equals(XMLConstants.sidentity)) {
                Node code = walker.firstChild();
                String codeValue = XMLDoc.getTextData(code);

                //String codeValue = walker.firstChild().getNodeValue();
                //walker.parentNode(); // move the walker to the level of code / id. 
                Node id = walker.nextSibling();
                String idValue = XMLDoc.getTextData(id);
                //String idValue = walker.firstChild().getNodeValue();
                //walker.parentNode(); //get to the level of id /code

                Identity identity = new Identity(idValue, codeValue);

                mActionId = identity;
                
                walker.parentNode(); // get to the level of identity
            } else if (nodeChildName.equals(XMLConstants.sparam)) {
                Parameter parameter = new Parameter();

                Node paramNode = nodeChild;
                parameter.initialize(paramNode);

                /*
                 String paramname = parameter.get(XMLname);
                 if (mParameters == null)
                 mParameters = new Hashtable();
                 mParameters.put(paramname, parameter); // parameters are stored according to their names
                 mParameterNames.add(paramname);
                 */
                addParameter(parameter);

                //walker is already at the level of paramdefn
            }
            nodeChild = walker.nextSibling();
        } // end while
    }

    /**
     * Initalizes the node with the data from corresponding line of the .man file.
     * @param pDataLine The data string from the .man file
     * @param pLineNum The line number on which this data string exists
     */
    public void initialize(String pDataLine, int pLineNum) {
        StringTokenizer tokenizer;
        String token, paramName;
        String delimeter = " ";

        /* Note: The member variable mParameterNames contains only the names of the paramter for which
         * this action has the value, whereas the parameterNames from the paramFormatvec has names of 
         * all parameters this action can have.
         */
        // Get the order of the parameters from ConfigData
//                //System.out.println("Action:initialize() : Theres is this LINE # : " + pLineNum );
        Vector<String> paramFormatLine = MCREWConfig.getManFileFormat(mActionId, pLineNum);
//		//System.out.println("Action:initialize() : Theres is this TESTING LINE AFTERWARDS" );
        if (paramFormatLine == null) {	//System.err.println("Action:initialize()"+
            //"Theres is no man_format.xml data for the dataLine "+
            //         pDataLine + " of action" + mActionId);
            return;
        }
        /*
         Vector paramFormatVec = ConfigData.getManFileForamt(mActionId);
         if(paramFormatVec == null)
         {
         //System.err.println("Action:Initialize()->" + "Theres no action "+ mActionId  + " in man_format.xml");
         return;
         }
         Vector paramFormatLine;
         try{
         paramFormatLine = (Vector)paramFormatVec.get(pLineNum);
         }
         catch(ArrayIndexOutOfBoundsException e)
         {		
         //e.printStackTrace();
         //System.err.println("Action:initialize()"+ "Theres is no man_format.xml data for the dataLine "+
         pDataLine + " of action" + mActionId);
         return;
         } */
        int numParams = paramFormatLine.size();
//                //System.out.println("Action:initialize() : Number of parameters are : " +  numParams );
        if (numParams == 1) // no need to use Stringtokenizer
        {
            paramName = paramFormatLine.get(0);
            Parameter parameter = new Parameter(paramName, pDataLine);
            addParameter(parameter);
        } else {	// Now handle the normal case where in a data line contains various param 
            // values separated through single space (delimeter)
            int paramCount = 0;
            tokenizer = new StringTokenizer(pDataLine, delimeter);
            while (tokenizer.hasMoreElements()) {
                token = tokenizer.nextToken(); //the token is the parameter Value

                try {
                    paramName = paramFormatLine.get(paramCount++);
                    Parameter parameter = new Parameter(paramName, token);
                    addParameter(parameter);
                    ////System.out.println("Added "+ paramName + " Value "+ token + " @Action:initialize()");
                } catch (ArrayIndexOutOfBoundsException e) {
                    e.printStackTrace();
                    //System.err.println("@Action:initialize()-. Parameter number " + (paramCount -1)
                    //+ " with value = "+ token + " in action "+ mActionId + 
                    //	" is associated with no parameter in man_foramt.xml file "); 
                }

            }

        } // end else

    }

    /**
     * Returns the identity object associated with the ACTION object. This is generally used in
     * figuring out the object in the hashtable that stores it.
     * @return The identity object associated with the Action object.
     */
    public Identity getIdentity() {
        return mActionId;
    }

    /**
     * Getter Method returning the list of parameter names
     * @return Returns the vector containing the parameter names contained in the action
     * object.
     */
    public Vector<String> getParameterNames() {
        return mParameterNames;
    }

    /**
     * Getter Method returning the Parameter object associated with the parameter
     * name
     * @param pName The name of the parameter whose parameter object is requested
     * @return The requested parameter object
     */
    public Parameter getParameter(String pName) {
        if (mParameters != null) {
            return mParameters.get(pName);
        }

        return null;
    }

    /**
     * Getter method to get the name of the Action object
     * @return Return the action name by fetching it from the appropriate ActionMeta class
     */
    public String getActionName() {
        String actionName = null;
        ActionMeta actionMeta = MCREWConfig.getObjectMeta(XMLConstants.soperation).getActionMeta(mActionId);
        if (actionMeta != null) {
            actionName = actionMeta.getActionName();
        }

        return actionName;
    }

    /**
     * Tells whether its a crop object or not
     * @return Return true if the object is of type CropObject, otherwise false
     */
    public boolean isCrop() {
        return mActionId.equals(new Identity(XMLConstants.kCropId, XMLConstants.sCropCode));
    }

    /**
     * Tells whether its a OperationObject or not by checking if the identity is an operation 
     * i.e. checks if code = "O"
     * @return Return true if the object is of type OperationObject, otherwise false
     */
    public boolean isOperation() {
        if (mActionId.code.equals(XMLConstants.sOperationCode)) {
//                    //System.out.println("Action : isOperation() : Operation Code is : " + mActionId.code);
            return true;
        }
        return false;
    }

    /**
     * Getter method that fetches the value from the object whose name is pParamName
     * @param pParamName The name of the parameter whose object is to be retrieve and value fetched
     * @return Returns the value of the parameter object with name pParamName
     */
    public String getValue(String pParamName) {
        if (mParameters != null) {
            Parameter parameter = mParameters.get(pParamName);
            if (parameter != null) {
                return parameter.get(XMLConstants.svalue);
            }
        }

        return null;
    }

    /**
     * Getter method that returns the action node of the document.
     * @param doc The XML document from where we contruct the tree nodes using the
     * template to organize the data from our database for future access
     * using objects(DOM)
     * @return The node formed using this data
     */
    public Node getNode(Document doc) {
        Node actionvalueNode = doc.createElement(XMLConstants.sactionvalue);

        Node identityNode = mActionId.getNode(doc);
        actionvalueNode.appendChild(identityNode);

        for (String paramName : mParameterNames) {
            Parameter parameter = mParameters.get(paramName);
            Node paramNode = parameter.getNode(doc);

            actionvalueNode.appendChild(paramNode);
        }
        /* Old implementation
         Enumeration enum = mParameters.elements();
         while(enum.hasMoreElements())
         {
         Parameter parameter = (Parameter)enum.nextElement();
         Node paramNode = parameter.getNode(doc);
			
         actionvalueNode.appendChild(paramNode);
         }
         */
        return actionvalueNode;
    }

    /**
     * The method that makes a copy of the action object that stores the data in
     * a format defined in the opeation (def, display, lang ) & crop
     * (def, display, lang ) XML files.
     * @return Returns the object that was created after the copy object.
     */
    @Override
    public Object clone() {
        try {
            ////System.err.println("Action:clone" + " Cloning "+ mActionId);
            Action copy = (Action) super.clone();

            // copy.mParameters = (Hashtable)mParameters.clone(); // This just creates a shallow copy of the Hashtable
            copy.mParameters = new Hashtable<String, Parameter>();

            Enumeration<String> e = mParameters.keys();
            while (e.hasMoreElements()) {
                String key = e.nextElement();
                Parameter parameter = mParameters.get(key);

                String keyCopy = key;
                Parameter parameterCopy = (Parameter) parameter.clone();

                copy.mParameters.put(keyCopy, parameterCopy);

            }

            //	copy.mParameterNames = (Vector)mParameterNames.clone(); //This just creates a shallow copy of Vector.
            copy.mParameterNames = new Vector<String>();
            e = mParameterNames.elements();
            while (e.hasMoreElements()) {
                String paramName = e.nextElement();
                String paramNameCopy = paramName;

                copy.mParameterNames.add(paramNameCopy);
            }

            return copy;
        } catch (CloneNotSupportedException e) {
            //System.err.println("Action:Clone();" + "Clone Not Supported!");
            //throw new Error("This should never happen!"); 
        }

        return null;
    }

    /**
     * Checks if the parameters stored in the data structure are consistent with
     * the existing document that is being refered and also the sequence in which 
     * they appear to have stored.
     * @return The vector containing parameters which are in the data object (somehow) but not in the def'n
     */
    public Vector<String> checkParameterConsistency() {
        // Parameters which are in the data object (somehow) but not in the def'n
        Vector<String> invalidParams = null;

        // Cross check for all parameters from ActionMeta
        // a NPE here indicates that the jar is in the wrong spot
        ActionMeta actionMeta = MCREWConfig.getObjectMeta(XMLConstants.soperation).getActionMeta(mActionId);
        if (actionMeta == null) {
            return null;
        }

        Vector<String> metaParameters = actionMeta.getParameterNames();
		// the vector mParameterNames and parameters should have parameter names in same order

        // This is small heuruistis to redce the time checking for invlaid parameters
        if (metaParameters.size() == mParameterNames.size()) {
            ////System.out.println("Action:checkParameterConsistency" + "Action " + mActionId + ":
            //Number of parameters are same. Consistency is assumed");
            return null;
        }

        int metaSize = metaParameters.size();
        for (int i = 0; i < metaSize; i++) {
            try {
                String metaParamName = metaParameters.get(i);
                String existingParamName = mParameterNames.get(i);

                if (!(metaParamName.equals(existingParamName))) {
                    if (invalidParams == null) {
                        invalidParams = new Vector<String>();
                    }

                    invalidParams.add(metaParamName);
                }
            } catch (ArrayIndexOutOfBoundsException e) {
//				//System.out.println("Action:checkParameterConsistency" + " Trying to access parameter number "+ i + ".
                //There are only " + metaSize + " number of parameters in the Meta data");
            }
        }

        return invalidParams;

    }
}
