package pdf;

import java.awt.HeadlessException;
import java.awt.print.Book;
import java.awt.print.PageFormat;
import java.awt.print.Pageable;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Locale;

import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.IntegerSyntax;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.TextSyntax;
import javax.print.attribute.standard.Copies;
import javax.print.attribute.standard.JobName;
import javax.print.attribute.standard.RequestingUserName;
import javax.swing.JFileChooser;

/**
 * <p>
 * This class extends awt's PrinterJob, to provide a simple method of writing
 * PDF documents.
 * </p>
 * 
 * <p>
 * You can use this with any code that uses Java's printing mechanism. It does
 * include a few extra methods to provide access to some of PDF's features like
 * annotations, or outlines.
 * </p>
 * 
 * @author Gilbert DeLeeuw, gil1@users.sourceforge.net
 */
public class PDFPrinterJob extends PrinterJob {

	/**
	 * The file chooser
	 */
	private static JFileChooser fileChooser;

	/**
	 * Printing options;
	 */
	private PrintRequestAttributeSet attributes;

	/**
	 * PDF document properties
	 */
	private PDFInfo info;

	/**
	 * A pageable, or null
	 */
	private Pageable pageable = null;

	/**
	 * Page format.
	 */
	private PageFormat pageFormat;

	/**
	 * The Printable object to print.
	 */
	private Printable printable;

	/**
	 * The actual print job.
	 */
	private PDFJob printJob;

	/**
	 * Initializes a new instance of <code>PDFPrinterJob</code>.
	 */
	public PDFPrinterJob() {
		attributes = new HashPrintRequestAttributeSet();
		info = new PDFInfo();
		pageFormat = new PageFormat(); // default page format.
		setJobName("Java Printing");
	}

	@Override
	public void cancel() {
		// Cancel is not an option
	}

	@Override
	public PageFormat defaultPage(PageFormat page) {
		return validatePage(page);
	}

	@Override
	public int getCopies() {
		return ((IntegerSyntax) attributes.get(Copies.class)).getValue();
	}

	@Override
	public String getJobName() {
		return ((TextSyntax) attributes.get(JobName.class)).getValue();
	}

	public static PrinterJob getPrinterJob() {
		return new PDFPrinterJob();
	}

	@Override
	public String getUserName() {
		return ((TextSyntax) attributes.get(RequestingUserName.class))
				.getValue();
	}

	@Override
	public boolean isCancelled() {
		return false;
	}

	@Override
	public PageFormat pageDialog(PageFormat page) throws HeadlessException {
		// No page dialog is supported.
		return (PageFormat) page.clone();
	}

	/**
	 * Prints a set of pages.
	 * 
	 * @param pathname
	 *            the full path for the output PDF file.
	 * @exception PrinterException
	 *                an error in the print system caused the job to be aborted.
	 * @see Book
	 * @see Pageable
	 * @see Printable
	 */
	public void print(String pathname) throws PrinterException {
		int pageCount;
		File file = null;
		FileOutputStream fileOutputStream = null;

		try {
			file = new File(pathname);
			fileOutputStream = new FileOutputStream(file);
		} catch (Exception e) {
			System.err.println("Error!! - Invalid output file path: "
					+ pathname);
		}

		PDFGraphics pdfGraphics = null;
		printJob = new PDFJob(fileOutputStream);

		if (info != null) {
			printJob.getPDFDocument().setPDFInfo(info);
		}

		pageCount = pageable.getNumberOfPages();
		for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
			pageFormat = pageable.getPageFormat(pageIndex);

			pdfGraphics = (PDFGraphics) printJob.getGraphics(pageFormat);
			printable = pageable.getPrintable(pageIndex);
			printable.print(pdfGraphics, pageFormat, pageIndex);
			pdfGraphics.dispose();
		}

		printJob.end();

	}

	@Override
	public void print() throws PrinterException {
		File file;
		File path;
		String jobName = getJobName();

		if (fileChooser == null) {
			fileChooser = new JFileChooser();
			fileChooser.setMultiSelectionEnabled(false);
			fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
		}

		// Make sure job name is not blank
		if (jobName.equals("")) {
			jobName = "Java Printing";
		}
		// Eliminate invalid characters from job name.
		jobName = jobName.replaceAll("\\\\", "-");
		jobName = jobName.replaceAll("/", "-");

		path = fileChooser.getCurrentDirectory();
		file = new File(path, jobName + ".pdf");

		// Dialog to get file name...
		fileChooser.setSelectedFile(file);

		// Print
		if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
			file = fileChooser.getSelectedFile();
			print(file.getAbsolutePath());
		}
	}

	@Override
	public boolean printDialog() throws HeadlessException {
		return true;
	}

	/**
	 * Sets the author for this document.
	 * 
	 * @param author the author's name.
	 */
	public void setAuthor(String author) {
		info.setAuthor(author);
	}
	
	@Override
	public void setCopies(int copies) {
		// Will be ignored, but add attribute anyway
		attributes.add(new Copies(copies));
	}

	/**
	 * Sets the creator for this document.
	 * 
	 * @param creator the application name.
	 */
	public void setCreator(String creator) {
		info.setCreator(creator);
	}

	@Override
	public void setJobName(String jobName) {
		attributes.add(new JobName(jobName, Locale.getDefault()));

		if (info.getTitle() == null) {
			info.setTitle(jobName);
		}
	}

	@Override
	public void setPageable(Pageable document) throws NullPointerException {
		if (document == null) {
			throw new NullPointerException("Pageable cannot be null.");
		}
		this.pageable = document;
	}

	@Override
	public void setPrintable(Printable painter) {
		this.printable = painter;
	}

	@Override
	public void setPrintable(Printable painter, PageFormat format) {
		this.printable = painter;
		this.pageFormat = format;
	}

	/**
	 * Sets the title for this document.
	 * 
	 * @param title the document title.
	 */
	public void setTitle(String title) {
		info.setTitle(title);
	}

	@Override
	public PageFormat validatePage(PageFormat page) {
		return (PageFormat) page.clone();
	}

}
