package com.technia.tif.enovia.job.executors.file.java;

import java.awt.Font;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;

import javax.imageio.ImageIO;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.technia.tif.core.annotation.CopyToDocumentation;
import com.technia.tif.enovia.job.executors.file.StatusLog;
import com.technia.tif.enovia.job.executors.file.external.ExtPDFWatermark;
import com.technia.tif.enovia.util.modifier.PDFWatermark;
import com.technia.tif.enovia.util.modifier.PDFWatermark.PositionType;
import com.technia.tvc.commons.io.FileUtils;
import com.technia.tvc.core.gui.image.TextToImage;
import com.technia.tvc.core.util.StringUtils;
import com.technia.tvc.lowagie.text.BadElementException;
import com.technia.tvc.lowagie.text.DocumentException;
import com.technia.tvc.lowagie.text.Image;
import com.technia.tvc.lowagie.text.Rectangle;
import com.technia.tvc.lowagie.text.pdf.PdfContentByte;
import com.technia.tvc.lowagie.text.pdf.PdfGState;
import com.technia.tvc.lowagie.text.pdf.PdfReader;
import com.technia.tvc.lowagie.text.pdf.PdfStamper;

/**
 * Stamps watermarks on a PDF file using Lowagie library.
 *
 * @since 2018.3.0
 */
public class LowagiePDFWatermark extends ExtPDFWatermark {

    private static final Logger logger = LoggerFactory.getLogger(LowagiePDFWatermark.class);

    /**
     * Main entry point of the class.
     *
     * @param input Input file
     * @param pdfWatermarks List of PDFWatermarks
     */
    @Override
    protected void perform(File input, List<PDFWatermark> pdfWatermarks, StatusLog log) throws IOException {
        log.log("About to stamp file %s", input.getAbsolutePath());

        PdfReader reader = null;
        PdfStamper stamper = null;
        File output = File.createTempFile("tmp-", ".pdf", input.getParentFile());
        try {
            reader = new PdfReader(input.toURI().toURL());
            stamper = new PdfStamper(reader, new BufferedOutputStream(new FileOutputStream(output)));
            for (PDFWatermark pdfWatermark : pdfWatermarks) {
                stamp(reader, stamper, pdfWatermark, log);
            }
        } catch (DocumentException t) {
            throw new IOException(t);
        } finally {
            if (stamper != null) {
                try {
                    stamper.close();
                } catch (DocumentException e) {
                }
            }
            if (reader != null) {
                reader.close();
            }
        }

        renameFile(output, input);
    }

    protected void stamp(PdfReader reader,
                         PdfStamper stamper,
                         PDFWatermark pdfWatermark,
                         StatusLog log) throws IOException, DocumentException {
        String[] text = generateText(pdfWatermark::getText, pdfWatermark.getCount());
        if (text != null) {
            byte[] textAsImage = createImageFromText(text, pdfWatermark);
            PdfGState gstate = new PdfGState();
            gstate.setFillOpacity(getTransparency(pdfWatermark));
            PdfContentByte over = null;
            int pageCount = reader.getNumberOfPages();

            log.log("Apply watermark on %d pages", pageCount);
            for (int i = 0; i < text.length; i++) {
                log.log("    Text %d = %s", i + 1, text[i]);
            }

            for (int pageNum = 1; pageNum <= pageCount; ++pageNum) {
                over = stamper.getOverContent(pageNum);
                Rectangle pageSize = reader.getPageSize(pageNum);
                Position start = getStartPosition(pdfWatermark, pageSize);
                Position end = getEndPosition(pdfWatermark, pageSize);
                Image watermark = createWatermarkImage(textAsImage, start, end);
                over.setGState(gstate);
                over.addImage(watermark);
                over.saveState();
                over.restoreState();
            }
        } else {
            log.log("No text to apply?");
        }
    }

    /**
     * Creates an image from watermark text.
     *
     * @param text The text of the watermark
     * @param pdfWatermark
     * @return Byte array containing image data.
     * @throws IOException
     */
    protected byte[] createImageFromText(String text[], PDFWatermark pdfWatermark) throws IOException {
        Font font = new Font(pdfWatermark.getFontName(), Font.PLAIN, pdfWatermark.getFontSize());
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            BufferedImage textAsImage = TextToImage.rotateText(text, font, pdfWatermark.getFontColor(),
                    pdfWatermark.getTextRotation(), null);
            ImageIO.write(textAsImage, "png", baos);
            return baos.toByteArray();
        }
    }

    /***
     * Generates an array of text representing the watermark.
     *
     * @param textSupplier Text supplier
     * @param count How many times text is repeated
     * @return Watermark text
     */
    protected String[] generateText(Supplier<String> textSupplier, int count) {
        if (StringUtils.isOnlyWhitespaceOrEmpty(textSupplier.get())) {
            // Otherwise we will get exception later if text is empty.
            return null;
        }
        return Stream.generate(textSupplier).limit(count).toArray(String[]::new);
    }

    /**
     * Creates an Image object from image data in a byte array.
     *
     * @param imageAsBytes Byte array containing image data.
     * @param start Start position for watermark
     * @param end End position for watermark
     * @return Image object
     * @throws BadElementException
     * @throws MalformedURLException
     * @throws IOException
     */
    protected Image createWatermarkImage(byte[] imageAsBytes, Position start, Position end) throws BadElementException,
                                                                                            MalformedURLException,
                                                                                            IOException {
        Image retval = Image.getInstance(imageAsBytes);
        scaleWatermarkImage(retval, start, end);
        retval.setAbsolutePosition(start.getX(), start.getY());
        return retval;
    }

    /**
     * Renames the output file according to expected target file so that it
     * could be handled further by TIF.
     *
     * @param output Output file
     * @param dest Final file.
     * @throws IOException
     */
    protected void renameFile(File output, File dest) throws IOException {
        if (output.exists()) {
            if (dest.exists() && !dest.delete()) {
                throw new IOException("File cannot be deleted: " + dest.getName());
            }
            logger.debug("Renaming {} to {}", output.getName(), dest.getName());
            FileUtils.moveFile(output, dest);
        } else {
            throw new IOException("File not found: " + output.getName());
        }
    }

    /**
     * Calculates the start X,Y coordinates for watermark. Coordinates are
     * either as percentual or absolute values.
     *
     * @param pdfWatermark
     * @param pageSize Size of current page
     * @return Start position
     */
    protected Position getStartPosition(PDFWatermark pdfWatermark, Rectangle pageSize) {
        float x = pdfWatermark.getStartXPosition() == PositionType.ABSOLUTE ? (float) pdfWatermark.getStartX()
                : pageSize.getWidth() * (float) pdfWatermark.getStartX();
        float y = pdfWatermark.getStartYPosition() == PositionType.ABSOLUTE ? (float) pdfWatermark.getStartY()
                : pageSize.getHeight() * (float) pdfWatermark.getStartY();
        return new Position(x, y);
    }

    /**
     * Calculates the end X,Y coordinates for watermark. Coordinates are either
     * as percentual or absolute values.
     *
     * @param pdfWatermark
     * @param pageSize Size of current page
     * @return End position
     */
    protected Position getEndPosition(PDFWatermark pdfWatermark, Rectangle pageSize) {
        float x = Position.isDefined(pdfWatermark.getEndX())
                ? pdfWatermark.getEndXPosition() == PositionType.ABSOLUTE ? (float) pdfWatermark.getEndX()
                        : pageSize.getWidth() * (float) pdfWatermark.getEndX()
                : Position.UNDEFINED;
        float y = Position.isDefined(pdfWatermark.getEndY())
                ? pdfWatermark.getEndYPosition() == PositionType.ABSOLUTE ? (float) pdfWatermark.getEndY()
                        : pageSize.getHeight() * (float) pdfWatermark.getEndY()
                : Position.UNDEFINED;
        return new Position(x, y);
    }

    /**
     * Returns the transparency level as percentage.
     *
     * @param pdfWatermark
     * @return Transparency level
     */
    protected float getTransparency(PDFWatermark pdfWatermark) {
        return pdfWatermark.getTextTransparency() / 255f;
    }

    /**
     * Scales the watermark Image object in case endX and/or endY are specified.
     *
     * @param image Watermark mage object
     * @param start Start position
     * @param end End position.
     */
    protected void scaleWatermarkImage(Image image, Position start, Position end) {
        if (end.isXDefined() && end.isYDefined()) {
            image.scaleAbsolute(end.getX() - start.getX(), end.getY() - start.getY());
        } else if (end.isXDefined()) {
            image.scaleAbsoluteWidth(end.getX() - start.getX());
        } else if (end.isYDefined()) {
            image.scaleAbsoluteHeight(end.getY() - start.getY());
        }
    }

}
