/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.idrsolutions.com
 * Help section for developers at http://www.idrsolutions.com/support/
 *
 * (C) Copyright 1997-2017 IDRsolutions and Contributors.
 *
 * This file is part of JPedal/JPDF2HTML5
 *
 
 *
 * ---------------
 * ConvertPagesToHiResImages.java
 * ---------------
 */

package org.jpedal.examples.images;

import com.idrsolutions.image.tiff.TiffEncoder;

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;
import javax.imageio.ImageIO;

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;

/**
 <h2>Image Extraction from PDF files</h2>
 <p>
 * 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<p>
 <p>
 <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>
 <p>
 <h3>Example 2 - convenience static method</h3>
 * Convert all pages as images with options to create higher resolution output<p>
 <p>
 <pre><code>
 * ConvertPagesToHiResImages.writeAllPagesAsHiResImagesToDir("pdfs", "images" , "png", options);
 </code></pre>
 <p>
 <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:<p>
 <p>
 <code>java -cp libraries_needed org/jpedal/examples/images/ConvertPagesToHiResImages inputValues</code><p>
 <p>
 * 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, png, jpg.</li>
 </ul>
 <p>
 * There is another example (org.jpedal.examples.images.ConvertPagesToImages) for producing images of pages if extra features not needed.
 <p><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></p>
 */
public final class ConvertPagesToHiResImages extends BaseImageExtraction {

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

    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 {
            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
     */
    public static void writeAllPagesAsHiResImagesToDir(final String inputDir, final String outputDir, final String formatthrows PdfException {
        
        /*
         * 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, java.io.Serializable> mapValues = new HashMap<Integer, java.io.Serializable>();
        
        /* 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();
    }

    /**
     * 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
                 */
                String pageAsString = String.valueOf(pageNo);
                final String maxPageSize = String.valueOf(end);
                final int padding = maxPageSize.length() - pageAsString.length();
                for (int ii = 0; ii < padding; ii++) {
                    pageAsString = '0' + pageAsString;
                }

                outputFileName = outputPath + "page" + pageAsString + 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 colorspacesUsed = decode_pdf.getPageInfo(PageInfo.COLORSPACES);

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

            while (colorspacesUsed != null && colorspacesUsed.hasNext()) {
                nextID = (Integer) (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) {

                if (fileType.startsWith("tif")) {

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

                    if (isSingleOutputFile) {

                        final File file = new File(outputFileName);
                        if (pageNo == start && 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 {

                    final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(outputFileName)));
                    ImageIO.write(imageToSave, fileType, bos);
                    bos.flush();
                    bos.close();
                }

                imageToSave.flush();
            }
        }
    }

    public BufferedImage getPageAsHiResImage(final int page, final boolean isTransparent, final Map 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);
    }
}