/* 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.awt.Color;
import java.util.*;
import javax.swing.Icon;
import javax.swing.ImageIcon;

import org.apache.log4j.Logger;
import org.w3c.dom.*;
import org.w3c.dom.traversal.*;
import usda.weru.resources.Resources;

/**
 * This is the data class which holds the data for a single row.
 */
public class RowInfo implements Cloneable, Comparable<RowInfo> {
    public static final byte[] blank = new byte[0];
    /**
     * This enum handles what type of operation is contained in each cell, and will
     * take care of generating the graphical component necessary by returning either
     * the icon associated with the operation or the color associated with the operation.
     */
    public enum OpType
    {
        HAR(Resources.getIcon("Harvest.gif"), Color.ORANGE), //HARVEST
        TIL(Resources.getIcon("Op_Tilliage.gif"), Color.RED), //TILLAGE
        IRR(Resources.getIcon("blueline.gif"), Color.BLUE), //IRRIGATION
        PLA(Resources.getIcon("Planting.gif"), Color.PINK), //PLANT
        GEN(Resources.getIcon("Operation.gif"), Color.GRAY), //GENERAL OPERATION
        MOR(Resources.getIcon("move.gif"), Color.BLACK), //More Options (hidden)
        BLA(new ImageIcon(blank), Color.WHITE); //NoOp
        
        OpType(Icon input, Color color)
        {
            pic = input;
            col = color;
        }
        
        public final Icon pic;
        
        public final Color col;
        
    }
    private static final Logger LOGGER = Logger.getLogger(RowInfo.class);
    private JulianCalendar mDate;
    private JulianCalendar mDisplayDate; //When in NRCS mode, the date displayed
                                         //is different from the actual date.
    private int yearOffset = 0;  //This will be non-zero if the table is in NRCS
                                  //mode and this row is in the pivoted section.
    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;
    }

    private OpType type = OpType.BLA;
    
    /**
     * 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();

            switch (nodeName) {
                case XMLConstants.sdate:
                    date = XMLDoc.getTextData(node);
                    setDate(date);
                    break;
                case XMLConstants.soperationDB:
                    Node operationDBNode = node; // operationDB is a OperationObject
                    OperationObject operationObject = new OperationObject();
                    operationObject.initialize(operationDBNode);
                    addDataObject(operationObject.getObjectName(), operationObject);
                    break;
            }

            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.setType();
    }

    public OpType getType()
    {
        return type;
    }
    
    private void setType()
    {
        int[] harvest = { 32, 33, 37, 38, 42, 43, 47, 48, 61, 62 };
        int[] plant = { 51 };
        int[] irrigation = { 71, 72, 73, 74 };
        int[] till = { 1, 2, 3, 4, 5, 11, 12, 13, 14, 25, 26 };
        DataObject ob = mDataObjectTable.get(XMLConstants.soperation);
        if(ob instanceof OperationObject)
        {
            Vector<Identity> id = ((OperationObject) ob).mActionIdVec;
            for(int item : harvest)
            {
                if(id.contains(new Identity(item, "P")))
                {
                    type = OpType.HAR;
                    return;
                }
            }
            for(int item : plant)
            {
                if(id.contains(new Identity(item, "P")))
                {
                    type = OpType.PLA;
                    return;
                }
            }
            for(int item : irrigation)
            {
                if(id.contains(new Identity(item, "P")))
                {
                    type = OpType.IRR;
                    return;
                }
            }
            for(int item : till)
            {
                if(id.contains(new Identity(item, "P")))
                {
                    type = OpType.TIL;
                    return;
                }
            }
            type = OpType.GEN;
        }
    }
    
    /**
     * 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;
        synchDates();
        ////System.out.println("RowInfo:setDate(JulianClendar)" + " called");
    }
    
    public void setDisplayDate(JulianCalendar pCal) 
    {
        mDisplayDate = pCal;
        resynchDates();
    }

    /**
     * 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);
        synchDates();
        //System.out.println("RowInfo:setDate(String)" + " called");
    }
    
    public void setDisplayDate(String pDate)
    {
        mDisplayDate = new JulianCalendar(pDate);
        resynchDates();
    }

    /**
     * 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);
        this.setType();

        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;
    }
    
    public JulianCalendar getDisplayDate()
    {
        if(mDisplayDate == null) mDisplayDate = mDate.cloneDate();
        return mDisplayDate;
    }

    /* 
     */
    /**
     * 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;
        if (pObjectName.equals(XMLConstants.scrop)) {
            // Crop values are embedded inside weps_operation
            obj = mDataObjectTable.get(XMLConstants.soperation);
        } else {
            // All other values (operatin values) are in their objects
            obj = mDataObjectTable.get(pObjectName);
        }
        if (obj == null) {
            return null;
        }

        return obj.getValue(pColName, pUnit);
    }

    /**
     *
     * @param pObjectName
     * @param pColName
     * @param pUnit
     * @return
     */
    public String[] getValues(String pObjectName, String pColName, int pUnit) {
        if (pObjectName.equals(XMLConstants.scrop)) {
            pObjectName = XMLConstants.soperation;
        }

        DataObject obj = 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)};
        }
    }

    /**
     *
     * @param pObjectName
     * @param pColName
     * @param values
     * @param pUnit
     */
    public void setValues(String pObjectName, String pColName, String[] values, int pUnit) {
        DataObject obj = null;

        if (values == null) {
            return;
        }

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

        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)) {
            // Crop values are embedded inside weps_operation
            obj = mDataObjectTable.get(XMLConstants.soperation);                     
        } else {
            // All other values (operatin values) are in their objects
            obj = mDataObjectTable.get(pObjectName);                   
        }

        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 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);
            this.setType();
        } 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<DataObject> e = mDataObjectTable.elements();
        while (e.hasMoreElements()) {
            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.
     * @throws java.lang.CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        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<String> e = mDataObjectTable.keys();
            while (e.hasMoreElements()) {
                String key = e.nextElement();
                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 pRow
     * @return -1 if the object is null or the date falls before the assigned date else 1.
     */
    @Override
    public int compareTo(RowInfo pRow) {
        Calendar compareToCal = (pRow).getDate();

        if (mDate == null) {
            return -1;
        }

        return mDate.compareTo(compareToCal);

    }

    /**
     *
     * @param obj
     * @return
     */
    @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 == null) || (other.mDataObjectTable == null) || (!this.mDataObjectTable.equals(other.mDataObjectTable))) 
//        {
//            return false;
//        }
//        return true;
        return super.equals(obj);
    }

    /**
     *
     * @return
     */
    @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;
    }
    
    public void setYearOffset(int input) { yearOffset = input; }
    
    /**
     * Changes the Display date to reflect changes in the actual date.
     */
    public void synchDates()
    {
        if((mDate != null))
        {
            mDisplayDate = new JulianCalendar(mDate.get(Calendar.YEAR) + yearOffset,
                                    mDate.get(Calendar.MONTH), mDate.get(Calendar.DATE));
        }
    }
    
    /**
     * Changes the actual date to reflect changes in the display date.
     */
    public void resynchDates()
    {
        if((mDisplayDate != null))
        {
            if((mDisplayDate.get(Calendar.YEAR) - yearOffset) > 0)
            {
                mDate = new JulianCalendar(mDisplayDate.get(Calendar.YEAR) - yearOffset,
                                    mDisplayDate.get(Calendar.MONTH), mDisplayDate.get(Calendar.DATE));
            }
        }
    }
    
    /**
     * Forces the display date to match the actual date.
     */
    public void enforceActualDate() { mDisplayDate = mDate.cloneDate(); }
    /**
     * Forces the actual date to match the display date.
     */
    public void enforceDisplayDate() { mDate = mDisplayDate.cloneDate(); }
    

}
