/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

/* $Id: PDFProfile.java 1302518 2012-03-19 15:56:19Z vhennebert $ */

package org.apache.fop.pdf;

import java.text.MessageFormat;

/**
 * This class allows tracks the enabled PDF profiles (PDF/A and PDF/X) and provides methods to
 * the libarary and its users to enable the generation of PDFs conforming to the enabled PDF
 * profiles.
 * <p>
 * Some profile from PDF/X and PDF/A can be active simultaneously (example: PDF/A-1 and
 * PDF/X-3:2003).
 */
public class PDFProfile {

    /**
     * Indicates the PDF/A mode currently active. Defaults to "no restrictions", i.e.
     * PDF/A not active.
     */
    protected PDFAMode pdfAMode = PDFAMode.DISABLED;

    /**
     * Indicates the PDF/X mode currently active. Defaults to "no restrictions", i.e.
     * PDF/X not active.
     */
    protected PDFXMode pdfXMode = PDFXMode.DISABLED;

    private PDFDocument doc;

    /**
     * Main constructor
     * @param doc the PDF document
     */
    public PDFProfile(PDFDocument doc) {
        this.doc = doc;
    }

    /**
     * Validates if the requested profile combination is compatible.
     */
    protected void validateProfileCombination() {
        if (pdfAMode != PDFAMode.DISABLED) {
            if (pdfAMode == PDFAMode.PDFA_1B) {
                if (pdfXMode != PDFXMode.DISABLED && pdfXMode != PDFXMode.PDFX_3_2003) {
                    throw new PDFConformanceException(
                            pdfAMode + " and " + pdfXMode + " are not compatible!");
                }
            }
        }
    }

    /** @return the PDFDocument this profile is attached to */
    public PDFDocument getDocument() {
        return this.doc;
    }

    /** @return the PDF/A mode */
    public PDFAMode getPDFAMode() {
        return this.pdfAMode;
    }

    /** @return true if any PDF/A mode is active */
    public boolean isPDFAActive() {
        return getPDFAMode() != PDFAMode.DISABLED;
    }

    /**
     * Sets the PDF/A mode
     * @param mode the PDF/A mode
     */
    public void setPDFAMode(PDFAMode mode) {
        if (mode == null) {
            mode = PDFAMode.DISABLED;
        }
        this.pdfAMode = mode;
        validateProfileCombination();
    }

    /** @return the PDF/X mode */
    public PDFXMode getPDFXMode() {
        return this.pdfXMode;
    }

    /** @return true if any PDF/X mode is active */
    public boolean isPDFXActive() {
        return getPDFXMode() != PDFXMode.DISABLED;
    }

    /**
     * Sets the PDF/X mode
     * @param mode the PDF/X mode
     */
    public void setPDFXMode(PDFXMode mode) {
        if (mode == null) {
            mode = PDFXMode.DISABLED;
        }
        this.pdfXMode = mode;
        validateProfileCombination();
    }

    /** {@inheritDoc} */
    public String toString() {
        StringBuffer sb = new StringBuffer();
        if (isPDFAActive() && isPDFXActive()) {
            sb.append("[").append(getPDFAMode()).append(",").append(getPDFXMode()).append("]");
        } else if (isPDFAActive()) {
            sb.append(getPDFAMode());
        } else if (isPDFXActive()) {
            sb.append(getPDFXMode());
        } else {
            sb.append(super.toString());
        }
        return sb.toString();
    }

    //---------=== Info and validation methods ===---------

    private String format(String pattern, Object[] args) {
        return MessageFormat.format(pattern, args);
    }

    private String format(String pattern, Object arg) {
        return format(pattern, new Object[] {arg});
    }

    /** Checks if encryption is allowed. */
    public void verifyEncryptionAllowed() {
        final String err = "{0} doesn't allow encrypted PDFs";
        if (isPDFAActive()) {
            throw new PDFConformanceException(format(err, getPDFAMode()));
        }
        if (isPDFXActive()) {
            throw new PDFConformanceException(format(err, getPDFXMode()));
        }
    }

    /** Checks if PostScript XObjects are allowed. */
    public void verifyPSXObjectsAllowed() {
        final String err = "PostScript XObjects are prohibited when {0}"
                + " is active. Convert EPS graphics to another format.";
        if (isPDFAActive()) {
            throw new PDFConformanceException(format(err, getPDFAMode()));
        }
        if (isPDFXActive()) {
            throw new PDFConformanceException(format(err, getPDFXMode()));
        }
    }

    /**
     * Checks if the use of transparency is allowed.
     * @param context Context information for the user to identify the problem spot
     */
    public void verifyTransparencyAllowed(String context) {
        final String err = "{0} does not allow the use of transparency. ({1})";
        if (isPDFAActive()) {
            throw new PDFConformanceException(MessageFormat.format(err,
                    new Object[] {getPDFAMode(), context}));
        }
        if (isPDFXActive()) {
            throw new PDFConformanceException(MessageFormat.format(err,
                    new Object[] {getPDFXMode(), context}));
        }
    }

    /** Checks if the right PDF version is set. */
    public void verifyPDFVersion() {
        final String err = "PDF version must be 1.4 for {0}";
        if (getPDFAMode().isPDFA1LevelB()
                && !Version.V1_4.equals(getDocument().getPDFVersion())) {
            throw new PDFConformanceException(format(err, getPDFAMode()));
        }
        if (getPDFXMode() == PDFXMode.PDFX_3_2003
                && !Version.V1_4.equals(getDocument().getPDFVersion())) {
            throw new PDFConformanceException(format(err, getPDFXMode()));
        }
    }

    /**
     * Checks a few things required for tagged PDF.
     */
    public void verifyTaggedPDF() {
        if (getPDFAMode().isPDFA1LevelA()) {
            final String err = "{0} requires the {1} dictionary entry to be set";
            PDFDictionary markInfo = getDocument().getRoot().getMarkInfo();
            if (markInfo == null) {
                throw new PDFConformanceException(format(
                        "{0} requires the MarkInfo dictionary to be present", getPDFAMode()));
            }
            if (!Boolean.TRUE.equals(markInfo.get("Marked"))) {
                throw new PDFConformanceException(format(err,
                        new Object[] {getPDFAMode(), "Marked"}));
            }
            if (getDocument().getRoot().getStructTreeRoot() == null) {
                throw new PDFConformanceException(format(err,
                        new Object[] {getPDFAMode(), "StructTreeRoot"}));
            }
            if (getDocument().getRoot().getLanguage() == null) {
                throw new PDFConformanceException(format(err,
                        new Object[] {getPDFAMode(), "Lang"}));
            }
        }
    }

    /** @return true if the ID entry must be present in the trailer. */
    public boolean isIDEntryRequired() {
        return isPDFAActive() || isPDFXActive();
    }

    /** @return true if all fonts need to be embedded. */
    public boolean isFontEmbeddingRequired() {
        return isPDFAActive() || isPDFXActive();
    }

    /** Checks if a title may be absent. */
    public void verifyTitleAbsent() {
        if (isPDFXActive()) {
            final String err = "{0} requires the title to be set.";
            throw new PDFConformanceException(format(err, getPDFXMode()));
        }
    }

    /** @return true if the ModDate Info entry must be present. */
    public boolean isModDateRequired() {
        return getPDFXMode() == PDFXMode.PDFX_3_2003;
    }

    /** @return true if the Trapped Info entry must be present. */
    public boolean isTrappedEntryRequired() {
        return getPDFXMode() == PDFXMode.PDFX_3_2003;
    }

    /** @return true if annotations are allowed */
    public boolean isAnnotationAllowed() {
        return !isPDFXActive();
    }

    /** Checks if annotations are allowed. */
    public void verifyAnnotAllowed() {
        if (!isAnnotationAllowed()) {
            final String err = "{0} does not allow annotations inside the printable area.";
            //Note: this rule is simplified. Refer to the standard for details.
            throw new PDFConformanceException(format(err, getPDFXMode()));
        }
    }

    /** Checks if Actions are allowed. */
    public void verifyActionAllowed() {
        if (isPDFXActive()) {
            final String err = "{0} does not allow Actions.";
            throw new PDFConformanceException(format(err, getPDFXMode()));
        }
    }

    /** Checks if embedded files are allowed. */
    public void verifyEmbeddedFilesAllowed() {
        final String err = "{0} does not allow embedded files.";
        if (isPDFAActive()) {
            throw new PDFConformanceException(format(err, getPDFAMode()));
        }
        if (isPDFXActive()) {
            //Implicit since file specs are forbidden
            throw new PDFConformanceException(format(err, getPDFXMode()));
        }
    }

}
