/*
 * Copyright 1999-2004 The Apache Software Foundation.
 * 
 * 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 org.apache.cocoon.generation;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameterizable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.generation.ServletGenerator;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
 * This generator generates - using Apache POI -a Gnumeric compliant XML 
 * Document from a Microsoft Excel Workbook.
 * 
 * Sitemap use:<br>
 * &lt;map:generate type="excel" src="{1}.xls"/&gt;
 * 
 * Eventually you can set the parameter "formatting" = "true" in order to
 * receive not only the data but also the formatting information of the 
 * workbook.
 * 
 * You can also set the encoding and the namespace prefix ad URL (default
 * are "ISO-8859-1", "gmr" and "http://www.gnome.org/gnumeric/v7").
 * 
 * @author <a href="patrick@arpage.ch">Patrick Herber </a>
 */
public class ExcelGenerator extends ServletGenerator implements Parameterizable {

	public final static String PARAM_ENCODING = "encoding";;
	public final static String PARAM_NAMESPACE_PREFIX = "ns-prefix";
	public final static String PARAM_NAMESPACE_URL = "ns-url";
	public final static String PARAM_FORMATTING = "formatting";

	public final static String DEFAULT_ENCODING = "ISO-8859-1";
	public final static String DEFAULT_NAMESPACE_PREFIX = "gmr";
	public final static String DEFAULT_NAMESPACE_URL = "http://www.gnome.org/gnumeric/v7";

	protected Source inputSource;
	private String namespaceUrl;
	private String namespacePrefix;
	private String encoding;
	private boolean formatting;
	private AttributesImpl attr = new AttributesImpl();

	/**
	 * Recycle this component. All instance variables are set to
	 * <code>null</code>.
	 */
	public void recycle() {
		if (null != this.inputSource) {
			super.resolver.release(this.inputSource);
			this.inputSource = null;
		}
		this.formatting = false;
		this.attr.clear();
		super.recycle();
	}

	public void parameterize(Parameters parameters) throws ParameterException {
		this.encoding = parameters.getParameter(PARAM_ENCODING,
				DEFAULT_ENCODING);
		this.namespacePrefix = parameters.getParameter(PARAM_NAMESPACE_PREFIX,
				DEFAULT_NAMESPACE_PREFIX);
		this.namespaceUrl = parameters.getParameter(PARAM_NAMESPACE_URL,
				DEFAULT_NAMESPACE_URL);
	}

	public void setup(SourceResolver resolver, Map objectModel, String src,
			Parameters par) throws ProcessingException, SAXException,
			IOException {
		super.setup(resolver, objectModel, src, par);
		try {
			this.inputSource = super.resolver.resolveURI(src);
		} catch (SourceException se) {
			throw SourceUtil.handle("Error resolving '" + src + "'.", se);
		}
		this.encoding = par.getParameter(PARAM_ENCODING, encoding);
		this.namespacePrefix = par.getParameter(PARAM_NAMESPACE_PREFIX,
				namespacePrefix);
		this.namespaceUrl = par.getParameter(PARAM_NAMESPACE_URL, namespaceUrl);
		this.formatting = par.getParameterAsBoolean(PARAM_FORMATTING, false);
	}

	/**
	 * Generate XML data.
	 */
	public void generate() throws SAXException {
		try {
			HSSFWorkbook workbook = 
				new HSSFWorkbook(this.inputSource.getInputStream());
			writeXML(workbook);
		} catch (Exception e) {
			getLogger().error("Error generating XML Data", e);
		}
	}


	/**
	 * Writes out the workbook data as XML, without formatting information
	 */
	private void writeXML(HSSFWorkbook workbook) throws Exception {
		this.contentHandler.startDocument();
		start("Workbook");
		start("SheetNameIndex");
		for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
			start("SheetName");
			data(workbook.getSheetName(i));
			end("SheetName");
		}
		end("SheetNameIndex");
		start("Sheets");
		for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
			HSSFSheet sheet = workbook.getSheetAt(i);
			start("Sheet");
			start("Name");
			data(workbook.getSheetName(i));
			end("Name");
			start("MaxCol");
			data(Integer.toString(getMaxCol(sheet)));
			end("MaxCol");
			start("MaxRow");
			data(Integer.toString(sheet.getLastRowNum()));
			end("MaxRow");
			if (formatting) {
				writeStyles(workbook, sheet);
			}
			start("Cells");
			HSSFRow row = null;
			HSSFCell cell = null;
			Iterator cells = null;
			Iterator rows = sheet.rowIterator();
			while (rows.hasNext()) {
				row = (HSSFRow) rows.next();
				cells = row.cellIterator();
				while (cells.hasNext()) {
					cell = (HSSFCell) cells.next();
					attribute("Row", Integer.toString(row.getRowNum()));
					attribute("Col", Short.toString(cell.getCellNum()));
					attribute("ValueType", getValueType(cell.getCellType()));
					start("Cell");
					data(cell.getStringCellValue());
					end("Cell");
				}
			}
			end("Cells");
			end("Sheet");
		}
		end("Sheets");
		end("Workbook");
		this.contentHandler.endDocument();
	}
	
	/**
	 * Returns the max column index of the given sheet
	 * @param sheet
	 * @return the max column index
	 */
	private int getMaxCol(HSSFSheet sheet) {
		int max = -1;
		HSSFRow row = null;
		Iterator rows = sheet.rowIterator();
		while (rows.hasNext()) {
			row = (HSSFRow) rows.next();
			int lastNum = row.getLastCellNum();
			if (lastNum > max) {
				max = lastNum;
			}
		}	
		return max;
	}

	/**
	 * Returns the Gnumeric cell type.
	 * @param cellType	POI cell type
	 * @return the Gnumeric cell type.
	 */
	private String getValueType(int cellType) {
		switch (cellType) {
			case HSSFCell.CELL_TYPE_BLANK:
				return "10";
			case HSSFCell.CELL_TYPE_BOOLEAN:
				return "20";
			case HSSFCell.CELL_TYPE_NUMERIC:
				return "40";
			case HSSFCell.CELL_TYPE_ERROR:
				return "50";
			case HSSFCell.CELL_TYPE_FORMULA:
			case HSSFCell.CELL_TYPE_STRING:
			default:
				return "60";
		}
	}

	/**
	 * Writes out the workbook data as XML, with formatting information
	 */
	private void writeStyles(HSSFWorkbook workbook, HSSFSheet sheet) throws Exception {
		start("Styles");
		HSSFRow row = null;
		HSSFCell cell = null;
		Iterator cells = null;
		Iterator rows = sheet.rowIterator();
		while (rows.hasNext()) {
			row = (HSSFRow) rows.next();
			cells = row.cellIterator();
			while (cells.hasNext()) {
				cell = (HSSFCell) cells.next();
				attribute("startRow", Integer.toString(row.getRowNum()));
				attribute("endRow", Integer.toString(row.getRowNum()));
				attribute("startCol", Short.toString(cell.getCellNum()));
				attribute("endCol", Short.toString(cell.getCellNum()));
				start("StyleRegion");
				HSSFCellStyle style = cell.getCellStyle();
				attribute("HAlign", Integer.toString(style.getAlignment()));
				attribute("VAlign", Integer.toString(style.getVerticalAlignment()));
				attribute("WrapText", ((style.getWrapText()) ? "1" : "0"));
				attribute("Orient", Integer.toString(style.getRotation()));
				attribute("Indent", Integer.toString(style.getIndention()));
				attribute("Locked", ((style.getLocked()) ? "1" : "0"));
				attribute("Hidden", ((style.getHidden()) ? "1" : "0"));
				attribute("Fore", workbook.getCustomPalette().getColor(style.getFillForegroundColor()).getHexString());
				attribute("Back", workbook.getCustomPalette().getColor(style.getFillBackgroundColor()).getHexString());
				attribute("PatternColor", Integer.toString(style.getFillPattern())); // TODO
				attribute("Format", "General"); // TODO
				start("Style");
				HSSFFont font = workbook.getFontAt(style.getFontIndex());
				attribute("Unit", Short.toString(font.getFontHeightInPoints()));
				attribute("Bold", Short.toString(font.getBoldweight()));
				attribute("Italic", ((font.getItalic()) ? "1" : "0"));
				attribute("Unterline", Integer.toString(font.getUnderline()));
				attribute("StrikeThrough", ((font.getStrikeout()) ? "1" : "0"));
				start("Font");
				data(font.getFontName());
				end("Font");
				end("Style");
				end("StyleRegion");
			}
		}
		end("Styles");
	}
	
	// Utilities methods 

	/**
	 * Adds an attribute with the given name and value.
	 */
	private void attribute(String name, String value) {
		attr.addAttribute("", name, name, "CDATA", value);
	}

	/**
	 * Starts an element with the given local name.
	 * @param name	local name of the element
	 * @throws SAXException
	 */
	private void start(String name) throws SAXException {
		super.contentHandler.startElement(namespaceUrl, name, namespacePrefix
				+ ":" + name, attr);
		attr.clear();
	}

	/**
	 * Ends the given element.
	 * @param name local name of the element
	 * @throws SAXException
	 */
	private void end(String name) throws SAXException {
		super.contentHandler.endElement(namespaceUrl, name, namespacePrefix
				+ ":" + name);
	}

	/** 
	 * Writes the given element data.
	 * @param data
	 * @throws SAXException
	 */
	private void data(String data) throws SAXException {
		super.contentHandler.characters(data.toCharArray(), 0, data.length());
	}
}


