/** **********************************************************************
 *
 * Copyright 2018 Jochen Staerk
 *
 * Use is subject to license terms.
 *
 * 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 org.mustangproject.ZUGFeRD;

import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.preflight.PreflightDocument;
import org.apache.pdfbox.preflight.exception.ValidationException;
import org.apache.pdfbox.preflight.parser.PreflightParser;
import org.apache.pdfbox.preflight.utils.ByteArrayDataSource;

import javax.activation.DataSource;
import javax.activation.FileDataSource;
import java.io.*;
import java.util.HashMap;

public class ZUGFeRDExporterFromA3Factory implements IExporterFactory {
	protected boolean ignorePDFAErrors = false;
	protected ZUGFeRDConformanceLevel zugferdConformanceLevel = ZUGFeRDConformanceLevel.EXTENDED;
	protected PDFAConformanceLevel conformanceLevel = PDFAConformanceLevel.UNICODE;
	/**
	 * Producer (attribute for PDF
	 */
	protected String producer = "mustangproject";
	/**
	 * Human creator (attribute for PDF)
	 */
	protected String creator = "mustangproject";
	/**
	 * Creator tool (attribute for PDF)
	 */
	protected String creatorTool = null;

	private HashMap<String, byte[]> additionalXMLs = new HashMap<String, byte[]>();


	protected int ZFVersion = ZUGFeRDExporter.DefaultZUGFeRDVersion;
	protected boolean ensurePDFisUpgraded = false;
	private boolean attachZUGFeRDHeaders = true;

	/**
	 * Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on the
	 * metadata level, this will not e.g. convert graphics to JPG-2000)
	 *
	 * @param pdfFilename filename of an PDF/A1 compliant document
	 */
	public ZUGFeRDExporter load(String pdfFilename) throws IOException {

		ensurePDFIsValidPDFA(new FileDataSource(pdfFilename));
		ZUGFeRDExporter zugFeRDExporter;
		PDDocument doc = PDDocument.load(new File(pdfFilename));
		zugFeRDExporter = new ZUGFeRDExporter(doc);
		zugFeRDExporter.setZUGFeRDVersion(ZFVersion);
		zugFeRDExporter.setZUGFeRDConformanceLevel(zugferdConformanceLevel);
		zugFeRDExporter.setCreator(creator);
		// Use creator as default for compatibility
		zugFeRDExporter.setCreatorTool(creatorTool != null ? creatorTool : creator);
		zugFeRDExporter.setProducer(producer);
		zugFeRDExporter.setAttachZUGFeRDHeaders(attachZUGFeRDHeaders);
		zugFeRDExporter.setPDFA3(ensurePDFisUpgraded);

		return zugFeRDExporter;

	}


	/**
	 * Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on the
	 * metadata level, this will not e.g. convert graphics to JPG-2000)
	 *
	 * @param pdfBinary binary of a PDF/A1 compliant document
	 */
	public ZUGFeRDExporter load(byte[] pdfBinary) throws IOException {
		ensurePDFIsValidPDFA(new ByteArrayDataSource(new ByteArrayInputStream(pdfBinary)));
		ZUGFeRDExporter zugFeRDExporter;
		PDDocument doc = PDDocument.load(pdfBinary);
		zugFeRDExporter = new ZUGFeRDExporter(doc);
		zugFeRDExporter.setZUGFeRDVersion(ZFVersion);
		zugFeRDExporter.setZUGFeRDConformanceLevel(zugferdConformanceLevel);
		zugFeRDExporter.setCreator(creator);
		zugFeRDExporter.setProducer(producer);
		zugFeRDExporter.setAttachZUGFeRDHeaders(attachZUGFeRDHeaders);
		zugFeRDExporter.setPDFA3(ensurePDFisUpgraded);

		return zugFeRDExporter;
	}

	public ZUGFeRDExporterFromA3Factory() {

		ensurePDFisUpgraded = false;
	}

	/**
	 * Makes A PDF/A3a-compliant document from a PDF-A1 compliant document (on the
	 * metadata level, this will not e.g. convert graphics to JPG-2000)
	 *
	 * @param pdfSource source to read a PDF/A1 compliant document from
	 */
	public ZUGFeRDExporter load(InputStream pdfSource) throws IOException {
		return load(readAllBytes(pdfSource));
	}

	private void ensurePDFIsValidPDFA(final DataSource dataSource) throws IOException {
		if (!ignorePDFAErrors && !isValidA1(dataSource)) {
			throw new IOException("File is not a valid PDF/A input file");
		}
	}

	private static byte[] readAllBytes(InputStream in) throws IOException {
		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		IOUtils.copy(in, buffer);
		return buffer.toByteArray();
	}

	private static boolean isValidA1(DataSource dataSource) throws IOException {
		return getPDFAParserValidationResult(new PreflightParser(dataSource));
	}

	/**
	 * Sets the ZUGFeRD conformance level (override).
	 *
	 * @param zugferdConformanceLevel the new conformance level
	 */
	public IExporterFactory setZUGFeRDConformanceLevel(ZUGFeRDConformanceLevel zugferdConformanceLevel) {
		this.zugferdConformanceLevel = zugferdConformanceLevel;
		return this;
	}

	private static boolean getPDFAParserValidationResult(PreflightParser parser) throws IOException {
		/*
		 * Parse the PDF file with PreflightParser that inherits from the
		 * NonSequentialParser. Some additional controls are present to check a set of
		 * PDF/A requirements. (Stream length consistency, EOL after some Keyword...)
		 */
		parser.parse();// might add a Format.PDF_A1A as parameter and iterate through A1 and A3

		try (PreflightDocument document = parser.getPreflightDocument()) {
			/*
			 * Once the syntax validation is done, the parser can provide a
			 * PreflightDocument (that inherits from PDDocument) This document process the
			 * end of PDF/A validation.
			 */

			document.validate();

			// Get validation result
			return document.getResult().isValid();
		} catch (ValidationException e) {
			/*
			 * the parse method can throw a SyntaxValidationException if the PDF file can't
			 * be parsed. In this case, the exception contains an instance of
			 * ValidationResult
			 */
			return false;
		}
	}

	/**
	 * All files are PDF/A-3, setConformance refers to the level conformance.
	 * <p/>
	 * PDF/A-3 has three coformance levels, called "A", "U" and "B".
	 * <p/>
	 * PDF/A-3-B where B means only visually preservable, U -standard for Mustang-
	 * means visually and unicode preservable and A means full compliance, i.e.
	 * visually, unicode and structurally preservable and tagged PDF, i.e. useful
	 * metainformation for blind people.
	 * <p/>
	 * Feel free to pass "A" as new level if you know what you are doing :-)
	 */
	public IExporterFactory setConformanceLevel(PDFAConformanceLevel newLevel) {
		conformanceLevel = newLevel;
		return this;
	}

	public IExporterFactory ignorePDFAErrors() {
		this.ignorePDFAErrors = true;
		return this;
	}


	public IExporterFactory setCreator(String creator) {
		this.creator = creator;
		return this;
	}

	public IExporterFactory setCreatorTool(String creatorTool) {
		this.creatorTool = creatorTool;
		return this;
	}

	public IExporterFactory setProducer(String producer) {
		this.producer = producer;
		return this;
	}

	public IExporterFactory setAttachZUGFeRDHeaders(boolean attachHeaders) {
		this.attachZUGFeRDHeaders = attachHeaders;
		return this;
	}


	@Override
	public IExporterFactory setZUGFeRDVersion(int version) {
		this.ZFVersion = version;
		return this;
	}
}
