/* CropObject This class holds all crop data
 * Operation objects have all the values of crop under their actions (G:03, P:51). Only time a crop object 
 * is used is when the user selects new crop from the table. Then the values from crop object are later
 * copied onto the appropriate paramters in the actions of Operation object.
 */
package usda.weru.mcrew;


import de.schlichtherle.io.File;
import de.schlichtherle.io.FileWriter;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.apache.log4j.Logger;
import org.apache.xml.serialize.*;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.TreeWalker;



/**
 * This object holds all the information about a crop. It helps to read the
 * information specific to this crop from the management(.man) and/or XML
 * files and be able to initialize/update the data coming from various
 * screens of the GUI model.
 */
public class CropObject extends DataObject implements Cloneable
{
    private final static Logger logger = Logger.getLogger(CropMeta.class);
    /**
     * The name of the current crop object
     */    
	public String mCropName; // Name of this particular operation
	private Hashtable mParameters; // to hold all parameter values;
	Vector mParameterNames; 
    
    /**
     * If the .crop file is found it returns 1
     */
    public static final int kSuccess = 1;
    
    /** Flag if true indicates that atleast one category for this object
     * does not have any parameter and so need not display that category 
     * in the crop drilldown screen- neha
   */
    public boolean categoryEmpty = false;
    
    /** Flag - true if all the parameters in a particular category are 
     * hidden (a H display attribute), if true we hide that category
     * in the drilldown screen - neha
     */
    public boolean allParamsHidden = false;
    /**
     * If an error type relating the file is not found we use this value.
     */
    public static final int kUnknown_Error = -1;
    
	/* 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 
	 **/
    /**
     * If the .crop file we are looking for is not found we return -2
     */    
	public static final int
		kFile_NotFound = -2
		;
		
    /**
     * The constructor for crop object that defines the hashtable holding
     * all the parameter objects for the crop.
     */    
	public CropObject()
	{
		mObjectName = XMLConstants.scrop;
		mCropName = "No Crop";
		mParameters = new Hashtable();
		mParameterNames = null;
	}
	
    /**
     * The constructor for crop object that defines the hashtable holding
     * all the parameter objects for the crop
     * @param pObjectName The name of the object that is being created.
     */    
	public CropObject(String pObjectName)
	{
		mObjectName = XMLConstants.scrop;
		mCropName = "No Crop";
		mParameters=  new Hashtable();
		
	}
	
    /**
     * The constructor for crop object that defines the hashtable holding
     * all the parameter objects for the crop
     * @param pObjectName The name of the object that is being created.
     * @param pCropName The name of the crop whose object is being created.
     */    
	public CropObject(String pObjectName, String pCropName)
	{
		mObjectName = XMLConstants.scrop;
		mCropName = pCropName;
		mParameters = new Hashtable();
	}
	
	/* Constructor to construct a Crop object from the action in an Operation Object
	 *
	 */
	
    /**
     * A method that sets the name of the crop to the whatever string is being
     * passed as an argument.
     * @param pCropName The sting that store the name of the crop.
     */    
	public void setCropName(String pCropName)
	{
		mCropName = pCropName;
	}
	
    /**
     * A method that fetches the current object name.
     * @return The name of the crop object
     */    
	public String getObjectName()
	{
		return mObjectName;
	}
	
    /**
     * A method that fetches the current crop name.
     * @return The name of the crop itself 
     */    
	public String getCropName()
	{
		return mCropName;
	}
	
    /**
     * Initialize function can be called while reading in a new crop XML file where 
     * the document is traversed through node by node and if its a crop node then 
     * its meta data like name, parameters are pulled, initialised and the data is set.
     * @param pNode Its the node that may consist of an operation or crop details.
     */
    
	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.scropDB))
		{	
			Node nodeChild = walker.firstChild();
			while(nodeChild != null) //No need to loop as the root itself is passed to action
			{
				String nodeChildName = nodeChild.getNodeName();
				if(nodeChildName.equals(XMLConstants.scropname))
				{
					String cropName = XMLDoc.getTextData(nodeChild);
					//String cropName = walker.firstChild().getNodeValue();
					//walker.parentNode();
					
					setCropName(cropName);
				}
				else if(nodeChildName.equals(XMLConstants.sparam))
				{
					Parameter parameter = new Parameter();
					parameter.initialize(nodeChild);
				
					String pname = parameter.get(XMLConstants.sname);
					//addParameter(parameter);
					mParameters.put(pname, parameter);
				}
				nodeChild = walker.nextSibling();
			} // end while	
		} // if(nodeName.equals(XMLcropDB))
	}
	
    /**
     * This method Only this class knows the structure of a cropDB node and so both reading from
     * (cropDB)Node and creating new node are done here.
     * @param doc The document object that contains the crop data for various crop nodes.
     * @return cropDB node whose node contents were set from the document that was parsed
     */ 
    
	public Node getNode(Document doc)
	{
		/* 
		 */
		Node cropDBNode = doc.createElement(XMLConstants.scropDB);
		
		setNodeContents(cropDBNode, doc);
		
		return cropDBNode;
	}
	
	
	/**
     * Sets a given cropDBNode with the present Object contents. The template it uses 
     * comes from the various crop XML files
     * @param pNode THe node whose contents such as crop name, parameters etc. need
     * to be set.
     * @param pDoc The document that contains the data which will be set as node contents
     * of the crop with name cropname.
	 */
	private void setNodeContents(Node pNode, Document pDoc)
	{
		Node cropNameNode = pDoc.createElement(XMLConstants.scropname);
                logger.debug("CropObject :setNodeContents : The Document name is : " + pDoc.getNodeName() + "   The Document Value  is : " + pDoc.getNodeValue());
		XMLDoc.setTextData(cropNameNode, mCropName, pDoc);
	
		pNode.appendChild(cropNameNode);
		
		for(int i = 0; i < mParameterNames.size(); i++)
		{
			String paramName = (String)mParameterNames.get(i);
			Parameter parameter = (Parameter)mParameters.get(paramName);
			Node paramNode = parameter.getNode(pDoc);
			
			pNode.appendChild(paramNode);
		}
        
		/* 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);
		} */
				
	}
        
    /**
     * Sets a given cropDBNode with the present Object contents. The template it uses 
     * comes from the various crop XML files
     * @param pNode THe node whose contents such as crop name, parameters etc. need
     * to be set.
     * @param pDoc The document that contains the data which will be set as node contents
     * of the crop with name cropname.
     * @param newFileName The name of the file being served as the crop name as the
     * data fpr this crop will be stored here in this file.
     */
    
        private void setNodeContents(Node pNode, Document pDoc, String newFileName)
	{
            
            Node cropNameNode = pDoc.createElement(XMLConstants.scropname);
            logger.debug("CropObject :setNodeContents : The Document name is : " + pDoc.getNodeName() + "   The Document Value  is : " + pDoc.getNodeValue());
            if( cropNameNode.getNodeName().equals(XMLConstants.scropname) && ( !mCropName.equals( newFileName )) ){
                    mCropName = newFileName;
            }
            XMLDoc.setTextData(cropNameNode, mCropName, pDoc);
            pNode.appendChild(cropNameNode);
                
            Vector missingParameters = new Vector();
            boolean paramPresent = false;
            String defaultParamName = "";
            //Vector defaultCropParameterNames = null;
            CropObject defaultCrop = null;
            //Atleast one category under this crop object has no parameter objects
            if(categoryEmpty == true){
                //get the no_crop XML file into a Node
                String fullCropName = new File("mcrew_cfg", XMLConstants.sno_cropFileName).getAbsolutePath();
                Node cropNode = SkelImportPanel.loadXMLCropFile(fullCropName);
                //create a CropObject and initialize it with the node from the no_crop XML file
                defaultCrop = new CropObject();
                defaultCrop.initialize(cropNode);
               /** Get the parameters from the no_crop and the parameters from "this" CropObject
                * and get those parameters that are NOT in "this" CropObject and present
                * in no_crop. Add these parameters to the new crop file that is being written - neha
                */
                Enumeration entries = defaultCrop.mParameters.keys();
                while(entries.hasMoreElements()){
                    paramPresent = false;
                    defaultParamName = (String)entries.nextElement();
                    for(int k = 0; k < mParameterNames.size(); k++){
                        String paramName = (String) mParameterNames.get(k);
                        if(defaultParamName.equals(paramName) == true){
                            paramPresent = true;
                            break;
                        }
                   }//end of for loop on mParameterNames vector
                    if(paramPresent == false){
                      //  //System.out.println("defaultParamName:"+defaultParamName);
                        missingParameters.add(defaultParamName);
                    }
                }//end of while loop
                //Add the missing parameters to the pNode
                for(int p = 0; p < missingParameters.size(); p++){
                    String missingParamName = (String)missingParameters.get(p);
                    Parameter parameter = (Parameter)defaultCrop.mParameters.get(missingParamName);
                    Node paramNode = parameter.getNode(pDoc);
                    pNode.appendChild(paramNode);
                }
            }//end of if categoryEmpty is true
            
                //System.out.println("The size of vector mParameterNames is:"+mParameterNames.size());
		for(int i = 0; i < mParameterNames.size(); i++)
		{
			String paramName = (String)mParameterNames.get(i);
                        ////System.out.println("paramName:"+paramName);
			Parameter parameter = (Parameter)mParameters.get(paramName);
                        Node paramNode = parameter.getNode(pDoc);
			pNode.appendChild(paramNode);
		}
                
						
	}	
	
    /**
     * Read new crop xml file (*.oprn). Note that initialize() is called directly
     * when reading from management files. This function is called to read from
     * new crop when adding a crop from Table.java. Also when data from a XML file needs 
     * to be displayed on the main MCREW screen and the corresponding crop/operation screens
	 * @param pFileName The XML file that needs to be read with the crop/operation 
     * formatted data.
     * @return A positive integer if the file is read successfully else negative 1 which means error code
     *
     */        
               
	public int readXMLFile(String pFileName)
	{
                logger.debug("XML Filename is :-" + 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());
	
		return kSuccess;		
		//return 1;
	}

    /**
     *
     * Writes a new crop xml file. This function is called when user selcts 
     * to save a new crop from crop Drill down screen.
     * @param pFileName The name of the file to be used for saving the data.
     * @return A positive integer if the file was saved successfully else negative 
     */    
       
	public int writeXMLFile(String pFileName)
	{   String newFileName1 = null;
            if(!pFileName.endsWith(".crop")){
                pFileName = pFileName+".crop";
            }
         //   //System.out.println("pFileName:"+pFileName);
                newFileName1 = pFileName.substring(pFileName.lastIndexOf("\\") + 1, pFileName.lastIndexOf(".") );
            if(newFileName1 == null){
                newFileName1 = pFileName.substring(pFileName.lastIndexOf("/") + 1, pFileName.lastIndexOf(".") );
            }
            //System.out.println("CropObject : writeXMLFile : Filename OUTSIDE is - - -  " + newFileName1 );
            Node root;
            
            String nonEscapingElements[] = {XMLConstants.scropname, XMLConstants.sname};
            //System.out.println("CropObject : writeXMLFile : - scropname is : - - -  " + XMLConstants.scropname + "sname is : - - -  " + XMLConstants.sname );
           
            try{
			
			/* createDocument function creates a new Document with appropriate Doctype and stylesheet attached */
			Document cropDoc = XMLDoc.createDocument(XMLConstants.scrop); 
                        //System.out.println("CropObject : writeXMLFile : - scrop is : - - -  " + XMLConstants.scrop );
			if(cropDoc == null)
			{
				System.err.println("OprnObject:writerXMLFile(): "+ "cropDoc is null");
				return kUnknown_Error;
			}
			
			/* Note: 
			 */
			root = cropDoc.getDocumentElement();
            //System.out.println("CropObject : writeXMLFile : ROOT NodeName is - - -  " + root.getNodeName() );
			setNodeContents(root, cropDoc, newFileName1);
						
			OutputFormat format = new OutputFormat(cropDoc);
			format.setIndenting(true);
			format.setIndent(XMLConstants.INDENT);
			format.setEncoding(XMLConstants.sEncoding);
			format.setNonEscapingElements(nonEscapingElements);
			format.setMediaType(XMLConstants.sMediaType);
			format.setVersion(XMLConstants.sVersion);
		
			//BufferedWriter bw = new BufferedWriter(new FileWriter(pFileName));
			//System.out.println("CropObject : writeXMLFile : Filename INSIDE is - - -  " + pFileName );
                        FileWriter fw = new FileWriter(pFileName);
			
			XMLSerializer serializer = new XMLSerializer(fw,format);
			serializer.serialize(cropDoc);
		}
		catch(DOMException e)
		{
			System.err.println("DomException");
		}
		catch(java.io.IOException e)
		{
			e.printStackTrace();
		}
		return kSuccess;
	}
	
	
	/**
     * This function can be called to initialize all parameter values from an already 
	 * existing crop action (P51 or anuy other crop Action) within a OperationObject
	 * Note that cropObject by itself does not have actions.
	 * Also, P51 has three extra parameters that a crop object does not have. These parameters 
	 * are removed.
     * @param pAction The action object that contains these parameters and other data 
     * like the identity, etc.
     */    
        
	public void setCropParameters(Action pAction)
	{
		CropMeta cropMeta = (CropMeta)ConfigData.getObjectMeta(XMLConstants.scrop);
		if(cropMeta == null)
		{
			System.err.println("CropObject:setCropParameters: " + "No CropMeta object found");
			return;
		}
		
		/* Get the parameter names from CropMeta object. Retireve the values for those parameters from
		 * pAction and then copy it onto the current ctopObject
		 */
		mParameterNames = new Vector();
		 
		List <String> cropMetaParameters = cropMeta.getParameterNames();
		//Vector actionParameters = pAction.getParameterNames();
		
		for(int i = 0; i < cropMetaParameters.size(); i++)
		{
			String name = (String)cropMetaParameters.get(i);
			
			Parameter parameter = (Parameter)pAction.getParameter(name);
			
			if(parameter != null)
			{
				mParameters.remove(name); // Remove older one
				mParameters.put(name, parameter);	 // Add the newer one
				logger.debug("Parameter Name added is :-" + name + "  and its value is : "+ ( (String)parameter.getValue() ) );
                                ////System.out.println("CropObject: setCropParameters(): Parameter Name is :-" + name);
				mParameterNames.add(name);
			}
			else{
                            logger.warn("Cropobject does not have value for parameter "+ name);
                        }				

				
		}

	}
	
        
    /**
     * This method fetches the parameters present in each category of the
     * crop are divided into for their classification purposes. 
     * An example could be found in the crop_display XML file that tells you 
     * about various category parameters.
     * @return The hashtable that contains the various category parameters 
     */        
        public Map <String, List<String>> getCategoryParams(){
            CropMeta cropMeta = (CropMeta)ConfigData.getObjectMeta(XMLConstants.scrop);
            if(cropMeta != null){
                Map <String, List<String>> cP = cropMeta.getCropCatAndParams();

             return cP;
            }else
                return null;
        }     
        
    /**
     * This method fetches the list of category names for a crop and the
     * crop are divided into for their classification purposes. 
     * An example could be found in the crop_display XML file that tells you 
     * about various category names used as Tab headers in the crop_Dlg screen.
     * @return The vector that contains the list of category names for that crop.
     */        
        public List <String> getCategoryNames(){
            CropMeta cropMeta = (CropMeta)ConfigData.getObjectMeta(XMLConstants.scrop);
            if(cropMeta != null){
                List <String> cnVec = cropMeta.getCategoryNamesVec();
                return cnVec;
            }else
                return null;
        }
        
        /**
         * This method fetches the the parameter object associated with the name pName
         * in the hashtable mParameters.
         * @param pName The name whose parameter object is requested.
         * @return The parameter object associated with pName
         */        
	public Parameter getParameter(String pName)	
	{
		if(mParameters != null)
		{
			return (Parameter)mParameters.get(pName);
		}
		return null;
	}	
	
    /**
     * Fetches the parameter names that are associated with the crop object.
     * @return The vector that holds all these parameter names.
     */    
	public Vector getParameterNames()
  {
  	return mParameterNames;
	}
	
	
    /**
     * Fetches the values associated with the parameter in the crop object for the
     * column with name pColName.
     * @param pColName The name of the column whose parameter value is requested.
     * @return The actual value associated with the parameter.
     */    
	public String getValue(String pColName)
	{
		if(pColName.equals(XMLConstants.scropname))                 
			return mCropName;
			
		/* If not crop name, then the pColName should be any parameter name */
		Parameter parameter = (Parameter)mParameters.get(pColName);
		if(parameter != null)
			return parameter.get(XMLConstants.svalue);
		
		return null;
	}
	
    /**
     * Fetches the values associated with the parameter in the crop object for the
     * column with name pColName.
     * @param pColName The name of the column whose parameter value is requested.
     * @param pUnit The units in which the parameter meta data is stored or represented
     * This metadata is for the parameter in the column pColName for the crop Object.
     * @return The actual value associated with the parameter.
     */    
	public String getValue(String pColName, int pUnit)
	{
		if(pColName.equals(XMLConstants.scropname))
			return mCropName;
			
		/* If not crop name, then the pColName should be any parameter name */
		String returnValue = null;
		Parameter parameter = (Parameter)mParameters.get(pColName);
                if(parameter != null){
                    returnValue = parameter.get("value");
                }
		
		if( (returnValue != null ) && (pUnit == XMLConstants.kAlternateUnit) )  // i.e conversion is required
		{
			CropMeta cropMeta = (CropMeta)ConfigData.getObjectMeta(XMLConstants.scrop);
			if(cropMeta != null)
			{
				ParameterMeta paramMeta = cropMeta.getParameterMeta(pColName);
				
				returnValue = paramMeta.convertToAlternateUnits(returnValue);
			}
		}
		return returnValue;
		//return null;
	}
	
    /**
     * Sets the parameter in crop object with name pParamName to the value for
     * the column with name pColName.
     * @param pParamName The name of the parameter whose value is being updated to a
     * new value
     * @param pNewValue The new value of the crop parameter.
     * @param pUnit The units to which the new value for the parameter name will
     * exists.
     */    
	public void setValue(String pParamName, String pNewValue, int pUnit)
	{	
		String oldValue = null;
		String valueToSet = pNewValue;
		
		Parameter parameter = (Parameter)mParameters.get(pParamName);
		oldValue = parameter.get(XMLConstants.svalue);
		
		ParameterMeta paramMeta = ( (CropMeta)ConfigData.getObjectMeta(XMLConstants.scrop)).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)
			{
				if(paramMeta != null){

					valueToSet = paramMeta.convertToPrimaryUnits(pNewValue);
                                }

				logger.debug("Converted new value " + pNewValue + " to " + valueToSet);
			}		
				
			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);
						
				parameter.setValue(properTypeValue);
				return;
					
			}
		}
		else
		{
		logger.warn("Could not find the parameterMeta. Setting new value anyway");

			parameter.setValue(pNewValue);
			return;		
		}
		
	}

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final CropObject other = (CropObject) obj;
        if (this.mCropName != other.mCropName && (this.mCropName == null || !this.mCropName.equals(other.mCropName))) {
            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 = 5;
        hash = 79 * hash + (this.mCropName != null ? this.mCropName.hashCode() : 0);
        return hash;
    }
        
        
			
			
    /**
     * The method that makes an identical copy of the crop object.
     * @return The copy made from the original object.
     */    
	public Object clone()
	{
		//try
		{
			CropObject copy = (CropObject)super.clone();
			copy.mCropName = mCropName;
			
			copy.mParameters = new Hashtable();
			
			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);
			}		
			return copy;
		}
		/*catch(CloneNotSupportedException e) //// raises the error 'Exception never thrown' in the preceeding body of try
		{
			System.err.println("CropObject:Clone();" + "Clone Not Supported!");
		}
		return null; */
	}
}
