/* 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 e = mParameters.elements();
			while(e.hasMoreElements())
			{
				Parameter oldParameter = (Parameter)e.nextElement();
				Parameter newParameter = pCrop.getParameter(oldParameter.get(XMLConstants.sparamname));
				if(newParameter != null) {
                    oldParameter.changeValue(newParameter);
                }
			}
		}
		
	}

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

    @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 paramFormatLine = ConfigData.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 = (String)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 = (String)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 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 = ConfigData.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;
	}

	// FIXME - Change this get to getValue
    /**
     * 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 get(String pParamName)
	{
		if(mParameters != null)
		{ Parameter 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);
		
		/* Write out the parameters into the XML ie the order as they are in mParameterNames vector.
		 * Note mParameterNames  was constructed when a operation file was intially read in. So, the parameters
		 * in it are already in right order.
		 */
		for(int i = 0; i < mParameterNames.size(); i++)
		{
			String paramName = (String)mParameterNames.get(i);
			Parameter 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.
     */    
	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 e = mParameters.keys();
			while(e.hasMoreElements())
			{
				String key = (String)e.nextElement();
				Parameter 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 = (String)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 checkParameterConsistency()
	{
		Vector <String> invalidParams = null; // Parameters which are in the data object (somehow) but not in the def'n
		
		
		//Cross check for all parameters from ActionMeta
		ActionMeta actionMeta = ConfigData.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;
			
	}
}
