package it.mice.voila.runtime.displaytag.poiexporter;

import it.mice.voila.runtime.displaytag.DisplayTagUtils;
import it.mice.voila.runtime.displaytag.basedec.Currency2DecimalDecorator;
import it.mice.voila.runtime.displaytag.basedec.Currency3DecimalDecorator;
import it.mice.voila.runtime.displaytag.basedec.Currency4DecimalDecorator;
import it.mice.voila.runtime.displaytag.basedec.DateDecorator;
import it.mice.voila.runtime.displaytag.basedec.DateTimeDecorator;
import it.mice.voila.runtime.displaytag.basedec.Percentage0DecimalDecorator;
import it.mice.voila.runtime.displaytag.basedec.Percentage1DecimalDecorator;
import it.mice.voila.runtime.displaytag.basedec.Percentage2DecimalDecorator;
import it.mice.voila.runtime.displaytag.basedec.ProgressBarDecorator;
import it.mice.voila.runtime.displaytag.basedec.Value1DecimalDecorator;
import it.mice.voila.runtime.displaytag.basedec.Value2DecimalDecorator;
import it.mice.voila.runtime.displaytag.basedec.Value3DecimalDecorator;
import it.mice.voila.runtime.displaytag.basedec.Value4DecimalDecorator;
import it.mice.voila.runtime.displaytag.basedec.Value5DecimalDecorator;
import it.mice.voila.runtime.displaytag.basedec.ValueDecorator;
import it.mice.voila.runtime.displaytag.exporter.AbstractTotalExporter;

import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

import javax.servlet.jsp.JspException;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFDataFormat;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Name;
import org.apache.poi.ss.util.CellRangeAddress;
import org.displaytag.Messages;
import org.displaytag.decorator.ColumnDecorator;
import org.displaytag.decorator.TotalTableDecorator;
import org.displaytag.exception.BaseNestableJspTagException;
import org.displaytag.exception.DecoratorException;
import org.displaytag.exception.SeverityEnum;
import org.displaytag.model.Column;
import org.displaytag.model.ColumnIterator;
import org.displaytag.model.HeaderCell;
import org.displaytag.model.Row;
import org.displaytag.model.RowIterator;
import org.displaytag.model.TableModel;

public class JfExcelHssfView extends AbstractTotalExporter {
    /**
     * Logger.
     */
    private static Log log = LogFactory.getLog(JfExcelHssfView.class);

    private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy",
			Locale.ITALY);
	// private DecimalFormat df = new DecimalFormat("#.##0,00dd/MM/yyyy",
	// Locale.ITALY);

	private HSSFCellStyle titleStyle;
	private HSSFCellStyle headerStyle;
	private HSSFCellStyle totalStyle;
	private HSSFCellStyle cellStyle;
	private HSSFCellStyle cellStyleForDate;
	private HSSFCellStyle cellStyleForDateTime;
	private HSSFCellStyle cellStyleForNumber;
	private HSSFCellStyle cellStyleForPercentage;
	private HSSFCellStyle cellStyleForPercentage1digit;
	private HSSFCellStyle cellStyleForPercentage2digit;
	private HSSFCellStyle cellStyleForValue;
	private HSSFCellStyle cellStyleForCurrency1digit;
	private HSSFCellStyle cellStyleForCurrency2digit;
	private HSSFCellStyle cellStyleForCurrency3digit;
	private HSSFCellStyle cellStyleForCurrency4digit;
	private HSSFCellStyle cellStyleForCurrency5digit;
	private HSSFFont titleFont;
	private HSSFFont headerFont;
	private HSSFFont dataFont;
	private boolean initialized = false;

	/**
	 * TableModel to render.
	 */
	private TableModel model;

	/**
	 * export full list?
	 */
	private boolean exportFull;

	/**
	 * include header in export?
	 */
	private boolean header;

	/**
	 * decorate export?
	 */
	private boolean decorated;

	protected void initStyles(HSSFWorkbook wb) {

		titleFont = wb.createFont();
		titleFont.setBold(true);
		titleFont.setFontHeightInPoints((short) 12);
		titleFont.setColor(HSSFColor.HSSFColorPredefined.BLUE.getIndex());

		headerFont = wb.createFont();
		headerFont.setBold(true);
		headerFont.setFontHeightInPoints((short) 10);
		headerFont.setColor(HSSFColor.HSSFColorPredefined.BLUE.getIndex());

		dataFont = wb.createFont();
		dataFont.setBold(true);
		dataFont.setFontHeightInPoints((short) 9);
		dataFont.setColor(HSSFColor.HSSFColorPredefined.BLACK.getIndex());

		titleStyle = wb.createCellStyle();
		// titleStyle.setFillBackgroundColor(HSSFColor.GREY_25_PERCENT.index);
		// titleStyle.setFillPattern(HSSFCellStyle.FINE_DOTS);
		titleStyle.setAlignment(HorizontalAlignment.LEFT);
		titleStyle.setFont(titleFont);

		headerStyle = wb.createCellStyle();
		// headerStyle.setFillBackgroundColor(HSSFColor.YELLOW.index);
		// headerStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
		headerStyle.setAlignment(HorizontalAlignment.CENTER);
		headerStyle.setBorderTop(BorderStyle.THIN);
		headerStyle.setBorderBottom(BorderStyle.THIN);
		headerStyle.setBorderLeft(BorderStyle.THIN);
		headerStyle.setBorderRight(BorderStyle.THIN);
		headerStyle.setWrapText(true);
		headerStyle.setFont(headerFont);

		totalStyle = wb.createCellStyle();
		totalStyle.setFillBackgroundColor(HSSFColor.HSSFColorPredefined.YELLOW.getIndex());
		totalStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

		cellStyle = createBorderedNoFillCellStyle(wb);
		cellStyle.setWrapText(true);

		cellStyleForDate = createBorderedNoFillCellStyle(wb);
		cellStyleForDate.setAlignment(HorizontalAlignment.RIGHT);
		cellStyleForDate.setDataFormat(HSSFDataFormat
				.getBuiltinFormat("m/d/yy"));

		cellStyleForDateTime = createBorderedNoFillCellStyle(wb);
		cellStyleForDateTime.setAlignment(HorizontalAlignment.RIGHT);
		cellStyleForDateTime.setDataFormat(HSSFDataFormat
				.getBuiltinFormat("m/d/yy h:mm"));

		cellStyleForCurrency1digit = createBorderedNoFillCellStyle(wb);
		cellStyleForCurrency1digit.setAlignment(HorizontalAlignment.RIGHT);
		cellStyleForCurrency1digit.setDataFormat(HSSFDataFormat
				.getBuiltinFormat("#,##0.00"));

		cellStyleForCurrency2digit = createBorderedNoFillCellStyle(wb);
		cellStyleForCurrency2digit.setAlignment(HorizontalAlignment.RIGHT);
		cellStyleForCurrency2digit.setDataFormat(HSSFDataFormat
				.getBuiltinFormat("#,##0.00"));

		cellStyleForCurrency3digit = createBorderedNoFillCellStyle(wb);
		cellStyleForCurrency3digit.setAlignment(HorizontalAlignment.RIGHT);
		cellStyleForCurrency3digit.setDataFormat(wb.createDataFormat().getFormat("#,##0.000"));

		cellStyleForCurrency4digit = createBorderedNoFillCellStyle(wb);
		cellStyleForCurrency4digit.setAlignment(HorizontalAlignment.RIGHT);
		cellStyleForCurrency4digit.setDataFormat(wb.createDataFormat().getFormat("#,##0.0000"));

		cellStyleForCurrency5digit = createBorderedNoFillCellStyle(wb);
		cellStyleForCurrency5digit.setAlignment(HorizontalAlignment.RIGHT);
		cellStyleForCurrency5digit.setDataFormat(wb.createDataFormat().getFormat("#,##0.00000"));

		cellStyleForPercentage = createBorderedNoFillCellStyle(wb);
		cellStyleForPercentage.setAlignment(HorizontalAlignment.RIGHT);
		cellStyleForPercentage.setDataFormat(HSSFDataFormat
				.getBuiltinFormat("0%"));

		cellStyleForPercentage1digit = createBorderedNoFillCellStyle(wb);
		cellStyleForPercentage1digit.setAlignment(HorizontalAlignment.RIGHT);
		cellStyleForPercentage1digit.setDataFormat(HSSFDataFormat
				.getBuiltinFormat("0.0%"));

		cellStyleForPercentage2digit = createBorderedNoFillCellStyle(wb);
		cellStyleForPercentage2digit.setAlignment(HorizontalAlignment.RIGHT);
		cellStyleForPercentage2digit.setDataFormat(HSSFDataFormat
				.getBuiltinFormat("0.00%"));

		cellStyleForNumber = createBorderedNoFillCellStyle(wb);
		cellStyleForNumber.setAlignment(HorizontalAlignment.RIGHT);
		cellStyleForNumber.setDataFormat(HSSFDataFormat.getBuiltinFormat("0"));

		initialized = true;
	}

	protected HSSFCellStyle createBorderedNoFillCellStyle(HSSFWorkbook wb) {
		HSSFCellStyle cs = wb.createCellStyle();
		cs.setFillPattern(FillPatternType.NO_FILL);
		cs.setBorderTop(BorderStyle.THIN);
		cs.setBorderBottom(BorderStyle.THIN);
		cs.setBorderLeft(BorderStyle.THIN);
		cs.setBorderRight(BorderStyle.THIN);
		// cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);
		cs.setFont(dataFont);

		return cs;
	}

	/**
	 * @see org.displaytag.export.ExportView#setParameters(TableModel, boolean,
	 *      boolean, boolean)
	 */
	public void setParameters(TableModel tableModel, boolean exportFullList,
			boolean includeHeader, boolean decorateValues) {
		this.model = tableModel;
		this.exportFull = exportFullList;
		this.header = includeHeader;
		this.decorated = decorateValues;
	}

	/**
	 * @throws ExcelGenerationException 
	 * @see org.displaytag.export.BinaryExportView#doExport(OutputStream)
	 */
	@SuppressWarnings("unchecked")
	public void doExport(OutputStream out) throws ExcelGenerationException {
		try {
			HSSFWorkbook wb = new HSSFWorkbook();
			HSSFSheet sheet = createSheet(wb);

			// if (!initialized) {
			initStyles(wb);
			// }

	        super.init(this.model);

			int rowNum = 0;
			int colNum = 0;

			short totNumColsMinusOne = Short.parseShort((this.model
					.getHeaderCellList().size() - 1)
					+ "");

			Object[] backupColumnDecorators = null;

			if (this.header) {
				rowNum = createSheetTitle(sheet, rowNum, totNumColsMinusOne);
				// Create an header row
				HSSFRow xlsPropRow = sheet.createRow(rowNum++);
				xlsPropRow.setZeroHeight(true);

				HSSFRow xlsRow = sheet.createRow(rowNum++);
				xlsRow.setHeightInPoints((float) 30);

				Iterator<HeaderCell> iterator = this.model.getHeaderCellList().iterator();

				backupColumnDecorators = new Object[this.model.getHeaderCellList().size()];

				while (iterator.hasNext()) {
					HeaderCell headerCell = iterator.next();
					backupColumnDecorators[colNum] = headerCell
							.getColumnDecorator();
					if (backupColumnDecorators[colNum] instanceof ValueDecorator) {
						headerCell.setColumnDecorator(null);
					}
					String columnHeader = headerCell.getTitle();

					if (columnHeader == null) {
						columnHeader = StringUtils.capitalize(headerCell.getBeanPropertyName());
					}
					HSSFCell cell = xlsRow.createCell(colNum);
					cell.setCellValue(new HSSFRichTextString(columnHeader));
					cell.setCellStyle(headerStyle);

					HSSFCell propCell = xlsPropRow.createCell(colNum++);
					if (headerCell.isImportable()) {
						propCell.setCellValue(headerCell.getBeanPropertyName());
					}
				}
			}

			// get the correct iterator (full or partial list according to the
			// exportFull field)
			RowIterator rowIterator = this.model
					.getRowIterator(this.exportFull);
			// iterator on rows

			while (rowIterator.hasNext()) {
				Row row = rowIterator.next();

	            super.startRow(row.getObject());

				HSSFRow xlsRow = sheet.createRow(rowNum++);
				colNum = 0;

				// iterator on columns
				ColumnIterator columnIterator = row
						.getColumnIterator(this.model.getHeaderCellList());

				while (columnIterator.hasNext()) {
					Column column = columnIterator.nextColumn();

					// Get the value to be displayed for the column
					Object value = column.getValue(this.decorated);

					HSSFCell cell = xlsRow.createCell(colNum++);
					setCellStyle(colNum, backupColumnDecorators, cell);

					writeCell(value, cell);
				}
			}

			generateTotals(sheet, rowNum, backupColumnDecorators);

			for (short i = 0; i < this.model.getHeaderCellList().size(); i++) {
				sheet.autoSizeColumn(i);
			}

			wb.write(out);
		} catch (Exception e) {
			throw new ExcelGenerationException(e);
		}
	}

	private void generateTotals(HSSFSheet sheet, int rowNum,
			Object[] backupColumnDecorators) {
		int colNum;
		if (model.getTableDecorator() != null && model.getTableDecorator() instanceof TotalTableDecorator) {
			HSSFRow xlsRow = sheet.createRow(rowNum++);
			colNum = 0;

			Iterator<HeaderCell> iterator = this.model.getHeaderCellList().iterator();
		    Map<String, Double> totals = super.getTotalRow();

		    while (iterator.hasNext()) {
		        HeaderCell headerCell = iterator.next();
		        Object value = totals.get(headerCell.getBeanPropertyName());
		        ColumnDecorator decorator = headerCell.getColumnDecorator();
		        if (decorator != null) {
					try {
						value = decorator.decorate(value);
					} catch (DecoratorException e) {
						log.warn(e.getMessage(), e);
					}
		        }
				HSSFCell cell = xlsRow.createCell(colNum++);
				setCellStyle(colNum, backupColumnDecorators, cell);

				writeCell(value, cell);
		    }
		}
	}

	/**
	 * Write the value to the cell. Override this method if you have complex
	 * data types that may need to be exported.
	 *
	 * @param value
	 *            the value of the cell
	 * @param cell
	 *            the cell to write it to
	 */
	protected void writeCell(Object value, HSSFCell cell) {
		if (value instanceof Number) {
			Number num = (Number) value;
			cell.setCellValue(num.doubleValue());
		} else if (value instanceof java.util.Date) {
			cell.setCellValue((java.util.Date) value);
		} else if (value instanceof java.sql.Date) {
			cell.setCellValue((java.sql.Date) value);
		} else if (value instanceof Calendar) {
			cell.setCellValue((Calendar) value);
		} else {
			cell.setCellValue(new HSSFRichTextString(escapeColumnValue(value)));
		}
	}

	// patch from Karsten Voges
	/**
	 * Escape certain values that are not permitted in excel cells.
	 *
	 * @param rawValue
	 *            the object value
	 * @return the escaped value
	 */
	protected String escapeColumnValue(Object rawValue) {
		if (rawValue == null) {
			return null;
		}
		String returnString = ObjectUtils.toString(rawValue);
		// escape the String to get the tabs, returns, newline explicit as \t \r
		// \n
		returnString = StringEscapeUtils.escapeJava(StringUtils
				.trimToEmpty(returnString));
		// remove tabs, insert four whitespaces instead
		returnString = StringUtils.replace(StringUtils.trim(returnString),
				"\\t", "    ");
		// remove the return, only newline valid in excel
		returnString = StringUtils.replace(StringUtils.trim(returnString),
				"\\r", " ");
		// unescape so that \n gets back to newline
		returnString = StringEscapeUtils.unescapeJava(returnString);
		return returnString;
	}

	protected void setCellStyle(int colNum, Object[] backupColumnDecorators,
			HSSFCell cell) {
		if (backupColumnDecorators[colNum - 1] instanceof Value2DecimalDecorator || backupColumnDecorators[colNum - 1] instanceof Currency2DecimalDecorator) {
			cell.setCellStyle(cellStyleForCurrency2digit);
		} else if (backupColumnDecorators[colNum - 1] instanceof Value1DecimalDecorator) {
			cell.setCellStyle(cellStyleForCurrency1digit);
		} else if (backupColumnDecorators[colNum - 1] instanceof Value3DecimalDecorator || backupColumnDecorators[colNum - 1] instanceof Currency3DecimalDecorator) {
			cell.setCellStyle(cellStyleForCurrency3digit);
		} else if (backupColumnDecorators[colNum - 1] instanceof Value4DecimalDecorator || backupColumnDecorators[colNum - 1] instanceof Currency4DecimalDecorator) {
			cell.setCellStyle(cellStyleForCurrency4digit);
		} else if (backupColumnDecorators[colNum - 1] instanceof Value5DecimalDecorator) {
			cell.setCellStyle(cellStyleForCurrency5digit);
		} else if (backupColumnDecorators[colNum - 1] instanceof DateDecorator) {
			cell.setCellStyle(cellStyleForDate);
		} else if (backupColumnDecorators[colNum - 1] instanceof DateTimeDecorator) {
			cell.setCellStyle(cellStyleForDateTime);
		} else if (backupColumnDecorators[colNum - 1] instanceof Percentage0DecimalDecorator) {
			cell.setCellStyle(cellStyleForPercentage);
		} else if (backupColumnDecorators[colNum - 1] instanceof Percentage1DecimalDecorator) {
			cell.setCellStyle(cellStyleForPercentage1digit);
		} else if (backupColumnDecorators[colNum - 1] instanceof Percentage2DecimalDecorator || backupColumnDecorators[colNum - 1] instanceof ProgressBarDecorator) {
			cell.setCellStyle(cellStyleForPercentage2digit);
		} else if (backupColumnDecorators[colNum - 1] instanceof ValueDecorator) {
			cell.setCellStyle(cellStyleForNumber);
		} else {
			cell.setCellStyle(cellStyle);
		}
	}

	protected int createSheetTitle(HSSFSheet sheet, int rowNum,
			short totNumColsMinusOne) {
		String title = DisplayTagUtils.getDocumentTitle(model);
		if (title != null) {
			// Create an header row
			HSSFRow xlsRow = sheet.createRow(rowNum++);
			xlsRow.setHeightInPoints((float) 25);

			HSSFCell cell = xlsRow.createCell(0);
			cell.setCellValue(new HSSFRichTextString(title));
			cell.setCellStyle(titleStyle);
			// sheet.addMergedRegion(new
			// CellRangeAddress(0,0,0,totNumColsMinusOne));
			if (totNumColsMinusOne > 0) {
				// sheet.addMergedRegion(new
				// Region(0,(short)0,0,totNumColsMinusOne));
				sheet.addMergedRegion(new CellRangeAddress(0, (short) 0, 0,
						totNumColsMinusOne));
			}
		}
		return rowNum;
	}

	protected HSSFSheet createSheet(HSSFWorkbook wb) {
		String sheetName = model.getProperties().getProperty(
				"export.excel.hssf.sheetName");
		if (sheetName == null) {
			sheetName = "Foglio1";
		}
		HSSFSheet sheet = wb.createSheet(sheetName);
		return sheet;
	}

	/**
	 * Wraps IText-generated exceptions.
	 *
	 * @author Fabrizio Giustina
	 * @version $Revision: 1086 $ ($Author: rapruitt $)
	 */
	static class ExcelGenerationException extends BaseNestableJspTagException {

		/**
		 * D1597A17A6.
		 */
		private static final long serialVersionUID = 899149338534L;

		/**
		 * Instantiate a new PdfGenerationException with a fixed message and the
		 * given cause.
		 *
		 * @param cause
		 *            Previous exception
		 */
		public ExcelGenerationException(Throwable cause) {
			super(JfExcelHssfView.class, Messages
					.getString("ExcelView.errorexporting"), cause); //$NON-NLS-1$
		}

		/**
		 * @see org.displaytag.exception.BaseNestableJspTagException#getSeverity()
		 */
		public SeverityEnum getSeverity() {
			return SeverityEnum.ERROR;
		}
	}

	public HSSFCellStyle getCellStyle() {
		return cellStyle;
	}

	public HSSFCellStyle getCellStyleForCurrency() {
		return cellStyleForCurrency2digit;
	}

	public HSSFCellStyle getCellStyleForDate() {
		return cellStyleForDate;
	}

	public HSSFCellStyle getCellStyleForNumber() {
		return cellStyleForNumber;
	}

	public HSSFCellStyle getCellStyleForPercentage() {
		return cellStyleForPercentage1digit;
	}

	public HSSFCellStyle getCellStyleForValue() {
		return cellStyleForValue;
	}

	public HSSFFont getDataFont() {
		return dataFont;
	}

	public boolean isDecorated() {
		return decorated;
	}

	public boolean isExportFull() {
		return exportFull;
	}

	public boolean isHeader() {
		return header;
	}

	public HSSFFont getHeaderFont() {
		return headerFont;
	}

	public HSSFCellStyle getHeaderStyle() {
		return headerStyle;
	}

	public boolean isInitialized() {
		return initialized;
	}

	public TableModel getModel() {
		return model;
	}

	public HSSFFont getTitleFont() {
		return titleFont;
	}

	public HSSFCellStyle getTitleStyle() {
		return titleStyle;
	}

	public void setCellStyle(HSSFCellStyle cellStyle) {
		this.cellStyle = cellStyle;
	}

	public void setCellStyleForCurrency(HSSFCellStyle cellStyleForCurrency) {
		this.cellStyleForCurrency2digit = cellStyleForCurrency;
	}

	public void setCellStyleForDate(HSSFCellStyle cellStyleForDate) {
		this.cellStyleForDate = cellStyleForDate;
	}

	public void setCellStyleForNumber(HSSFCellStyle cellStyleForNumber) {
		this.cellStyleForNumber = cellStyleForNumber;
	}

	public void setCellStyleForPercentage(HSSFCellStyle cellStyleForPercentage) {
		this.cellStyleForPercentage1digit = cellStyleForPercentage;
	}

	public void setCellStyleForValue(HSSFCellStyle cellStyleForValue) {
		this.cellStyleForValue = cellStyleForValue;
	}

	public void setDataFont(HSSFFont dataFont) {
		this.dataFont = dataFont;
	}

	public void setDecorated(boolean decorated) {
		this.decorated = decorated;
	}

	public void setExportFull(boolean exportFull) {
		this.exportFull = exportFull;
	}

	public void setHeader(boolean header) {
		this.header = header;
	}

	public void setHeaderFont(HSSFFont headerFont) {
		this.headerFont = headerFont;
	}

	public void setHeaderStyle(HSSFCellStyle headerStyle) {
		this.headerStyle = headerStyle;
	}

	public void setInitialized(boolean initialized) {
		this.initialized = initialized;
	}

	public void setModel(TableModel model) {
		this.model = model;
	}

	public void setTitleFont(HSSFFont titleFont) {
		this.titleFont = titleFont;
	}

	public void setTitleStyle(HSSFCellStyle titleStyle) {
		this.titleStyle = titleStyle;
	}

	/**
	 * @return "application/vnd.ms-excel"
	 * @see org.displaytag.export.BaseExportView#getMimeType()
	 */
	public String getMimeType() {
		return "application/vnd.ms-excel"; //$NON-NLS-1$
	}

}
