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

import it.mice.voila.runtime.displaytag.basedec.ValueDecorator;
import it.mice.voila.runtime.displaytag.exporter.AbstractTotalExporter;

import java.awt.Color;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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;
import org.displaytag.util.TagConstants;

import com.lowagie.text.BadElementException;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.FontFactory;
import com.lowagie.text.HeaderFooter;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;

/**
 * Created by IntelliJ IDEA.
 * User: zzy9v4
 * Date: May 23, 2006
 * Time: 11:50:45 AM
 * To change this template use File | Settings | File Templates.
 */
public abstract class AbstractPdfExporter extends AbstractTotalExporter {
    /**
     * Logger.
     */
    private static Log log = LogFactory.getLog(AbstractPdfExporter.class);

    private PageContext pageContext;

    /**
     * PDF table fragment size for fragmentation of large tables.
     * for more detail see http://itextdocs.lowagie.com/tutorial/objects/tables/pdfptable/
     */
    protected static final int PDFTABLE_FRAGMENT_SIZE = 20;

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

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

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

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

    /**
     * This is the table, added as an Element to the PDF document. It contains all the data, needed to represent the
     * visible table into the PDF
     */
    protected PdfPTable tablePDF;

    /**
     * The default font used in the document.
     */
    protected Font smallFont;

    /**
     * @see org.displaytag.export.ExportView#setParameters(org.displaytag.model.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;
    }

    /**
     * Initialize the main info holder table.
     *
     * @throws BadElementException for errors during table initialization
     */
    protected void initTable() throws BadElementException {
        tablePDF = new PdfPTable(this.model.getNumberOfColumns());

        tablePDF.setWidthPercentage(100);
        tablePDF.setHeaderRows(1);

        smallFont = FontFactory.getFont(FontFactory.HELVETICA, 7, Font.NORMAL, new Color(0, 0, 0));
        
        super.init(this.model);
    }

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

    /**
     * The overall PDF table generator.
     *
     * @throws javax.servlet.jsp.JspException        for errors during value retrieving from the table model
     * @throws BadElementException IText exception
     */
    protected void generatePDFTable(Document document) throws JspException, DocumentException {
        if (this.header) {
            generateHeaders();
        }
        generateRows(document);
    }

    /**
     * @see org.displaytag.export.BinaryExportView#doExport(java.io.OutputStream)
     */
    public void doExport(OutputStream out) throws JspException {
        try {
            // Initialize the table with the appropriate number of columns
            initTable();

            // Initialize the Document and register it with PdfWriter listener and the OutputStream
            Document document = new Document(PageSize.A4.rotate(), 60, 60, 40, 40);
            PdfWriter.getInstance(document, out);

            document.addCreationDate();

            prepareHeaderFooter(document);

            document.open();

            prepareDocumentTitle(document);

            // Fill the virtual PDF table with the necessary data
            generatePDFTable(document);
            document.add(this.tablePDF);
            document.close();

        }
        catch (Exception e) {
            throw new PdfGenerationException(e);
        }
    }

    /**
     * Generates the header cells, which persist on every page of the PDF document.
     *
     * @throws BadElementException IText exception
     */
    @SuppressWarnings("unchecked")
	protected void generateHeaders() throws BadElementException {
        Iterator<HeaderCell> iterator = this.model.getHeaderCellList().iterator();

        while (iterator.hasNext()) {
            HeaderCell headerCell = iterator.next();

            String columnHeader = headerCell.getTitle();

            if (columnHeader == null) {
                columnHeader = StringUtils.capitalize(headerCell.getBeanPropertyName());
            }

            PdfPCell hdrCell = getCell(columnHeader);
            hdrCell.setGrayFill(0.9f);
            hdrCell.setHorizontalAlignment(Element.ALIGN_CENTER);
            tablePDF.addCell(hdrCell);
        }
    }

    /**
     * Generates all the row cells.
     *
     * @throws javax.servlet.jsp.JspException        for errors during value retrieving from the table model
     * @throws BadElementException errors while generating content
     */
    protected void generateRows(Document document) throws JspException, DocumentException {
        int rowNum = 0;
        // 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()) {
            if (rowNum % PDFTABLE_FRAGMENT_SIZE == PDFTABLE_FRAGMENT_SIZE - 1) {
                document.add(tablePDF);
                tablePDF.deleteBodyRows();
                tablePDF.setSkipFirstHeader(true);
            }
            Row row = rowIterator.next();

            super.startRow(row.getObject());
            
            // 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);

                processPdfCell(column.getHeader(), value, false);
            }
            rowNum++;
        }
        
        generateTotals();
    }

	private void generateTotals() throws BadElementException {
		if (model.getTableDecorator() != null && model.getTableDecorator() instanceof TotalTableDecorator) {
            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);
					}
                }
                processPdfCell(headerCell, value, true);
            }
        }
	}

	private void processPdfCell(HeaderCell hc, Object value, boolean isTotal)
			throws BadElementException {
		PdfPCell cell = getCell(value == null ? "" : value.toString());

		if (isTotal) {
			if (value == null || value == "") {
		        cell.setBorder(Rectangle.NO_BORDER);
			} else {
				cell.setBackgroundColor(Color.LIGHT_GRAY);
			}
		}
		Object al = hc.getHtmlAttributes().get(TagConstants.ATTRIBUTE_ALIGN);
		if (al == null) {
		    Object dec = hc.getColumnDecorator();
		    if (dec instanceof ValueDecorator) {
		    	al = "right";
		    }
		}
		if (al != null) {
		    String align = (String)al;
		    if (align.equalsIgnoreCase("left")) {
		        cell.setHorizontalAlignment(Element.ALIGN_LEFT);
		    } else if(align.equalsIgnoreCase("right")) {
		        cell.setHorizontalAlignment(Element.ALIGN_RIGHT);
		    } else if(align.equalsIgnoreCase("center")) {
		        cell.setHorizontalAlignment(Element.ALIGN_CENTER);
		    }
		}
		tablePDF.addCell(cell);
	}

    /**
     * Returns a formatted cell for the given value.
     *
     * @param value cell value
     * @return Cell
     * @throws BadElementException errors while generating content
     */
    protected PdfPCell getCell(String value) throws BadElementException {
        PdfPCell cell = new PdfPCell(new Phrase(StringUtils.trimToEmpty(value), smallFont));
        cell.setVerticalAlignment(Element.ALIGN_TOP);
        cell.setLeading(8, 0);
        return cell;
    }

    protected void prepareDocumentTitle(Document document) throws DocumentException {
        Paragraph title = new Paragraph("Customize this document title by overriding prepareDocumentTitle method!");
        title.setAlignment(Element.ALIGN_CENTER);
        document.add(title);
        document.add(new Phrase(""));
    }

    protected void prepareHeaderFooter(Document document) {
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
        String footerText = sdf.format(new Date()) + "                         pagina: ";
        HeaderFooter footer = new HeaderFooter(new Phrase(footerText, smallFont), true);
        footer.setBorder(Rectangle.NO_BORDER);
        footer.setAlignment(Element.ALIGN_CENTER);
        document.setFooter(footer);
    }

    /**
     * Wraps IText-generated exceptions.
     *
     * @author Fabrizio Giustina
     * @version $Revision: 1.1 $ ($Author: zzy9v4 $)
     */
    static class PdfGenerationException 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 PdfGenerationException(Throwable cause) {
            super(AbstractPdfExporter.class, Messages.getString("PdfView.errorexporting"), cause); //$NON-NLS-1$
        }

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

    /**
     * Allow to register the PageContext instance useful to access all view context parameters such
     * as: request parameter, request/session attributes, etc...
     *
     * @param pageContext
     */
    public void setPageContext(PageContext pageContext) {
        this.pageContext = pageContext;
    }

    public PageContext getPageContext() {
        return pageContext;
    }
}