/* Title:		RowInfo
 * Version	
 * Author		Sada
 * Company: USDA-ARS
 * Date:    July, 2003
 * Description: This is the data class which holds the data for a single row.
 */

package usda.weru.mcrew;

import java.util.*;

import org.apache.log4j.Logger;
import org.w3c.dom.*;
import org.w3c.dom.traversal.*;

/**
 * This is the data class which holds the data for a single row.
 */
public class RowInfo implements Cloneable, Comparable<RowInfo>{		
    private static final Logger LOGGER = Logger.getLogger(RowInfo.class);
	private JulianCalendar mDate;
		Hashtable <String, DataObject> mDataObjectTable; // A single row can have more than one DataObject. Presently, it can have
														// an operation object and crop object. A crop object can be present only if 
														// the operatioin object has a planting process (Process G:03)
		       
   /**
    * Default constructor that creates the calendar object for the row to be stored 
    * in the first column of that row object. It also creates a hashtable that stores 
    * the various data objects that go in different cells of that row.
    */
    public RowInfo()
	{
		mDate = new JulianCalendar(1, 0, 1); // a new row has a default date
		//mDate = null; /* Not initializing the date wil calluse the date renderer/ editor to crash
			
		mDataObjectTable = new Hashtable<String, DataObject>();
		//mCrop  = null;
	}
	
    /**
    * Single argument constructor that takes the calendar object as an argument for
    * the row to be stored in the first column. It also creates a hashtable that stores 
    * the various data objects that go in different cells of that row
    * @param pCal The calendar object to be used in first column of that row.
     */    
	public RowInfo(JulianCalendar pCal)
	{
		mDate = pCal;
		mDataObjectTable = new Hashtable<String, DataObject>();
		
		//System.out.println("RowInfo:RowInfo(pCal)" + "Constructior called");
		//mCrop = null;
	}
		
    /**
     * The RowInfo object is being initialized with the data from the node passed 
     * as argument to this method.
     * @param pNode The node that is used for data initialization purposes. 
     */    
	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 node = walker.firstChild();
		while(node != null)
		{
			String date;
			String nodeName = node.getNodeName();
			
			if(nodeName.equals(XMLConstants.sdate))
			{	date = XMLDoc.getTextData(node);
				setDate(date);
			}
			else if(nodeName.equals(XMLConstants.soperationDB))
			{
				Node operationDBNode = node; // operationDB is a OperationObject
				OperationObject operationObject = new OperationObject();
				operationObject.initialize(operationDBNode);
				addDataObject(operationObject.getObjectName(), operationObject);
			}
			
			node = walker.nextSibling();
		} // end while
	}
	
	
   /**
    * Two argument constructor that takes the calendar object as first argument for
    * the row to be stored in its first column. The subsequent data objects are stored 
    * in the hashtable named DataObjectTable with their objectNames. 
    * @param pCal The calendar object to be used in first column of that row.
    * @param pDataObject The object that stores all the to be assigned to various 
    * cells in the row.
    */    
	public RowInfo(JulianCalendar pCal, DataObject pDataObject)
	{
                this();
		mDataObjectTable.put(pDataObject.mObjectName, pDataObject);
	}
		
    /**
     * This method sets the date or calendar object being passed as argument to 
     * this object's date variable that will be accessed via the date column of 
     * the row.
     * @param pCal The new calendar object to be assigned.
     */    
	public void setDate(JulianCalendar pCal)
	{
		mDate = pCal;
		////System.out.println("RowInfo:setDate(JulianClendar)" + " called");
	}
	
    /**
     * This method sets the date string being passed as argument to 
     * this object's date variable that will be accessed via the date column of 
     * the rowInfo object.
     * @param pDate The date string to be assigned.
     */    
	public void setDate(String pDate)
	{
		/* //Functionality moved to calendar class
		 
		StringTokenizer tokenizer = new StringTokenizer(pDate, JulianCalendar.sDateDelimeter);
		int date[] = new int[3];
		int tokenCount = 0;
		while(tokenizer.hasMoreElements())
		{
			String token = tokenizer.nextToken();
			date[tokenCount++] = (new Integer(token)).intValue();
		}
		
		date[kMonthField]--; // because the month values in man or xml files have index starting from 1 i.e Jan = 1
		mDate = new JulianCalendar(date[kYearField], date[kMonthField], date[kDayField]); // 2,1,0
		*/
		
		mDate = new JulianCalendar(pDate);
		////System.out.println("RowInfo:setDate(String)" + " called");
	}
	
    /**
     * This method changes the date using the calendar object being passed as 
     * argument to this method and then assigning it to the date variable of this object
     * @param pCal The new calendar object to be assigned.
     */    
	public void changeDate(JulianCalendar pCal)
	{
		mDate = pCal;
	}
			
    /**
     * Changes the dataObject stored under the same object name
     * @param pDataObject The new dataObject to be assigned to the old object name.
     * @return Returns -1 if there doesn't exist an object for that object name else 
     * returns 1.
     */    
	public int setDataObject(DataObject pDataObject)
	{
		if(mDataObjectTable.get(pDataObject.mObjectName) == null)
			return -1;
		
		mDataObjectTable.remove(pDataObject.mObjectName);
		mDataObjectTable.put(pDataObject.mObjectName, pDataObject);
		
		return 1;
	}
		
    /**
     * Returns the date that is currently assigned to this row.
     * @return The calendar object currently assigned to this row.
     */    
	public JulianCalendar getDate()
	{
		return mDate;
	}
	
	/* 
	 */
    /**
     * Fetches the value associated with the particular ColName (tagname /paramname)
     * @param pObjectName The name of hte object whose data is being requested.
     * @param pColName The column name (e:g names of main MCREW table columns) 
     * @param pUnit The units in which the data is requested.
     * @return The name of hte column from the specified object(crop/operation)
     */    
	public String get(String pObjectName, String pColName, int pUnit)
	{
                DataObject obj = null;
		if(pObjectName.equals(XMLConstants.scrop)){
                    obj = (DataObject)mDataObjectTable.get(XMLConstants.soperation);	 // Crop values are embedded inside weps_operation
                }
		else{
			obj = (DataObject)mDataObjectTable.get(pObjectName);	 // All other values (operatin values) are in their objects
                }
		if(obj == null)
			return null;
		
		return obj.getValue(pColName, pUnit);
	}
        
        public String[] getValues(String pObjectName, String pColName, int pUnit){             
            if(pObjectName.equals(XMLConstants.scrop)){
                pObjectName = XMLConstants.soperation;
            }
            
            DataObject obj = (DataObject)mDataObjectTable.get(pObjectName);
            if(obj == null){
                return null;
            }
            else if(obj instanceof OperationObject){
                OperationObject op = (OperationObject) obj;
                return op.getValues(pColName, pUnit);
            }
            else{
                return new String[] {obj.getValue(pColName, pUnit)};
            }                            
        }
        
	public void setValues(String pObjectName, String pColName, String[] values, int pUnit){
            DataObject obj = null;
		
            if(values == null){
                return;
            }

            if(pObjectName.equals(XMLConstants.scrop)){
                    obj = (DataObject)mDataObjectTable.get(XMLConstants.soperation);	 // Crop values are embedded inside weps_operation                        
            }else{
                    obj = (DataObject)mDataObjectTable.get(pObjectName);	 // All other values (operatin values) are in their objects                        
            }

            if(obj != null){                
                if (obj instanceof OperationObject){
                    OperationObject op = (OperationObject) obj;
                    op.setValues(pColName, values, pUnit);
                }
                else{
                    obj.setValue(pColName, values[0], pUnit);
                }
            }   
        }
	
    /**
     * This method sets the value of the column in the mentioned units for object 
     * with name pObjectName.
     * @param pObjectName The name of the object like opertion or crop whose column
     * data is to be set.
     * @param pColName The column name where the new data will go.
     * @param pValue The new value to be set.
     * @param pUnit The units in whic hthe data will be represented or retrieved.
     */    
	public void set(String pObjectName, String pColName, String pValue, int pUnit)
	{
            DataObject obj = null;

            if(pValue == null){
                return;
            }

            if(pObjectName.equals(XMLConstants.scrop)){
                obj = (DataObject)mDataObjectTable.get(XMLConstants.soperation);	 // Crop values are embedded inside weps_operation                        
            }else{
                obj = (DataObject)mDataObjectTable.get(pObjectName);	 // All other values (operatin values) are in their objects                        
            }

            if(obj != null){                
                obj.setValue(pColName, pValue, pUnit);
            }                
	}
	
	
    /**
     * Fetches the dataObject stored under the object name pObjectName
     * @param pObjectName The object name whose data object is being requested.
     * @return Returns The dataObject associated with object name pObjectName.
     */    
	public DataObject getDataObject(String pObjectName)
	{
		return (DataObject)mDataObjectTable.get(pObjectName);
	}
		
    /**
     * Adds the data object to the row. If its an operation object, then the fucntion removes 
	 * any older operation object and add thsi new one. If its a crop object, then the fucntion copies
	 * the values from the crop object to teh correpsponding actions
     * @param pObjectName The name of the object to which the data object will be added
     * @param pDataObject The data object(crop/operation) that needs to be added.
     * @return Returns 1 if the data object to be added is neither operation nor crop.
     */    
	public int addDataObject(String pObjectName, DataObject pDataObject)
	{
		if(pObjectName.equals(XMLConstants.soperation))
		{	
			/* Remove the old operation. But if old operation consists of a valid crop (not 'No_Name.crop') then copy it over to 
			 * new OperationObject.
			 *
			 */
			OperationObject oldObject = null; 
                        OperationObject newObject;
                        if (pDataObject instanceof OperationObject){
                             newObject = (OperationObject) pDataObject;
                        }
                        else{
                            LOGGER.error("Expected OperationObject: " + pDataObject != null ? pDataObject.toString() : "null");
                            return 1;
                        }
			
			
			if(mDataObjectTable.get(pObjectName) != null)
			{	
				oldObject = (OperationObject)mDataObjectTable.remove(pObjectName);
			
				//System.out.println("RowInfo:addDataObject->" + "Crop sratus, old = " + oldObject.hasCrop() + " ,new "+ newObject.hasCrop());
				if( (newObject.hasCrop())	&& (oldObject.hasCrop()) )
				{
				/* This has been changed to retain the existing crop when the new crop is a no crop
				 **/
					String newCropName = newObject.getCropName().toLowerCase();					
				
					//System.out.println("RowInfo:addDataObject->" + "Oldcropname = " + oldCropName + "newCropName " + newCropName);
					if( (newCropName.equals(XMLConstants.snocrop)) || (newCropName.equals(XMLConstants.sno_crop)) )
					{	
						CropObject retainCrop = oldObject.getCrop();
						newObject.changeCrop(retainCrop);
					}
				}	
			}
			//else
			//	//System.err.println("RowInfo:addDataObject-> "+ "No earlier operationObject in this row");
					
			mDataObjectTable.put(pObjectName, newObject);
		}
		else if(pObjectName.equals(XMLConstants.scrop))
		{
                        Object o = mDataObjectTable.get(XMLConstants.soperation);
                        if (o instanceof OperationObject){
                            OperationObject operation = (OperationObject) o;
                            if (pDataObject instanceof CropObject){
                                operation.changeCrop((CropObject)pDataObject);
                                return 0;
                            }
                            else{
                                LOGGER.error("Expected CropObject: " + pDataObject != null ? pDataObject.toString() : "null");
                            }
                        }
                        else{
                            LOGGER.error("Expected OperationObject: " + o != null ? o.toString() : "null");
                        }
			
			
		}
		
		return 1;
	}

    /**
     * Fetches the hashtable that stores all the dataobject meant for that particular
     * row object i:e object associated with all the cell in that row.
     * @return The hashtable that store them
     */    
	public Map <String, DataObject> getAllDataObjects()
	{
		return Collections.unmodifiableMap(mDataObjectTable);
	}
	
	/* 
	 */
    /**
     * Returns the row contents as a DOM Node to be written back to an XML file
     * @param doc The document from where the node is being pulled. 
     * @return Returns the DOM node that store the operation/crop data.
     */    
	public Node getNode(Document doc)
	{
		Node dataObjectNode;; // holds the information of every dataobject (only operation object for now)
		Node wepsmanvalueNode = doc.createElement(XMLConstants.swepsmanvalue);
		
		Node date = doc.createElement(XMLConstants.sdate);
		XMLDoc.setTextData(date, mDate.toString(), doc);
		
		wepsmanvalueNode.appendChild(date);
		
		Enumeration e = mDataObjectTable.elements();
		while(e.hasMoreElements())
		{
			DataObject dataObject = (DataObject)e.nextElement();
			dataObjectNode = dataObject.getNode(doc); //it will be an operationDB node as operation object 
																						// is the only type of object in any management file 
			
			wepsmanvalueNode.appendChild(dataObjectNode);
		}
		
		return wepsmanvalueNode;
	}
	
    /**
     * Provides a carbon copy of the existing rowinfo object for data manipulation.
     * @return The new copy of the rowinfo object.
     */    
	public Object clone()
	{
		try
		{
			//System.err.println("Rowinfo:clone" + " Cloning "+ mDate);
			
			RowInfo copy = (RowInfo)super.clone();
			//System.out.println("RowInfo:Clone() " + "Date cloned " + mDate);
			copy.mDate = new JulianCalendar(mDate);
			
			copy.mDataObjectTable = new Hashtable();
			Enumeration e = mDataObjectTable.keys();
			while(e.hasMoreElements())
			{
				String key = (String)e.nextElement();
				DataObject dataObject = (DataObject)mDataObjectTable.get(key);
				
				String keyCopy = key;
				DataObject dataObjectCopy = (DataObject)dataObject.clone();
				
				copy.mDataObjectTable.put(keyCopy, dataObjectCopy);
			}
			return copy;
		}
		catch(CloneNotSupportedException e)
		{
			//System.err.println("RowInfo::clone();" + "Clone Not Supported Exception!");
		}
		return null;
	}	
	
     /**
     * This method helps for comparing the two objects .. i:e current rowInfo object's
     * calendar object and the other object being passed as argument to see if 
     * they are the same.
     * @param o The other rowInfo object.
     * @return -1 if the object is null or the date falls before the assigned date else 1.
     */   
	public int compareTo(RowInfo pRow)
	{
		Calendar compareToCal = ((RowInfo)pRow).getDate();
		
		if(mDate == null){
			return -1;
                }
			
		return mDate.compareTo(compareToCal);			
		
	}

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final RowInfo other = (RowInfo) obj;
        if (this.mDate != other.mDate && (this.mDate == null || !this.mDate.equals(other.mDate))) {
            return false;
        }
        if (this.mDataObjectTable != other.mDataObjectTable && (this.mDataObjectTable == null || !this.mDataObjectTable.equals(other.mDataObjectTable))) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 17 * hash + (this.mDate != null ? this.mDate.hashCode() : 0);
        hash = 17 * hash + (this.mDataObjectTable != null ? this.mDataObjectTable.hashCode() : 0);
        return hash;
    }


        
        



}