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

import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;

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.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
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.ObjectLookupException;
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 it.mice.voila.runtime.displaytag.exporter.AbstractTotalExporter;

/**
 * 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;

    protected PDDocument document;
    
    protected PDPageContentStream contentStream;

    /**
     * The default font used in the document.
     */
    protected PDType1Font smallFont = PDType1Font.HELVETICA;

    /**
     * @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 initDocument(OutputStream out) throws Exception {
    	PDRectangle landscapeA4 = new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth());

        document = new PDDocument();
        PDPage page = new PDPage(landscapeA4);
        document.addPage(page);
        contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, true);
        contentStream.setFont(smallFont, 7);
        super.init(model);
    }

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


    /**
     * @throws PdfGenerationException 
     * @see org.displaytag.export.BinaryExportView#doExport(java.io.OutputStream)
     */
    public void doExport(OutputStream out) throws PdfGenerationException {
    	try {
            initDocument(out);
            
            prepareDocumentTitle();
            prepareHeaderFooter();

            if (header) {
                generateHeaders();
            }
            generateRows();

            contentStream.close();
            document.save(out);
            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 IOException {
        Iterator<HeaderCell> iterator = this.model.getHeaderCellList().iterator();
        float yPosition = 520;
        float xPosition = 50;
        float cellWidth = (PDRectangle.A4.getHeight() - 100) / model.getNumberOfColumns(); // Dynamic cell width
        float cellHeight = 10; // Height of each cell
        while (iterator.hasNext()) {
            HeaderCell headerCell = iterator.next();
            String columnHeader = headerCell.getTitle();
            if (columnHeader == null) {
                columnHeader = StringUtils.capitalize(headerCell.getBeanPropertyName());
            }
            addCell(columnHeader, xPosition, yPosition, cellWidth, cellHeight, true);
            xPosition += cellWidth;
        }
    }

    /**
     * Generates all the row cells.
     *
     * @throws javax.servlet.jsp.JspException        for errors during value retrieving from the table model
     * @throws DecoratorException 
     * @throws ObjectLookupException 
     * @throws BadElementException errors while generating content
     */
    protected void generateRows() throws IOException, JspException, ObjectLookupException, DecoratorException {
        int rowNum = 0;
        float yPosition = 510;
        float xPosition;
        float cellWidth = (PDRectangle.A4.getHeight() - 100) / model.getNumberOfColumns(); // Dynamic cell width
        float cellHeight = 0;
        float margin = 50;
        float footerHeight = 30;        
        float usablePageHeight = yPosition - margin - footerHeight;
        RowIterator rowIterator = this.model.getRowIterator(this.exportFull);
        int maxRowsPerPage = PDFTABLE_FRAGMENT_SIZE;
        while (rowIterator.hasNext()) {
            if (rowNum % maxRowsPerPage == maxRowsPerPage - 1) {
                contentStream.close();
                PDRectangle landscapeA4 = new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth());
                PDPage page = new PDPage(landscapeA4);
                document.addPage(page);
                contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, true);
                contentStream.setFont(smallFont, 7);
                yPosition = 540;
            }
            Row row = rowIterator.next();
            xPosition = 50;
            ColumnIterator columnIterator = row.getColumnIterator(this.model.getHeaderCellList());
            cellHeight = calculateRowHeight(row, cellWidth);
            maxRowsPerPage = (int) Math.floor(usablePageHeight / cellHeight);
            while (columnIterator.hasNext()) {
                Column column = columnIterator.nextColumn();
                Object value = column.getValue(this.decorated);
//                cellHeight = calculateCellHeight(value.toString(), cellWidth);
                addCell(value.toString(), xPosition, yPosition, cellWidth, cellHeight, false);
                xPosition += cellWidth;
            }
            yPosition -= cellHeight;
            rowNum++;
        }
        generateTotals();
    }
    
    private float calculateRowHeight(Row row, float cellWidth) throws IOException, ObjectLookupException, DecoratorException {
    	float maxHeight = 0;
        ColumnIterator columnIterator = row.getColumnIterator(this.model.getHeaderCellList());
    	while (columnIterator.hasNext()) {
            Column column = columnIterator.nextColumn();
            Object value = column.getValue(this.decorated);
            float cellHeight = calculateCellHeight(value.toString(), cellWidth);
            if (cellHeight > maxHeight) {
            	maxHeight = cellHeight;
            }
        }
    	return maxHeight;
    }
    
    private float calculateCellHeight(String text, float cellWidth) throws IOException {
        contentStream.setFont(smallFont, 7);
        String sanitizedText = sanitizeText(text);
        float textWidth = smallFont.getStringWidth(sanitizedText) / 1000 * 7; // Convert font size to PDF points
        int lines = (int) Math.ceil(textWidth / cellWidth);
        return lines * 10 + 4; // 10 points per line + 4 points padding
    }

    private void generateTotals() throws IOException {
        if (model.getTableDecorator() != null && model.getTableDecorator() instanceof TotalTableDecorator) {
            Iterator<HeaderCell> iterator = this.model.getHeaderCellList().iterator();
            Map<String, Double> totals = super.getTotalRow();

            float yPosition = 540; // Adjust as necessary
            float xPosition = 50;
            float cellWidth = 100; // Width of each cell
            float cellHeight = 20; // Height of each cell
            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);
                    }
                }
                addCell(value == null ? "" : value.toString(), xPosition, yPosition, cellWidth, cellHeight, true);
                yPosition -= cellWidth;
            }
        }
    }
    
    private String sanitizeText(String text) {
        // Rimuove i caratteri di controllo dal testo
        return text.replaceAll("\\p{Cntrl}", "");
    }
    
    /**
     * Returns a formatted cell for the given value.
     *
     * @param value cell value
     * @return Cell
     * @throws BadElementException errors while generating content
     */
//	private void addCell(String value, float x, float y, boolean isHeader) throws IOException {
//        value = sanitizeText(value);
//		contentStream.beginText();
//        contentStream.newLineAtOffset(x, y);
//        contentStream.setFont(smallFont, 7);
//        contentStream.showText(StringUtils.trimToEmpty(value));
//        contentStream.endText();
//        
//        if (isHeader) {
//            contentStream.setNonStrokingColor(200); // Light gray
//            contentStream.addRect(x, y - 5, 50, 10); // Cell dimensions
//            contentStream.fill();
//            contentStream.setNonStrokingColor(0); // Reset to black
//        }
//
//        // Manual vertical alignment
//        float leading = 8;
//        contentStream.setLeading(leading);
//    }
    protected void addCell(String value, float x, float y, float width, float height, boolean isHeader) throws IOException {
        value = sanitizeText(value);
        
        contentStream.setNonStrokingColor(isHeader ? 200 : 255); // Light gray for headers
        contentStream.addRect(x, y - height, width, height);
        contentStream.fill();
        contentStream.setNonStrokingColor(0); // Reset to black
        contentStream.setLineWidth(0.5f);
        contentStream.addRect(x, y - height, width, height);
        contentStream.stroke();

        if (isHeader) {
            float titleWidth = smallFont.getStringWidth(value) / 1000 * 7;
            float textX = x + (width - titleWidth) / 2; // Center horizontally
            float textY = y - (height) / 2 - 3; // Center vertically
            contentStream.beginText();
            contentStream.newLineAtOffset(textX, textY);
            contentStream.setFont(smallFont, 7);
            contentStream.showText(value);
            contentStream.endText();
        } else {

            float textY = y - 10; // Start text just below the top border of the cell
            float textX = x + 2; // Start text with some padding from the left border of the cell
            float availableWidth = width - 4; // Subtract padding from both sides

            String[] lines = splitTextIntoLines(value, availableWidth);

            for (String line : lines) {
//                if (textY - 10 < y - height) {
//                    // If text goes beyond cell height, truncate remaining text
//                    break;
//                }
                contentStream.beginText();
                contentStream.newLineAtOffset(textX, textY);
                contentStream.setFont(smallFont, 7);
                contentStream.showText(line);
                contentStream.endText();
                textY -= 10; // Move to next line
            }
        }
    }

    private String[] splitTextIntoLines(String text, float maxWidth) throws IOException {
        List<String> lines = new ArrayList<>();
        StringBuilder currentLine = new StringBuilder();
        String[] words = text.split(" ");
        
        for (String word : words) {
            if (smallFont.getStringWidth(currentLine + " " + word) / 1000 * 7 > maxWidth) {
                lines.add(currentLine.toString());
                currentLine = new StringBuilder(word);
            } else {
                if (currentLine.length() > 0) {
                    currentLine.append(" ");
                }
                currentLine.append(word);
            }
        }
        
        if (currentLine.length() > 0) {
            lines.add(currentLine.toString());
        }

        return lines.toArray(new String[0]);
    }

	protected void prepareDocumentTitle() throws IOException {
        contentStream.beginText();
        contentStream.newLineAtOffset(document.getPage(0).getMediaBox().getWidth() / 2, 800); // Centered on the page
        contentStream.setFont(smallFont, 12);
        contentStream.showText("Customize this document title by overriding prepareDocumentTitle method!");
        contentStream.endText();
    }

    protected void prepareHeaderFooter() throws IOException {
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
        String footerText = sdf.format(new Date()) + "                         pagina: ";
        contentStream.beginText();
        contentStream.newLineAtOffset(300, 30); // Bottom of the page
        contentStream.setFont(smallFont, 7);
        contentStream.showText(footerText);
        contentStream.endText();
    }

    /**
     * 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;
    }
}