/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.sys;

import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.AcroFields;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfGState;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;
import org.kuali.kfs.krad.util.ObjectUtils;

import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Map;

/**
 * This class writes the reports onto a pdf file as per a template provided.
 */
public class PdfFormFillerUtil {

    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PdfFormFillerUtil.class);
    private static final SimpleDateFormat FILE_NAME_TIMESTAMP = new SimpleDateFormat("_yyyy-MM-dd_hhmmss");

    /**
     * This method generates the reports from the template stream provided.
     *
     * @param template
     * @param replacementList
     * @throws IOException, DocumentException
     */
    public static byte[] populateTemplate(InputStream templateStream, Map<String, String> replacementList) throws IOException, DocumentException {
        // --------------------------------------------------
        // Validate the parameters
        // --------------------------------------------------
        if (templateStream == null || replacementList == null) {
            throw new IllegalArgumentException("All parameters are required, but one or more were null.");
        }

        // --------------------------------------------------
        // Use iText to build the new PDF
        // --------------------------------------------------
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        pdfStampValues(templateStream, outputStream, replacementList);
        return outputStream.toByteArray();
    }

    /**
     * This method generates the reports from the template file provided.
     *
     * @param template
     * @param replacementList
     * @throws IOException, DocumentException
     */
    public static byte[] populateTemplate(File template, Map<String, String> replacementList) throws IOException, DocumentException {
        // --------------------------------------------------
        // Validate the parameters
        // --------------------------------------------------
        if (template == null || replacementList == null) {
            throw new IllegalArgumentException("All parameters are required, but one or more were null.");
        }

        // Validate the template file
        if (template.exists() == false) {
            throw new IOException("The template file '" + template.getAbsolutePath() + "' does not exist.");
        }
        if (!template.isFile()) {
            throw new RuntimeException("The template file '" + template.getAbsolutePath() + "' is not a valid file.");
        }
        if (!template.canRead()) {
            throw new RuntimeException("The template file '" + template.getAbsolutePath() + "' cannot be read.");
        }

        // --------------------------------------------------
        // Use iText to build the new PDF
        // --------------------------------------------------
        InputStream templateStream = new FileInputStream(template);
        return populateTemplate(templateStream, replacementList);
    }


    /**
     * This method stamps the values onto the pdf file from the replacement list
     *
     * @param strLine
     * @param replacementList
     * @return
     */
    private static boolean validListTagFound(String strLine) {
        boolean valid = true;
        valid &= strLine.matches("^#[\\S]*$");
        return valid;
    }

    /**
     * This Method stamps the values from the map onto the fields in the template provided.
     *
     * @param templateStream
     * @param outputStream
     * @param replacementList
     * @throws IOException
     */
    private static void pdfStampValues(InputStream templateStream, OutputStream outputStream, Map<String, String> replacementList) throws IOException {
        try {
            // Create a PDF reader for the template
            PdfReader pdfReader = new PdfReader(templateStream);

            // Create a PDF writer
            PdfStamper pdfStamper = new PdfStamper(pdfReader, outputStream);
            // Replace the form data with the final values
            AcroFields fields = pdfStamper.getAcroFields();
            for (Object fieldName : fields.getFields().keySet()) {
                // Read the field data
                String text = fields.getField(fieldName.toString());
                String newText = fields.getField(fieldName.toString());
                // Replace the keywords
                if (fields.getFieldType(fieldName.toString()) == AcroFields.FIELD_TYPE_TEXT) {
                    newText = replaceValuesIteratingThroughFile(text, replacementList);
                } else {
                    if (ObjectUtils.isNotNull(replacementList.get(fieldName.toString()))) {
                        newText = replacementList.get(fieldName);
                    }
                }
                // Populate the field with the final value
                fields.setField(fieldName.toString(), newText);
            }

            // --------------------------------------------------
            // Save the new PDF
            // --------------------------------------------------
            pdfStamper.close();

        } catch (IOException e) {
            throw new IOException("IO error processing PDF template", e);
        } catch (DocumentException e) {
            throw new IOException("iText error processing PDF template", e);
        } finally {
            // --------------------------------------------------
            // Close the files
            // --------------------------------------------------
            templateStream.close();
            outputStream.close();
        }
    }

    /**
     * This method creates a Final watermark on the input Stream.
     *
     * @param templateStream
     * @param finalmarkText
     * @return
     * @throws IOException
     * @throws DocumentException
     */
    public static byte[] createFinalmarkOnFile(byte[] templateStream, String finalmarkText) throws IOException, DocumentException {
        // Create a PDF reader for the template
        PdfReader pdfReader = new PdfReader(templateStream);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        // Create a PDF writer
        PdfStamper pdfStamper = new PdfStamper(pdfReader, outputStream);
        int n = pdfReader.getNumberOfPages();
        int i = 1;
        PdfContentByte over;
        BaseFont bf;
        try {
            bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.EMBEDDED);
            PdfGState gstate = new PdfGState();
            while (i <= n) {
                // Watermark under the existing page
                Rectangle pageSize = pdfReader.getPageSizeWithRotation(i);
                over = pdfStamper.getOverContent(i);
                over.beginText();
                over.setFontAndSize(bf, 8);
                over.setGState(gstate);
                over.setColorFill(Color.BLACK);
                over.showTextAligned(Element.ALIGN_CENTER, finalmarkText, (pageSize.width() / 2), (pageSize.height() - 10), 0);
                over.endText();
                i++;
            }
            pdfStamper.close();
        } catch (DocumentException ex) {
            throw new IOException("iText error creating final watermark on PDF", ex);
        } catch (IOException ex) {
            throw new IOException("IO error creating final watermark on PDF", ex);
        }
        return outputStream.toByteArray();
    }

    /**
     * This Method creates a custom watermark on the File.
     *
     * @param templateStream
     * @param watermarkText
     * @return
     * @throws IOException
     * @throws DocumentException
     */
    public static byte[] createWatermarkOnFile(byte[] templateStream, String watermarkText) throws IOException, DocumentException {
        // Create a PDF reader for the template
        PdfReader pdfReader = new PdfReader(templateStream);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        // Create a PDF writer
        PdfStamper pdfStamper = new PdfStamper(pdfReader, outputStream);
        int n = pdfReader.getNumberOfPages();
        int i = 1;
        PdfContentByte over;
        BaseFont bf;
        try {
            bf = BaseFont.createFont(BaseFont.HELVETICA_BOLD, BaseFont.WINANSI, BaseFont.EMBEDDED);
            PdfGState gstate = new PdfGState();
            gstate.setFillOpacity(0.5f);
            while (i <= n) {
                // Watermark under the existing page
                Rectangle pageSize = pdfReader.getPageSizeWithRotation(i);
                over = pdfStamper.getOverContent(i);
                over.beginText();
                over.setFontAndSize(bf, 200);
                over.setGState(gstate);
                over.setColorFill(Color.LIGHT_GRAY);
                over.showTextAligned(Element.ALIGN_CENTER, watermarkText, (pageSize.width() / 2), (pageSize.height() / 2), 45);
                over.endText();
                i++;
            }
            pdfStamper.close();
        } catch (DocumentException ex) {
            throw new IOException("iText error creating watermark on PDF", ex);
        } catch (IOException ex) {
            throw new IOException("IO error creating watermark on PDF", ex);
        }
        return outputStream.toByteArray();
    }

    /**
     * This method splits all the text in the template file and replaces them if they match the keys in the Map.
     *
     * @param template
     * @param replacementList
     * @return
     */
    private static String replaceValuesIteratingThroughFile(String template, Map<String, String> replacementList) {
        StringBuilder buffOriginal = new StringBuilder();
        StringBuilder buffNormalized = new StringBuilder();

        String[] keys = template.split("[\\s]+");

        // Scan for each word
        for (String key : keys) {
            if (validListTagFound(key)) {
                String replacementKey = key.substring(1);
                String value = replacementList.get(replacementKey);
                if (ObjectUtils.isNotNull(value)) {
                    buffOriginal.append(value + " ");
                } else {
                    buffOriginal.append(" ");
                }
            } else {
                buffOriginal.append(key + " ");
            }
        }
        return buffOriginal.toString();
    }
}
