/**
 * Copyright (C) 2012 alanhay <alanhay99@hotmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package uk.co.certait.htmlexporter.writer;

import java.text.NumberFormat;
import java.text.ParseException;

import org.apache.commons.lang.StringUtils;
import org.jsoup.nodes.Attribute;
import org.jsoup.nodes.Element;

import uk.co.certait.htmlexporter.ss.CellRange;
import uk.co.certait.htmlexporter.ss.DefaultTableCellReference;
import uk.co.certait.htmlexporter.ss.Dimension;
import uk.co.certait.htmlexporter.ss.Function;
import uk.co.certait.htmlexporter.ss.RangeReferenceTracker;

/**
 * 
 * @author alanhay
 * 
 */
public abstract class AbstractTableCellWriter implements TableCellWriter {
	private RangeReferenceTracker tracker;

	public AbstractTableCellWriter() {
		tracker = new RangeReferenceTracker();
	}

	public void writeCell(Element element, int rowIndex, int columnIndex) {
		renderCell(element, rowIndex, columnIndex);

		if (isFunctionGroupCell(element)) {
			for (String rangeName : getFunctionGroupReferences(element)) {
				tracker.addCelltoRange(rangeName, new DefaultTableCellReference(rowIndex, columnIndex));
			}
		}

		if (isFunctionOutputCell(element)) {
			String rangeName = getFunctionOutputReference(element);
			addFunctionCell(rowIndex, columnIndex, tracker.getCellRange(rangeName), Function.SUM);
		}
	}

	/**
	 * Returns the actual text of the innermost child element for this cell.
	 * 
	 * @param element
	 * 
	 * @return The text to be output for this Cell.
	 */
	public String getElementText(Element element) {
		String text = element.ownText();

		for (Element child : element.children()) {
			text = child.ownText();
		}

		return text;
	}

	/**
	 * Checks the for the presence of the 'colspan' attribute on the cell and
	 * returns the value if this attribute if present, otherwise 1.
	 * 
	 * @param element
	 * 
	 * @return True if the this Element spans multiple columns (has the
	 *         'colspan' attribute defined, otherwise false.
	 */
	protected boolean spansMultipleColumns(Element element) {
		boolean spansMultipleColumns = false;

		if (element.hasAttr(COLUMN_SPAN_ATTRIBUTE)) {
			int columnCount = Integer.parseInt(element.attr(COLUMN_SPAN_ATTRIBUTE));

			spansMultipleColumns = columnCount > 1;
		}

		return spansMultipleColumns;
	}

	protected boolean definesFreezePane(Element element) {
		boolean definesFreezePane = false;

		if (element.hasAttr(DATA_FREEZE_PANE_CELL)) {
			if (Boolean.parseBoolean(element.attr(DATA_FREEZE_PANE_CELL))) {
				definesFreezePane = true;
			}
		}

		return definesFreezePane;
	}

	/**
	 * Checks the for the presence of the 'colspan' attribute on the cell and
	 * returns the value if this attribute if present, otherwise 1.
	 * 
	 * @param element
	 * 
	 * @return The number of columns this cell should span.
	 */
	protected int getMergedColumnCount(Element element) {
		int columnCount = 1;

		if (spansMultipleColumns(element)) {
			columnCount = Integer.parseInt(element.attr(COLUMN_SPAN_ATTRIBUTE));
		}

		return columnCount;
	}

	/**
	 * 
	 * @param element
	 * 
	 * @return
	 */
	protected boolean isFunctionGroupCell(Element element) {
		return element.hasAttr(DATA_GROUP_ATTRIBUTE);
	}

	protected boolean isDateCell(Element element) {
		return element.hasAttr(DATE_CELL_ATTRIBUTE);
	}

	protected String getDateCellFormat(Element element) {
		return element.attr(DATE_CELL_ATTRIBUTE);
	}

	/**
	 * 
	 * @param element
	 * 
	 * @return
	 */
	protected String[] getFunctionGroupReferences(Element element) {
		return getAttributeValues(element, DATA_GROUP_ATTRIBUTE);
	}

	/**
	 * 
	 * @param element
	 * 
	 * @return
	 */
	protected boolean isFunctionOutputCell(Element element) {
		boolean functionOutputCell = false;

		for (Attribute attribute : element.attributes()) {
			if (attribute.getKey().equalsIgnoreCase(DATA_GROUP_OUTPUT_ATTRIBUTE)) {
				functionOutputCell = true;
				break;
			}
		}

		return functionOutputCell;
	}

	/**
	 * 
	 * @param element
	 * 
	 * @return
	 */
	protected String getFunctionOutputReference(Element element) {
		String functionOutputGroup = null;

		for (Attribute attribute : element.attributes()) {
			if (attribute.getKey().equalsIgnoreCase(DATA_GROUP_OUTPUT_ATTRIBUTE)) {
				functionOutputGroup = attribute.getValue();
				break;
			}
		}

		return functionOutputGroup;
	}

	/**
	 * 
	 * @param element
	 * @return
	 */
	protected String getCellCommentText(Element element) {
		String commentText = null;

		for (Attribute attribute : element.attributes()) {
			if (attribute.getKey().equalsIgnoreCase(DATA_CELL_COMMENT_ATTRIBUTE)) {
				commentText = attribute.getValue();
				break;
			}
		}

		return StringUtils.trimToNull(commentText);
	}

	/**
	 * Return the Dimension for the cell comment. Return a Dimension of 3,1 if
	 * the dimension attribute is not present or has an invalid value.
	 * 
	 * @return
	 */
	protected Dimension getCellCommentDimension(Element element) {
		Dimension dimension = null;

		for (Attribute attribute : element.attributes()) {
			if (attribute.getKey().equalsIgnoreCase(DATA_CELL_COMMENT_DIMENSION_ATTRIBUTE)) {
				try {
					dimension = new Dimension(attribute.getValue());
				} catch (IllegalArgumentException ex) {
					dimension = new Dimension(3, 1);
				}
			}
		}

		return dimension != null ? dimension : new Dimension(3, 1);
	}

	/**
	 * 
	 * @param element
	 * @param attributeName
	 * 
	 * @return
	 */
	protected String[] getAttributeValues(Element element, String attributeName) {
		String values[] = null;

		if (element.hasAttr(attributeName)) {
			values = element.attr(attributeName).toLowerCase().split(",");

			for (String value : values) {
				value = value.trim().toLowerCase();
			}
		}

		return values;
	}

	/**
	 * 
	 * @param element
	 * 
	 * @return
	 */
	public Double getNumericValue(Element element) {
		Double numericValue = null;

		if (!element.hasAttr(DATA_TEXT_CELL))
			try {
				numericValue = NumberFormat.getInstance().parse(element.ownText()).doubleValue();
			} catch (ParseException e) {

			}

		return numericValue;
	}

	public abstract void renderCell(Element element, int rowIndex, int columnIndex);

	public abstract void addFunctionCell(int rowIndex, int columnIndex, CellRange range, Function function);
}
