/*
 * Copyright 2008-2019 by Emeric Vernat
 *
 *     This file is part of Java Melody.
 *
 * 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 net.bull.javamelody.swing.print;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.table.TableColumnModel;

import com.lowagie.text.BadElementException;
import com.lowagie.text.DocWriter;
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.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.Table;
import com.lowagie.text.pdf.PdfWriter;

import net.bull.javamelody.I18NAdapter;
import net.bull.javamelody.swing.table.MBasicTable;

/**
 * Objet d'impression/export pour Pdf (portrait ou paysage) en utilisant iText.
 *
 * @author Emeric Vernat
 */
public class MPdfWriter extends MPrinter {
	/**
	 * Icône Adobe Reader.
	 */
	public static final ImageIcon ADOBE_READER_ICON = new ImageIcon(
			MPdfWriter.class.getResource("/icons/adobe acrobat.png"));

	private final boolean landscape;

	/**
	 * Objet d'impression/export pour Pdf paysage.
	 *
	 * @author Emeric Vernat
	 */
	public static class LandscapePdfWriter extends MPdfWriter {
		/** Constructeur. */
		public LandscapePdfWriter() {
			super(true);
		}
	}

	/**
	 * Constructeur.
	 */
	public MPdfWriter() {
		this(false);
	}

	/**
	 * Constructeur.
	 *
	 * @param myLandscape
	 *           boolean
	 */
	public MPdfWriter(final boolean myLandscape) {
		super();
		this.landscape = myLandscape;
	}

	/**
	 * Retourne un booléen selon que l'export est en paysage ou en portrait.
	 *
	 * @return boolean
	 */
	public boolean isLandscape() {
		return landscape;
	}

	/**
	 * Lance l'impression (Méthode abstraite assurant le polymorphisme des instances.).
	 *
	 * @param table
	 *           MBasicTable
	 * @param out
	 *           OutputStream
	 * @throws IOException
	 *            Erreur disque
	 */
	@Override
	public void print(final MBasicTable table, final OutputStream out) throws IOException {
		writePdf(table, out);
	}

	/**
	 * Méthode abstraite : les instances doivent renvoyer l'extension du fichier exporté.
	 *
	 * @return String
	 */
	@Override
	public String getFileExtension() {
		return "pdf";
	}

	/**
	 * Méthode abstraite : les instances doivent renvoyer l'icône représentant le type.
	 *
	 * @return Icon
	 */
	@Override
	public Icon getIcon() {
		return ADOBE_READER_ICON;
	}

	/**
	 * Méthode abstraite : les instances doivent renvoyer leur nom.
	 *
	 * @return String
	 */
	@Override
	public String getName() {
		return I18NAdapter.getString(landscape ? "export_pdf_landscape" : "export_pdf");
	}

	/**
	 * Ecrit le pdf.
	 *
	 * @param table
	 *           MBasicTable
	 * @param out
	 *           OutputStream
	 * @throws IOException
	 *            e
	 */
	protected void writePdf(final MBasicTable table, final OutputStream out) throws IOException {
		try {
			// step 1: creation of a document-object
			final Rectangle pageSize = landscape ? PageSize.A4.rotate() : PageSize.A4;
			final Document document = new Document(pageSize, 50, 50, 50, 50);
			// step 2: we create a writer that listens to the document and directs a PDF-stream to out
			createWriter(table, document, out);

			// we add some meta information to the document, and we open it
			document.addAuthor(System.getProperty("user.name"));
			document.addCreator("JavaMelody");
			final String title = buildTitle(table);
			if (title != null) {
				document.addTitle(title);
			}
			document.open();

			// ouvre la boîte de dialogue Imprimer de Adobe Reader
			// if (writer instanceof PdfWriter) {
			// ((PdfWriter) writer).addJavaScript("this.print(true);", false);
			// }

			// table
			final Table datatable = new Table(table.getColumnCount());
			datatable.setCellsFitPage(true);
			datatable.setPadding(4);
			datatable.setSpacing(0);

			// headers
			renderHeaders(table, datatable);

			// data rows
			renderList(table, datatable);

			document.add(datatable);

			// we close the document
			document.close();
		} catch (final DocumentException e) {
			// on ne peut déclarer d'exception autre que IOException en throws
			throw new IOException(e);
		}
	}

	/**
	 * We create a writer that listens to the document and directs a PDF-stream to out
	 *
	 * @param table
	 *           MBasicTable
	 * @param document
	 *           Document
	 * @param out
	 *           OutputStream
	 * @return DocWriter
	 * @throws DocumentException
	 *            e
	 */
	protected DocWriter createWriter(final MBasicTable table, final Document document,
			final OutputStream out) throws DocumentException {
		final PdfWriter writer = PdfWriter.getInstance(document, out);
		// writer.setViewerPreferences(PdfWriter.PageLayoutTwoColumnLeft);

		// title
		if (table.getName() != null) {
			final HeaderFooter header = new HeaderFooter(new Phrase(table.getName()), false);
			header.setAlignment(Element.ALIGN_LEFT);
			header.setBorder(Rectangle.NO_BORDER);
			document.setHeader(header);
			document.addTitle(table.getName());
		}

		// simple page numbers : x
		// HeaderFooter footer = new HeaderFooter(new Phrase(), true);
		// footer.setAlignment(Element.ALIGN_RIGHT);
		// footer.setBorder(Rectangle.TOP);
		// document.setFooter(footer);

		// add the event handler for advanced page numbers : x/y
		writer.setPageEvent(new AdvancedPageNumberEvents());

		return writer;
	}

	/**
	 * Effectue le rendu des headers.
	 *
	 * @param table
	 *           MBasicTable
	 * @param datatable
	 *           Table
	 * @throws BadElementException
	 *            e
	 */
	protected void renderHeaders(final MBasicTable table, final Table datatable)
			throws BadElementException {
		final int columnCount = table.getColumnCount();
		final TableColumnModel columnModel = table.getColumnModel();
		// size of columns
		float totalWidth = 0;
		for (int i = 0; i < columnCount; i++) {
			totalWidth += columnModel.getColumn(i).getWidth();
		}
		final float[] headerwidths = new float[columnCount];
		for (int i = 0; i < columnCount; i++) {
			headerwidths[i] = 100f * columnModel.getColumn(i).getWidth() / totalWidth;
		}
		datatable.setWidths(headerwidths);
		datatable.setWidth(100f);

		// table header
		final Font font = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLD);
		datatable.getDefaultCell().setBorderWidth(2);
		datatable.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER);
		// datatable.setDefaultCellGrayFill(0.75f);

		String text;
		Object value;
		for (int i = 0; i < columnCount; i++) {
			value = columnModel.getColumn(i).getHeaderValue();
			text = value != null ? value.toString() : "";
			datatable.addCell(new Phrase(text, font));
		}
		// end of the table header
		datatable.endHeaders();
	}

	/**
	 * Effectue le rendu de la liste.
	 *
	 * @param table
	 *           MBasicTable
	 * @param datatable
	 *           Table
	 * @throws BadElementException
	 *            e
	 */
	protected void renderList(final MBasicTable table, final Table datatable)
			throws BadElementException {
		final int columnCount = table.getColumnCount();
		final int rowCount = table.getRowCount();
		// data rows
		final Font font = FontFactory.getFont(FontFactory.HELVETICA, 10, Font.NORMAL);
		datatable.getDefaultCell().setBorderWidth(1);
		datatable.getDefaultCell().setHorizontalAlignment(Element.ALIGN_LEFT);
		// datatable.setDefaultCellGrayFill(0);
		Object value;
		String text;
		int horizontalAlignment;
		for (int k = 0; k < rowCount; k++) {
			for (int i = 0; i < columnCount; i++) {
				value = getValueAt(table, k, i);
				if (value instanceof Number || value instanceof Date) {
					horizontalAlignment = Element.ALIGN_RIGHT;
				} else if (value instanceof Boolean) {
					horizontalAlignment = Element.ALIGN_CENTER;
				} else {
					horizontalAlignment = Element.ALIGN_LEFT;
				}
				datatable.getDefaultCell().setHorizontalAlignment(horizontalAlignment);
				text = getTextAt(table, k, i);
				datatable.addCell(new Phrase(8, text != null ? text : "", font));
			}
		}
	}
}
