package org.phoenixctms.ctsms.pdf;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

import org.apache.pdfbox.pdfwriter.COSWriter;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.phoenixctms.ctsms.pdf.PDFContentPainter.PageOrientation;

public class PDFImprinter {

	private PDFContentPainter painter;
	private PDFOutput output;
	private ByteArrayOutputStream pdfStream;
	private PDDocument doc;
	private PDPage page;
	private PDPageContentStream contentStream;
	private PDDocument templateDoc;
	private List templatePages;
	private COSWriter docWriter;

	public PDFImprinter() {
	}

	public PDFImprinter(PDFContentPainter painter, PDFOutput output) {
		this.painter = painter;
		this.output = output;
	}

	public void closeContentStream() throws IOException {
		contentStream.close();
	}

	private void createPage(int pageNum) throws Exception {
		String templateFileName;
		if (painter != null) {
			templateFileName = painter.getTemplateFileName();
		} else {
			templateFileName = null;
		}
		if (templateFileName != null && templateFileName.length() > 0) {
			if (templateDoc == null) {
				templateDoc = PDDocument.load(templateFileName);
				templatePages = templateDoc.getDocumentCatalog().getAllPages();
			}
			if (pageNum > templatePages.size()) {
				page = doc.importPage((PDPage) templatePages.get(templatePages.size() - 1));
			} else if (pageNum > 0) {
				page = doc.importPage((PDPage) templatePages.get(pageNum - 1));
			} else {
				page = new PDPage(PDPage.PAGE_SIZE_A4);
				doc.addPage(page);
			}
		} else {
			if (painter != null) {
				page = new PDPage(painter.getDefaultPageSize());
				if (PageOrientation.LANDSCAPE.equals(painter.getPageOrientation())) {
					page.setRotation(90);
				}
			} else {
				page = new PDPage(PDPage.PAGE_SIZE_A4);
			}
			doc.addPage(page);
		}
	}

	public PDDocument getDocument() {
		return doc;
	}

	public PDFOutput getOutput() {
		return output;
	}

	public PDFContentPainter getPainter() {
		return painter;
	}

	private void openContentStream() throws IOException {
		openContentStream(page, true, true);
	}

	public PDPageContentStream openContentStream(PDPage page) throws IOException {
		return openContentStream(page, true, false);
	}

	private PDPageContentStream openContentStream(PDPage page, boolean setPageSize, boolean applyPageOrientation) throws IOException {
		contentStream = new PDPageContentStream(doc, page, true, false);
		if (painter != null) {
			PDRectangle pageSize = page.findMediaBox();
			if (PageOrientation.LANDSCAPE.equals(painter.getPageOrientation())) {
				if (setPageSize) {
					painter.setPageHeight(pageSize.getWidth());
					painter.setPageWidth(pageSize.getHeight());
				}
				if (applyPageOrientation) {
					contentStream.concatenate2CTM(0, 1, -1, 0, pageSize.getWidth(), 0); // cos(theta) sin(theta) -sin(theta) cos(theta) 0 0 cm
				}
			} else {
				if (setPageSize) {
					painter.setPageHeight(pageSize.getHeight());
					painter.setPageWidth(pageSize.getWidth());
				}
			}
		}
		return contentStream;
	}

	public boolean render() throws Exception {
		templateDoc = null;
		templatePages = null;
		doc = null;
		page = null;
		contentStream = null;
		try {
			doc = new PDDocument();
			if (output != null) {
				output.setMetadata(doc);
			}
			if (painter != null) {
				painter.reset();
				painter.loadFonts(doc);
				// https://issues.apache.org/jira/browse/PDFBOX-1394
				painter.loadImages(doc);
				painter.populateBlocks();
				if (painter.hasNextBlock()) {
					painter.updateCursor();
				}
				painter.startNewPage();
				createPage(painter.getTemplatePageNum());
				openContentStream();
				while (painter.hasNextBlock()) {
					if (!painter.nextBlockFitsOnFullPage()) {
						painter.splitNextBlock();
					}
					if (!painter.nextBlockFitsOnPage()) {
						painter.drawPageBreakOldPage(contentStream);
						painter.drawPage(contentStream);
						closeContentStream();
						painter.updateCursor();
						painter.startNewPage();
						createPage(painter.getTemplatePageNum());
						openContentStream();
						painter.drawPageBreakNewPage(contentStream);
					}
					painter.drawNextBlock(contentStream);
				}
				painter.drawPage(contentStream);
				closeContentStream();
			} else {
				createPage(1);
			}
			if (painter != null) {
				painter.drawPageNumbers(this);
			}
			pdfStream = new ByteArrayOutputStream();
			docWriter = new COSWriter(pdfStream);
			docWriter.write(doc);
			if (output != null) {
				return output.save(pdfStream);
			} else {
				return false;
			}
		} finally {
			templatePages = null;
			if (templateDoc != null) {
				templateDoc.close();
				templateDoc = null;
			}
			if (doc != null) {
				doc.close();
				doc = null;
			}
			if (pdfStream != null) {
				pdfStream.close();
				pdfStream = null;
			}
			if (docWriter != null) {
				docWriter.close();
				docWriter = null;
			}
		}
	}

	public void setOutput(PDFOutput output) {
		this.output = output;
	}

	public void setPainter(PDFContentPainter painter) {
		this.painter = painter;
	}
}