/*
 * 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.
 */
package org.apache.pdfbox.pdmodel.interactive.digitalsignature.visible;

import java.awt.geom.AffineTransform;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.pdfbox.cos.COSDocument;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdfwriter.COSWriter;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;

/**
 * Class to build PDF template.
 *
 * @author Vakhtang Koroghlishvili
 */
public class PDFTemplateCreator
{
    private final PDFTemplateBuilder pdfBuilder;
    private static final Logger LOG = LogManager.getLogger(PDFTemplateCreator.class);

    /**
     * Constructor.
     * 
     * @param templateBuilder the template builder
     */
    public PDFTemplateCreator(PDFTemplateBuilder templateBuilder)
    {
        pdfBuilder = templateBuilder;
    }

    /**
     * Returns the PDFTemplateStructure object.
     * 
     * @return the template for the structure
     */
    public PDFTemplateStructure getPdfStructure()
    {
        return pdfBuilder.getStructure();
    }

    /**
     * Build a PDF with a visible signature step by step, and return it as a stream.
     *
     * @param properties properties to be used for the creation
     * @return InputStream stream containing the pdf holding the visible signature
     * @throws IOException if the PDF could not be created
     */
    public InputStream buildPDF(PDVisibleSignDesigner properties) throws IOException
    {
        LOG.info("pdf building has been started");
        PDFTemplateStructure pdfStructure = pdfBuilder.getStructure();

        // we create array of [Text, ImageB, ImageC, ImageI]
        pdfBuilder.createProcSetArray();
        
        //create page
        pdfBuilder.createPage(properties);
        PDPage page = pdfStructure.getPage();

        //create template
        pdfBuilder.createTemplate(page);

        try (PDDocument template = pdfStructure.getTemplate())
        {
            //create /AcroForm
            pdfBuilder.createAcroForm(template);
            PDAcroForm acroForm = pdfStructure.getAcroForm();
            // AcroForm contains signature fields
            pdfBuilder.createSignatureField(acroForm);
            PDSignatureField pdSignatureField = pdfStructure.getSignatureField();
            // create signature
            //TODO
            // The line below has no effect with the CreateVisibleSignature example.
            // The signature field is needed as a "holder" for the /AP tree,
            // but the /P and /V PDSignatureField entries are ignored by PDDocument.addSignature
            pdfBuilder.createSignature(pdSignatureField, page, "");
            // that is /AcroForm/DR entry
            pdfBuilder.createAcroFormDictionary(acroForm, pdSignatureField);
            // create AffineTransform
            pdfBuilder.createAffineTransform(properties.getTransform());
            AffineTransform transform = pdfStructure.getAffineTransform();
            // rectangle, formatter, image. /AcroForm/DR/XObject contains that form
            pdfBuilder.createSignatureRectangle(pdSignatureField, properties);
            pdfBuilder.createFormatterRectangle(properties.getFormatterRectangleParameters());
            PDRectangle bbox = pdfStructure.getFormatterRectangle();
            pdfBuilder.createSignatureImage(template, properties.getImage());
            // create form stream, form and  resource.
            pdfBuilder.createHolderFormStream(template);
            PDStream holderFormStream = pdfStructure.getHolderFormStream();
            pdfBuilder.createHolderFormResources();
            PDResources holderFormResources = pdfStructure.getHolderFormResources();
            pdfBuilder.createHolderForm(holderFormResources, holderFormStream, bbox);
            // that is /AP entry the appearance dictionary.
            pdfBuilder.createAppearanceDictionary(pdfStructure.getHolderForm(), pdSignatureField);
            // inner form stream, form and resource (holder form contains inner form)
            pdfBuilder.createInnerFormStream(template);
            pdfBuilder.createInnerFormResource();
            PDResources innerFormResource = pdfStructure.getInnerFormResources();
            pdfBuilder.createInnerForm(innerFormResource, pdfStructure.getInnerFormStream(), bbox);
            PDFormXObject innerForm = pdfStructure.getInnerForm();
            // inner form must be in the holder form as we wrote
            pdfBuilder.insertInnerFormToHolderResources(innerForm, holderFormResources);
            //  Image form is in this structure: /AcroForm/DR/FRM/Resources/XObject/n2
            pdfBuilder.createImageFormStream(template);
            PDStream imageFormStream = pdfStructure.getImageFormStream();
            pdfBuilder.createImageFormResources();
            PDResources imageFormResources = pdfStructure.getImageFormResources();
            pdfBuilder.createImageForm(imageFormResources, innerFormResource, imageFormStream, bbox,
                    transform, pdfStructure.getImage());
            pdfBuilder.createBackgroundLayerForm(innerFormResource, bbox);
            // now inject procSetArray
            pdfBuilder.injectProcSetArray(innerForm, page, innerFormResource, imageFormResources,
                    holderFormResources, pdfStructure.getProcSet());
            COSName imageFormName = pdfStructure.getImageFormName();
            COSName imageName = pdfStructure.getImageName();
            COSName innerFormName = pdfStructure.getInnerFormName();
            // now create Streams of AP
            pdfBuilder.injectAppearanceStreams(holderFormStream, imageFormStream, imageFormStream,
                    imageFormName, imageName, innerFormName, properties);
            pdfBuilder.createVisualSignature(template);
            pdfBuilder.createWidgetDictionary(pdSignatureField, holderFormResources);
            InputStream in = getVisualSignatureAsStream(pdfStructure.getVisualSignature());
            LOG.info("stream returning started, size= {}", in.available());
            // return result of the stream 
            return in;
        }
    }

    private InputStream getVisualSignatureAsStream(COSDocument visualSignature) throws IOException
    {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream())
        {
            COSWriter writer = new COSWriter(baos);
            writer.write(visualSignature);
            return new ByteArrayInputStream(baos.toByteArray());
        }
    }   
}
