/*
 * Column.java
 *
 * Created on June 8, 2006, 10:54 AM
 *
 */
package usda.weru.util.table;

import com.klg.jclass.table.JCTableEnum;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Vector;
import java.util.Hashtable;
import java.util.List;
import org.jdom2.Element;
import usda.weru.util.ConversionCalculator;
import usda.weru.util.ConversionUnit;
import usda.weru.util.ConvertedValue;

/**
 * Wrapper class for a Column object in a WepsTable.
 * @author Joseph Levin
 */
public class Column implements ColumnGroupOrColumn, XmlObject, ParseListener {

    /**
     *
     */
    protected Label c_label;

    /**
     *
     */
    protected WepsTableMeta c_meta;

    /**
     *
     */
    protected ColumnGroup c_parentColumnGroup;

    /**
     *
     */
    protected ColumnGroupOrColumn c_tempChild;

    /**
     *
     */
    protected String c_id;

    /**
     *
     */
    protected boolean c_anonymous;

    /**
     *
     */
    protected boolean c_forDisplay = true;

    /**
     *
     */
    protected boolean quickplotL = false;
    
    /**
     * 
     */
    protected boolean quickplotR = false;

    /**
     *
     */
    protected Column c_parent;
    //Inhertable junk

    /**
     *
     */
    protected String c_name;

    /**
     *
     */
    protected int c_dataType = WepsTableEnum.NO_VALUE;

    /**
     *
     */
    protected String c_dataKey;
    
    /**
     *
     */
    protected String c_qplotcol;

    /**
     *
     */
    protected ConversionUnit c_dataUnits;

    /**
     *
     */
    protected Hashtable<String, ConversionUnit> c_dataDisplayUnits;      //System to unit

    /**
     *
     */
    protected Hashtable<String, Format> c_dataFormats;      //System to format

    /**
     *
     */
    protected Hashtable<String, DataLimit> c_dataLimits;      //System to format

    /**
     *
     */
    protected List<DataAdjustment> c_dataAdjustments;

    /**
     *
     */
    protected int c_minWidth = WepsTableEnum.NO_VALUE;
    ;

	/**
	 *
	 */
	protected int c_width = WepsTableEnum.NO_VALUE;

    /**
     *
     */
    protected int c_maxWidth = WepsTableEnum.NO_VALUE;

    /**
     *
     */
    protected int c_hidden = WepsTableEnum.NO_VALUE;
    ;

	/**
	 *
	 */
	protected int c_visible = WepsTableEnum.NO_VALUE;
    ;

	/**
	 *
	 */
	protected CellStyle c_cellStyle;
        
    /**
     * Create a Column object associated with the given WepsTableMeta.
     * @param meta The WepsTableMeta to which this Column will be associated.
     */
    public Column(WepsTableMeta meta) {
        this(meta, true);
    }

    /**
     * Create a Column object associated with the given WepsTableMeta and is either displayable or not.
     * @param meta The WepsTableMeta to which this Column will be associated.
     * @param forDisplay Whether or not the Column will be displayable.
     */
    public Column(WepsTableMeta meta, boolean forDisplay) {
        init();
        c_meta = meta;
        c_forDisplay = forDisplay;
    }

    /**
     * Load configuration settings for this Column from a jdom Element.
     * @param node An org.jdom2.Element containing configuration information about this Column.
     */
    @Override
    public void fromXml(Element node) {
        loadId(node);
        loadParent(node);
        loadName(node);
        loadVisibility(node);
        loadCellStyle(node);
        loadData(node);
        loadWidth(node);
        loadqplotcol(node);
        if (isForDisplay()) {
            loadLabels(node);
        }
    }
    
    private void init() {

    }

    private boolean isForDisplay() {
        return c_forDisplay;
    }

    /**
     * Return whether or not this column is hidden.  If a parent is specified it will check the parent to see if it is hidden.
     * @return Whether or not the Column is hidden.
     */
    public boolean isHidden() {
        if (c_hidden == WepsTableEnum.NO_VALUE && c_parent != null) {
            return c_parent.isHidden();
        } else if (c_hidden == WepsTableEnum.NO_VALUE) {
            return false;
        } else {
            return (c_hidden == 1);
        }
    }

    /**
     * Return whether or not this column is visible.  If a parent is specified it will check the parent to see if it is visible.
     * @return Whether or not the column is visible.
     */
    public boolean isVisible() {
        if (c_visible == WepsTableEnum.NO_VALUE && c_parent != null) {
            return c_parent.isVisible();
        } else if (c_visible == WepsTableEnum.NO_VALUE) {
            return true;
        } else {
            return (c_visible == 1);
        }
    }

    public void setQuickPlotL(boolean value)
    {
        quickplotL = value;
    }
    
    public void setQuickPlotR(boolean value)
    {
        quickplotR = value;
    }
    
    /**
     *
     * @param value
     */
    public void setQuickPlot(int value) {
        if(value == 0)
        {
            quickplotL = false;
            quickplotR = false;
        }
        if(value == 1)
        {
            quickplotL = true;
            quickplotR = false;
        }
        if(value == 2)
        {
            quickplotL = false;
            quickplotR = true;
        }
    }

    /**
     *
     * @return
     */
    public boolean isQuickPlotL() {
        return quickplotL;
    }

    /**
     *
     * @return
     */
    public boolean isQuickPlotR() {
        return quickplotR;
    }

    /**
     * Return the Label used for this Column.
     * @return The Label for this Column.
     */
    @Override
    public Label getLabel() {
        return c_label;
    }
    
    public String getqplotcol() {
        return c_qplotcol;
    }

    /**
     * Return whether or not the Column is anonymous.
     * @return <b>true</b> if the column is anonymous, <b>false</b> otherwise.
     */
    public boolean isAnonymous() {
        return c_anonymous;
    }

    /**
     * Set the ColumnGroup this Column will be a part of.
     * @param parent The ColumnGroup this Column will be a part of.
     * @return 
     */
    @Override
    public ColumnGroupOrColumn setParent(ColumnGroup parent) {
        if (c_tempChild == null) {
            c_parentColumnGroup = parent;
            return this;
        } else {
            c_tempChild.setParent(parent);
            return c_tempChild;
        }
    }

    /**
     * Return the ColumnGroup this Column is a part of.
     * @return The ColumnGroup this Column is a part of.
     */
    @Override
    public ColumnGroup getParentGroup() {
        return c_parentColumnGroup;
    }

    /**
     * Set the parent Column this Column will inherit configuration settings from.
     * @param parent The parent Column this Column will inherit configuration settings from.
     */
    public void setParent(Column parent) {
        c_parent = parent;
    }

    /**
     * Return the depth in the header that this Columns Label will appear.
     * @return The depth in the header.
     */
    @Override
    public int depthInHeader() {
        int depth = 0;
        if (getLabel() != null && getLabel().getSpanRows() > 1) {
            depth = depth + getLabel().getSpanRows() - 1;
        }
        if (getParentGroup() != null) {
            depth++;
            depth = depth + getParentGroup().depthInHeader();
        }
        return depth;
    }

    /**
     * Returns the number of Columns under this Column (inclusive).
     * @return 1
     */
    @Override
    public int bottomBreadth() {
        return 1;
    }

    /**
     * Not implemented.
     * @param child 
     * @return <b>false</b>
     */
    @Override
    public boolean isFirstChild(ColumnGroupOrColumn child) {
        return false;
    }

    private void loadId(Element node) {
        //Get Id                
        String id = node.getAttributeValue(WepsTableEnum.XML_id);
        if (id != null) {
            c_id = id;
        } else {
            //Get Id from counter
            c_id = c_meta.getAnonymousId();
            c_anonymous = true;
        }
    }

    private void loadParent(Element node) {
        //Set Parent Column
        String parentId = node.getAttributeValue(WepsTableEnum.XML_parent);
        if (parentId != null) {
            Column parentColumn = c_meta.getColumn(parentId);
            //Has the parent been loaded?
            if (parentColumn != null) {
                setParent(parentColumn);
            }
        }
    }

    private void loadName(Element node) {
        c_name = node.getChildTextTrim(WepsTableEnum.XML_name);
    }
     
    private void loadqplotcol(Element node) {
        String qname;
        try {
            qname = node.getAttributeValue(WepsTableEnum.XML_qplotcol);
        } catch (Exception e) {
            qname = null;
        }
        c_qplotcol = qname;
    }

    private void loadVisibility(Element node) {
        String visibleText = node.getChildTextTrim(WepsTableEnum.XML_visible);
        String hiddenText = node.getChildTextTrim(WepsTableEnum.XML_hidden);

        if (visibleText != null) {
            if (Helper.isTrue(visibleText)) {
                c_visible = 1;
            } else {
                c_visible = 0;
            }
        }

        if (hiddenText != null) {
            if (Helper.isTrue(hiddenText)) {
                c_hidden = 1;
            } else {
                c_hidden = 0;
            }
        }

    }

    private void loadCellStyle(Element node) {
        Element styleNode = node.getChild(WepsTableEnum.XML_style);
        if (styleNode == null) {
            return;
        }
        String cellId = null;
        cellId = styleNode.getTextTrim();

        if (cellId != null && cellId.length() > 0) {
            c_cellStyle = c_meta.getCellStyle(cellId);
        } else {
            c_cellStyle = new CellStyle(c_meta);
            c_cellStyle.fromXml(styleNode);
        }
    }

    private void loadLabel(Element node) {
        c_label = new Label(c_meta);
        Element labelElement = node.getChild(WepsTableEnum.XML_label);
        c_label.fromXml(labelElement);
    }

    private void loadLabels(Element node) {
        List<Element> labelNodes = node.getChildren(WepsTableEnum.XML_label);
        c_label = new Label(c_meta);

        boolean first = true;
        boolean last = false;
        ColumnGroup parent = null;
        ColumnGroupOrColumn tempChild = null;
        for (Element child : labelNodes) {
            last = labelNodes.indexOf(child) == labelNodes.size() - 1;      //Is the child the last element?
            if (last) {
                c_label.fromXml(child);
                if (parent != null) {
                    parent.add(this);
                    c_tempChild = tempChild;
                }
            } else {
                //We have more labels which we will create unique ColumnGroup objects for.
                ColumnGroup tempColumns = new ColumnGroup(c_meta);
                if (parent != null) {
                    parent.add(tempColumns);
                }

                if (first) {
                    first = false;
                    tempChild = tempColumns;
                }
                Label tempLabel = new Label(c_meta);
                tempLabel.fromXml(child);
                tempColumns.setLabel(tempLabel);
                parent = tempColumns;
            }
        }
    }

    private void loadData(Element node) {
        Element data = node.getChild(WepsTableEnum.XML_data);
        if (data == null) {
            return;
        }
        String typeText = data.getAttributeValue(WepsTableEnum.XML_type);
        int type = WepsTableEnum.getEnumFromTag(typeText);
        if (type > 0) {
            c_dataType = type;
        }

        loadFormats(data);

        switch (getDataType()) {
            case WepsTableEnum.DATA_numeric:
                loadUnits(data);
                loadDisplayUnits(data);
                loadAdjustments(data);
                loadLimits(data);
                break;
            case WepsTableEnum.DATA_list:
                loadChoiceList(data);
                break;
        }

    }

    private void loadUnits(Element node) {
        try {
            String unitsTag = node.getChildTextTrim(WepsTableEnum.XML_units);
            //No tag, stop
            if (unitsTag == null) {
                return;
            }
            c_dataUnits = ConversionCalculator.getUnitFromTag(unitsTag);
        } catch (Exception e) {
        }

    }

    private void loadDisplayUnits(Element node) {
        if (getUnits() == null) {
            return;     //We don't need display units if we don't have units to convert from.
        }
        List<Element> displayUnits = node.getChildren(WepsTableEnum.XML_displayunits);
        //Load display units linked to a measurement system (SI, or US, etc...)
        boolean loadedDefault = false;
        for (Element unitElement : displayUnits) {
            String system = unitElement.getAttributeValue(WepsTableEnum.XML_system);
            if (system == null || system.length() == 0) {
                if (loadedDefault) {
                    continue;
                }
                system = WepsTableEnum.NO_UNITS_SYSTEM;
                loadedDefault = true;
            }
            if (unitElement.getText().length() > 0) {
                try {
                    ConversionUnit unit = ConversionCalculator.getUnitFromTag(unitElement.getText());
                    if (system != null && system.length() > 0 && unit != null) {
                        if (c_dataDisplayUnits == null) {
                            c_dataDisplayUnits = new Hashtable<String, ConversionUnit>();
                        }
                        c_dataDisplayUnits.put(system, unit);
                    }
                } catch (Exception e) {
                    //TODO:Notify of conversion unit lookup failure.
                }
            }

        }
    }

    private void loadFormats(Element node) {
        List<Element> formatNodeList = node.getChildren(WepsTableEnum.XML_format);
        //Load formats linked to a measurement system (SI, or US, etc...)
        boolean loadedDefault = false;
        for (Element formatNode : formatNodeList) {
            String system = formatNode.getAttributeValue(WepsTableEnum.XML_system);
            if (system == null || system.length() == 0) {
                if (loadedDefault) {
                    continue;
                }
                system = WepsTableEnum.NO_UNITS_SYSTEM;
                loadedDefault = true;
            }
            String pattern = formatNode.getText();
            if (pattern != null && pattern.length() > 0) {
                Format format = null;
                switch (getDataType()) {
                    case WepsTableEnum.DATA_numeric:
                        format = new DecimalFormat(pattern);
                        break;
                    case WepsTableEnum.DATA_date:
                        format = new SimpleDateFormat(pattern);
                }

                if (format != null) {
                    if (c_dataFormats == null) {
                        c_dataFormats = new Hashtable<String, Format>();
                    }
                    c_dataFormats.put(system, format);
                }
            }
        }
    }
    
    /**
     * Return the format used for the unit system this Column uses or the parents format if none is declared.
     * @param unitsSystem The unit system for the format required.
     * @return The format used by this Column for the unit system specified.
     */
    public Format getFormat(String unitsSystem) {
        if (c_dataFormats == null && c_parent != null) {
            return c_parent.getFormat(unitsSystem);
        } else {
            if (c_dataFormats == null) {
                return null;
            }
            return c_dataFormats.get(unitsSystem);
        }
    }

    private void loadAdjustments(Element node) {
        List<Element> adjustmentNodes = node.getChildren(WepsTableEnum.XML_adjust);
        for (Element adjustNode : adjustmentNodes) {
            if (c_dataAdjustments == null) {
                c_dataAdjustments = new Vector<DataAdjustment>();
            }
            DataAdjustment adjust = new DataAdjustment();
            adjust.fromXml(adjustNode);
            c_dataAdjustments.add(adjust);

        }
    }

    /**
     * Return a List of adjustments applied to the Columns data or a List from the parent if there is none specified.
     * @return The List of adjustments to be applied to the Columns data.
     */
    public List<DataAdjustment> getAdjustmentList() {
        if (c_dataAdjustments == null && c_parent != null) {
            return c_parent.getAdjustmentList();
        } else {
            return c_dataAdjustments;
        }
    }

    private void loadLimits(Element node) {
        List<Element> limitNodes = node.getChildren(WepsTableEnum.XML_limit);

        for (Element limitNode : limitNodes) {
            DataLimit limit = new DataLimit();
            limit.fromXml(limitNode);
            if (c_dataLimits == null) {
                c_dataLimits = new Hashtable<String, DataLimit>();
            }
            c_dataLimits.put(limit.getSystem(), limit);
        }
    }

    /**
     * Return the DataLimit to be applied to this Column in the unit system specified.
     * @param unitsSystem The unit system to be used.
     * @return The DataLimit to be applied.
     */
    public DataLimit getLimit(String unitsSystem) {
        if (c_dataLimits == null && c_parent != null) {
            return c_parent.getLimit(unitsSystem);
        } else {
            if (c_dataLimits == null) {
                return null;
            }
            return c_dataLimits.get(unitsSystem);
        }
    }

    private void loadChoiceList(Element node) {

    }

    private void loadWidth(Element node) {
        Element widthNode = node.getChild(WepsTableEnum.XML_width);
        if (widthNode == null) {
            return;
        }

        String widthText = widthNode.getValue();
        String minText = widthNode.getAttributeValue(WepsTableEnum.XML_min);
        String maxText = widthNode.getAttributeValue(WepsTableEnum.XML_max);

        int width = WepsTableEnum.getEnumFromTag(widthText);
        if (width != WepsTableEnum.NOT_FOUND) {
            c_width = width;
        } else {
            try {
                c_width = Integer.parseInt(widthText);
            } catch (NumberFormatException nfe) {
                return;
            }
        }

        try {
            c_minWidth = Integer.parseInt(minText);
        } catch (NumberFormatException nfe) {
        }

        try {
            c_maxWidth = Integer.parseInt(maxText);
        } catch (NumberFormatException nfe) {
        }
    }

    /**
     * Return the ID of this Column.
     * @return The ID for this Column.
     */
    public String getId() {
        return c_id;
    }

    /**
     * Return the name of the Column or the name of the parent Column if none is specified.
     * @return The name of the Column.
     */
    public String getName() {
        if (c_name == null && c_parent != null) {
            return c_parent.getName();
        } else if (c_name == null) {
            return getId();
        } else {
            return c_name;
        }
    }

    /**
     * Return the data key used by this Column.
     * @return The data key used by this Column.
     */
    public String getDataKey() {
        if (c_dataKey != null) {
            return c_dataKey;
        } else {
            if (isAnonymous() && c_parent != null) {
                return c_parent.getDataKey();
            } else {
                return getId();
            }
        }
    }

    /**
     * Return the CellStyle used by this Column, or the CellStyle used by the parent.
     * @return The CellStyle used by this Column.
     */
    public CellStyle getCellStyle() {
        if (c_cellStyle == null && c_parent != null) {
            return c_parent.getCellStyle();
        } else {
            return c_cellStyle;
        }
    }

    /**
     * Return a the Columns ID.
     * @return The Columns ID.
     */
    @Override
    public String toString() {
        return getId();
    }

    /**
     * ParseEvent handler.
     * @param event The ParseEvent to be handled.
     */
    @Override
    public void parse(ParseEvent event) {
        String field = event.getField();
        WepsTable table = event.getTable();
        String unitsSystem = table.getUnitsSystem();
        int rowIndex = event.getRowIndex();

        if (field == null) {
            return;
        }
        field = field.toLowerCase().trim();

        if (field.equals(WepsTableEnum.PARSE_units)) {
            if (getUnits() != null) {
                event.setParsedExpression(getUnits().getName());
            }
        } else if (field.equals(WepsTableEnum.PARSE_displayunits)) {
            ConversionUnit displayUnits = getDisplayUnits(unitsSystem);
            if (displayUnits != null) {
                event.setParsedExpression(displayUnits.getName());
            }
        } else if (field.equals(WepsTableEnum.PARSE_displayunitsabbreviation)) {
            ConversionUnit displayUnits = getDisplayUnits(unitsSystem);
            if (displayUnits != null) {
                event.setParsedExpression(displayUnits.getAbbreviation());
            }
        } else if (field.equals(WepsTableEnum.PARSE_value)) {
            event.setParsedExpression(table.getDataView().getObject(rowIndex, this));
        } else if (field.equals("quickplot")) {
            event.setParsedExpression(true);
        }
    }

    /**
     * Returns the units used by this Column or the units of the parent column if none are specified.
     * @return The units being used by this Column.
     */
    public ConversionUnit getUnits() {
        if (c_dataUnits == null && c_parent != null) {
            return c_parent.getUnits();
        } else {
            return c_dataUnits;
        }
    }

    /**
     * Returns the display units used by this Column given the system provided.
     * @param system The system of units being used.
     * @return The Columns units converted to the units of the system.
     */
    public ConversionUnit getDisplayUnits(String system) {
        if (system == null) {
            return null;
        }
        if (c_dataDisplayUnits == null && c_parent != null) {
            return c_parent.getDisplayUnits(system);
        } else {
            if (c_dataDisplayUnits == null) {
                return null;
            }
            return c_dataDisplayUnits.get(system);
        }

    }

    /**
     * Apply all available adjustments to the Object supplied.
     * @param o The Object to be adjusted.
     * @param unitsSystem Not used.
     * @return The adjusted Object.
     */
    public Object applyAdjustments(Object o, String unitsSystem) {
        //Adjust the data
        List<DataAdjustment> adjustments = getAdjustmentList();
        if (adjustments != null) {
            for (DataAdjustment adjust : adjustments) {
                o = adjust.adjust(o);
            }
        }
        return o;
    }

    /**
     * Convert this Columns data to use a different unit system for display.
     * @param o The Object to be converted.
     * @param unitsSystem The unit System to convert to.
     * @return The converted value.
     */
    public Object applyDisplayUnits(Object o, String unitsSystem) {
        if (o == null) {
            return null;
        }
        try {
            double d = Double.parseDouble(o.toString());
            ConversionUnit displayUnits = getDisplayUnits(unitsSystem);
            ConversionUnit units = getUnits();
            if (displayUnits != null && units != null) {
                ConvertedValue cValue = new ConvertedValue();
                cValue.setBaseUnits(units);
                cValue.setDisplayUnits(displayUnits);
                cValue.setValue(d);
                d = cValue.getDisplayValue();
                return d;
            } else {
                return o;
            }
        } catch (NumberFormatException nfe) {
            return o;
        } catch (Exception e) {
            return "#ERR#";
        }
    }

    /**
     * Apply available limits to this Column.
     * @param o The Object to apply the limits to.
     * @param unitsSystem The unit system specifying the set of limits to be used.
     * @return The adjusted value.
     */
    public Object applyLimits(Object o, String unitsSystem) {
        //Limit the data
        DataLimit limit = getLimit(unitsSystem);
        if (limit != null) {
            return limit.limit(o);
        } else {
            return o;
        }
    }

    /**
     * Apply formating to the Object passed.
     * @param o The Object to be formatted.
     * @param unitsSystem The unit system whos formatting will be used.
     * @return The formatted Object.
     */
    public Object applyFormat(Object o, String unitsSystem) {
        //Format the data
        Format format = getFormat(unitsSystem);
        if (format != null) {
            try {
                return format.format(o);
            } catch (Exception e) {
                return o;
            }
        } else {
            return o;
        }
    }

    /**
     * Return the data type for this Column or the data type of the parent if there is none specified.
     * @return The data type used by this Column.
     */
    public int getDataType() {
        if (c_dataType < 0 && c_parent != null) {
            return c_parent.getDataType();
        } else {
            return c_dataType;
        }
    }

    /**
     * Return the width of this Column or the width of the parent if there is none specified.
     * @return The width of the Column or JCTableEnum.VARIABLE_ESTIMATE if there is no value available.
     */
    public int getWidth() {
        if (c_width == WepsTableEnum.NO_VALUE && c_parent != null) {
            return c_parent.getWidth();
        } else {
            if (c_width == WepsTableEnum.NO_VALUE) {
                return JCTableEnum.VARIABLE_ESTIMATE;
            } else {
                return c_width;
            }
        }
    }

    /**
     * Return the minimum width or the minimum width of the parent if no minimum width is specified.
     * @return The minimum width of the Column.
     */
    public int getMinWidth() {
        if (c_minWidth == WepsTableEnum.NO_VALUE && c_parent != null) {
            return c_parent.getMinWidth();
        } else {
            return c_minWidth;
        }
    }

    /**
     * Return the maximum width or the maximum width of the parent if no maximum width is specified.
     * @return The maximum width of the Column.
     */
    public int getMaxWidth() {
        if (c_maxWidth == WepsTableEnum.NO_VALUE && c_parent != null) {
            return c_parent.getMaxWidth();
        } else {
            return c_maxWidth;
        }
    }

}
