/*
 * Copyright (c) 1997-2019 IDRsolutions (https://www.idrsolutions.com)
 */

package org.jpedal.examples.images;

import com.idrsolutions.image.bmp.BmpEncoder;
import com.idrsolutions.image.jpeg2000.Jpeg2000Encoder;
import com.idrsolutions.image.tiff.TiffEncoder;
import org.jpedal.JDeliHelper;
import org.jpedal.PdfDecoderServer;
import org.jpedal.color.ColorSpaces;
import org.jpedal.constants.JPedalSettings;
import org.jpedal.constants.PageInfo;
import org.jpedal.exception.PdfException;
import org.jpedal.fonts.FontMappings;
import org.jpedal.io.ColorSpaceConvertor;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 <h2>Image Extraction from PDF files</h2>
 <br>
 * This class provides a simple Java API to convert pages in a PDF files into images and also
 * a static convenience method if you just want to dump all the pages as images from a PDF file
 * or directory containing PDF files<br>
 <br>
 <h3>Example 1 - access API methods</h3>
 <pre><code>
 * ConvertPagesToHiResImages extract=new ConvertPagesToHiResImages("C:/pdfs/mypdf.pdf");
 * //extract.setPassword("password");
 * HashMap options=new HashMap(); //see https://javadoc.idrsolutions.com/org/jpedal/constants/JPedalSettings.html
 * if (extract.openPDFFile()) {
 *     int pageCount=extract.getPageCount();
 *     for (int page=1; page&lt;=pageCount; page++) {
 *
 *        BufferedImage image=extract.getPageAsHiResImage(page, isBackgroundTransparent,  options);
 *     }
 * }
 *
 * extract.closePDFfile();
 </code></pre>
 <br>
 <h3>Example 2 - convenience static method</h3>
 * Convert all pages as images with options to create higher resolution output<br>
 <br>
 <pre><code>
 * ConvertPagesToHiResImages.writeAllPagesAsHiResImagesToDir("pdfs", "images" , "png", options);
 </code></pre>
 <br>
 <h3>Example 3 - Access directly from the Jar</h3>
 * ExtractImages can run from jar directly using the command and will extract all files from a PDF file or directory
 * to a defined output directory:<br>
 <br>
 <code>java -cp libraries_needed org/jpedal/examples/images/ConvertPagesToHiResImages inputValues</code><br>
 <br>
 * Where inputValues is 3 values:
 <ul>
 <li>First value:    The PDF filename (including the path if needed) or a directory containing PDF files. If it contains spaces it must be enclosed by double quotes (ie "C:/Path with spaces/").</li>
 <li>Second value: The location to write out images extracted from the PDF file or files. If it contains spaces it must be enclosed by double quotes (ie "C:/Path with spaces/").</li>
 <li>Third value:    This indicates the required output image type (default is png if nothing specified). Options are tiff, bmp, png, jpg.</li>
 </ul>
 <br>
 * There is another example (org.jpedal.examples.images.ConvertPagesToImages) for producing images of pages if extra features not needed.
 <br><a href="https://www.idrsolutions.com/how-to-convert-pdf-files-to-image">Click here for a list of code examples to convert images</a><br>
 */
public final class ConvertPagesToHiResImages extends BaseImageExtraction {

    //only used if between 0 and 1
    private float JPEGcompression = -1f;

    @SuppressWarnings("unused")
    public static void main(final String[] args) {

        if (args != null && args.length > 1) {

            try {
                if (args.length == 2) {
                    writeAllPagesAsHiResImagesToDir(args[0], args[1]"png");
                else {
                    writeAllPagesAsHiResImagesToDir(args[0], args[1], args[2]);
                }
            catch (final PdfException ex) {
                throw new RuntimeException(ex.getMessage());
            }
        else if (args == null) {
            System.out.println("null arguments entered");

        else {
            System.out.println("wrong arguments entered");

            final StringBuilder arguments = new StringBuilder();
            for (final String arg : args) {
                arguments.append(arg).append('\n');
            }
            System.out.println("you entered:\n" + arguments + "as the arguments");
        }
    }

    /**
     * static method to write out all pages in a PDF files or directory of PDF files as images
     * Not for use with other image conversion methods in multi-threaded environments.
     * This method utilises some variables that may impact image conversion taking place on other threads.
     *
     @param inputDir directory of files to convert
     @param outputDir directory of output
     @param format format of images
     @throws org.jpedal.exception.PdfException PdfException
     */
    public static void writeAllPagesAsHiResImagesToDir(final String inputDir, final String outputDir, final String formatthrows PdfException {

        if (JDeliHelper.hasEncoderSupportForImageFormat(format)) {
            /*
             * this process is very flaxible to we create a Map and pass in values to select what sort
             * of results we want. There is a choice between methods used and image size. Larger images use more
             * memory and are slower but look better
             */
            final Map<Integer, Object> mapValues = new HashMap<>();

            /* USEFUL OPTIONS*/
            //do not scale above this figure
            mapValues.put(JPedalSettings.EXTRACT_AT_BEST_QUALITY_MAXSCALING, 2);

            //alternatively secify a page size (aspect ratio preserved so will do best fit)
            //set a page size (JPedal will put best fit to this)
            mapValues.put(JPedalSettings.EXTRACT_AT_PAGE_SIZE, new String[]{"2000""1600"});

            //which takes priority (default is false)
            mapValues.put(JPedalSettings.PAGE_SIZE_OVERRIDES_IMAGE, Boolean.TRUE);

            PdfDecoderServer.modifyJPedalParameters(mapValues);

            final ConvertPagesToHiResImages convert = new ConvertPagesToHiResImages(inputDir);

            convert.setup(format, outputDir);

            convert.processFiles(inputDir);

            convert.closePDFfile();
        else {
            throw new RuntimeException("Unknown image format - " + format);
        }
    }

    /**
     * Sets up an ConvertPagesToHiResImages instance to open a PDF File
     *
     @param fileName full path to a single PDF file
     */
    public ConvertPagesToHiResImages(final String fileName) {
        super(fileName);

        init();
    }

    /**
     * Sets up an ConvertPagesToHiResImages instance to open  a PDF file contained as a BLOB within a byte[] stream
     *
     @param byteArray
     */
    public ConvertPagesToHiResImages(final byte[] byteArray) {
        super(byteArray);

        init();

    }

    /**
     * main constructor to convert PDF to img
     *
     @param pdfFile
     @throws Exception
     */
    @Override
    void decodeFile(final String pdfFilethrows PdfException {


        System.out.println(pdfFile);
        System.out.println(output_dir);

        if (openPDFFile()) {

                         /*
             * allow output to multiple images with different values on each
             *
             * Note we REMOVE shapes as it is a new feature and we do not want to break existing functions
             */
            final String separation = System.getProperty("org.jpedal.separation");
            if (separation != null) {

                Object[] sepValues = {7"", Boolean.FALSE}//default of normal
                if (separation.equals("all")) {
                    sepValues = new Object[]{PdfDecoderServer.RENDERIMAGES, "image_and_shapes", Boolean.FALSE,
                            PdfDecoderServer.RENDERIMAGES + PdfDecoderServer.REMOVE_RENDERSHAPES, "image_without_shapes", Boolean.FALSE,
                            PdfDecoderServer.RENDERTEXT, "text_and_shapes", Boolean.TRUE,
                            7"all", Boolean.FALSE,
                            PdfDecoderServer.RENDERTEXT + PdfDecoderServer.REMOVE_RENDERSHAPES, "text_without_shapes", Boolean.TRUE
                    };
                }

                final int sepCount = sepValues.length;
                for (int seps = 0; seps < sepCount; seps += 3) {

                    decode_pdf.setRenderMode((IntegersepValues[seps]);
                    try {
                        extractPagesAsImages(output_dir, imageType, "_" + sepValues[seps + 1](BooleansepValues[seps + 2])//boolean makes last transparent so we can see white text
                    catch (final IOException ex) {
                        throw new PdfException(ex.getMessage());
                    }
                }

            else {
                try {
                    //just get the page
                    extractPagesAsImages(output_dir, imageType, ""false);
                catch (final IOException ex) {
                    throw new PdfException(ex.getMessage());
                }
            }
        }
    }

    /**
     * actual conversion of a PDF page into an image
     *
     @param fileType
     @param outputPath
     @param prefix
     @param isTransparent
     @throws PdfException
     @throws IOException
     */
    private void extractPagesAsImages(final String outputPath, final String fileType, final String prefix, final boolean isTransparentthrows PdfException, IOException {

        //create a directory if it doesn't exist
        final File output_path = new File(output_dir);
        if (!output_path.exists()) {
            output_path.mkdirs();
        }

        //page range
        final int start = 1;
        final int end = getPageCount();
        
        /*
         * set of JVM flags which allow user control on process
         */


        //////////////////TIFF OPTIONS/////////////////////////////////////////

        final String multiPageFlag = System.getProperty("org.jpedal.multipage_tiff");
        final boolean isSingleOutputFile = multiPageFlag != null && multiPageFlag.equalsIgnoreCase("true");

        final String tiffFlag = System.getProperty("org.jpedal.compress_tiff");
        final boolean compressTiffs = tiffFlag != null && tiffFlag.equalsIgnoreCase("true");

        //////////////////JPEG OPTIONS/////////////////////////////////////////

        //allow user to specify value
        final String rawJPEGComp = System.getProperty("org.jpedal.compression_jpeg");
        if (rawJPEGComp != null) {
            try {
                JPEGcompression = Float.parseFloat(rawJPEGComp);
            catch (final Exception e) {
                e.printStackTrace();
            }
            if (JPEGcompression < || JPEGcompression > 1) {
                throw new RuntimeException("Invalid value for JPEG compression - must be between 0 and 1");
            else {
                //remember to update https://www.idrsolutions.com/jvm-flags/ when we fix
                throw new RuntimeException("JPEG compression is not currently implemented. Please do not use");
            }
        }

        final String jpgFlag = System.getProperty("org.jpedal.jpeg_dpi");
        if (jpgFlag != null) {
            //remember to update https://www.idrsolutions.com/jvm-flags/ when we fix
            throw new RuntimeException("JPEG dpi is not currently implemented. Please do not use");
        }

        ///////////////////////////////////////////////////////////////////////

        for (int pageNo = start; pageNo < end + 1; pageNo++) {
            
            /*
             * If you are using decoder.getPageAsHiRes() after passing additional parameters into JPedal using the static method
             * PdfDecoder.modifyJPedalParameters(), then getPageAsHiRes() wont necessarily be thread safe.  If you want to use
             * getPageAsHiRes() and pass in additional parameters, in a thread safe mannor, please use the method
             * getPageAsHiRes(int pageIndex, Map params) or getPageAsHiRes(int pageIndex, Map params, boolean isTransparent) and
             * pass the additional parameters in directly to the getPageAsHiRes() method without calling PdfDecoder.modifyJPedalParameters()
             * first.
             *
             * Please see org/jpedal/examples/images/ConvertPagesToImages.java.html for more details on how to use HiRes image conversion
             */
            BufferedImage imageToSave = getPageAsHiResImage(pageNo, isTransparent, null);

            decode_pdf.flushObjectValues(true);

            //System.out.println("w="+imageToSave.getWidth()+" h="+imageToSave.getHeight());
            //image needs to be sRGB for JPEG
            if (fileType.equals("jpg")) {
                imageToSave = ColorSpaceConvertor.convertToRGB(imageToSave);
            }

            final String outputFileName;
            if (isSingleOutputFile) {
                outputFileName = outputPath + "allPages" + prefix + '.' + fileType;
            else {
                /*
                 * create a name with zeros for if more than 9 pages appears in correct order
                 */
                outputFileName = outputPath + "page" + getPageName(end, pageNo+ prefix + '.' + fileType;
            }

            //if just gray we can reduce memory usage by converting image to Grayscale
            
            /*
             * see what Colorspaces used and reduce image if appropriate
             * (only does Gray at present)
             *
             * Can return null value if not sure
             */
            final Iterator<Integer> colorspacesUsed = decode_pdf.getPageInfo(PageInfo.COLORSPACES);

            int nextID;
            boolean isGrayOnly = colorspacesUsed != null//assume true and disprove

            while (colorspacesUsed != null && colorspacesUsed.hasNext()) {
                nextID = colorspacesUsed.next();

                if (nextID != ColorSpaces.DeviceGray && nextID != ColorSpaces.CalGray) {
                    isGrayOnly = false;
                }
            }

            //draw onto GRAY image to reduce colour depth
            //(converts ARGB to gray)
            if (isGrayOnly) {
                final BufferedImage image_to_save2 = new BufferedImage(imageToSave.getWidth(), imageToSave.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
                image_to_save2.getGraphics().drawImage(imageToSave, 00null);
                imageToSave = image_to_save2;
            }

            //we save the image out here
            if (imageToSave != null) {
                saveImage(fileType, isSingleOutputFile, compressTiffs, pageNo, imageToSave, outputFileName);

                imageToSave.flush();
            }
        }
    }

    private void setJPEGCompressionOption(String rawJPEGComp) {
        try {
            JPEGcompression = Float.parseFloat(rawJPEGComp);
        catch (final Exception e) {
            e.printStackTrace();
        }
        if (JPEGcompression < || JPEGcompression > 1) {
            throw new RuntimeException("Invalid value for JPEG compression - must be between 0 and 1");
        else {
            //remember to update https://www.idrsolutions.com/jvm-flags/ when we fix
            throw new RuntimeException("JPEG compression is not currently implemented. Please do not use");
        }
    }

    private static void saveImage(String fileType, boolean isSingleOutputFile, boolean compressTiffs, int pageNo, BufferedImage imageToSave, String outputFileNamethrows IOException {
        if (fileType.startsWith("tif")) {

            final TiffEncoder tiffEncoder = new TiffEncoder();
            tiffEncoder.setCompressed(compressTiffs);

            if (isSingleOutputFile) {

                final File file = new File(outputFileName);
                if (pageNo == && file.exists()) {
                    file.delete();
                    file.createNewFile();
                }
                tiffEncoder.append(imageToSave, outputFileName);

            else {
                final File file = new File(outputFileName);
                final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
                tiffEncoder.write(imageToSave, bos);
                bos.flush();
                bos.close();
            }
        else if (fileType.equalsIgnoreCase("bmp")) {
            final BmpEncoder encoder = new BmpEncoder();
            final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(outputFileName)));
            encoder.write(imageToSave, bos);
            bos.flush();
            bos.close();

        else if (fileType.equalsIgnoreCase("jp2")) { //please note type may be JP2 instead of jp2
            final Jpeg2000Encoder encoder = new Jpeg2000Encoder();
            final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(outputFileName)));
            encoder.write(imageToSave, bos);
            bos.flush();
            bos.close();
        else {
            final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(outputFileName)));
            ImageIO.write(imageToSave, fileType, bos);
            bos.flush();
            bos.close();
        }
    }

    private static StringBuilder getPageName(int end, int pageNo) {
        StringBuilder pageAsString = new StringBuilder(String.valueOf(pageNo));
        final String maxPageSize = String.valueOf(end);
        final int padding = maxPageSize.length() - pageAsString.length();
        for (int ii = 0; ii < padding; ii++) {
            pageAsString.insert(0'0');
        }
        return pageAsString;
    }

    public BufferedImage getPageAsHiResImage(final int page, final boolean isTransparent, final Map<Integer, Object> optionsthrows PdfException {

        return decode_pdf.getPageAsHiRes(page, options, isTransparent);

    }

    private void setup(final String format, String outputDir) {

        //check output dir has separator
        if (!outputDir.endsWith(separator)) {
            outputDir += separator;
        }

        this.imageType = format;
        this.output_dir = outputDir;
    }

    @Override
    void init() {


        //mappings for non-embedded fonts to use
        FontMappings.setFontReplacements();

        type = ExtractTypes.RASTERIZED_PAGE;

        super.init();

        //decode_pdf.setExtractionMode(0, pageScaling);
    }
}