package org.jpedal.examples.images;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;

import javax.imageio.ImageIO;

import org.jpedal.PdfDecoder;
import org.jpedal.color.ColorSpaces;
import org.jpedal.exception.PdfException;
import org.jpedal.fonts.FontMappings;
import org.jpedal.objects.PdfPageData;
import org.jpedal.utils.LogWriter;
import org.jpedal.io.ColorSpaceConvertor;
import org.jpedal.io.JAIHelper;
import org.jpedal.constants.JPedalSettings;
import org.jpedal.constants.PageInfo;

public class HiResThumbnailExtractor {

    private static boolean debug = false;

    /**correct separator for OS */
    final static String separator = System.getProperty"file.separator" );

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

    public HiResThumbnailExtractor() {}

    public static void main(String[] argsthrows Exception {

                 /*
        * Change these variables
        */
        String fileType="",pdfFile="";

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

            pdfFile = args[0];
            fileType= args[1];

            //open the file
            if(pdfFile.toLowerCase().endsWith(".pdf"&& (fileType.equals("jpg"|| fileType.equals("jpeg"|| fileType.equals("png"|| fileType.equals("tiff"|| fileType.equals("tif"))){
                new HiResThumbnailExtractor(fileType, pdfFile);
            }else//open the directory

                File testDir=new File(pdfFile);

                if(testDir.isDirectory()){

                    /**
                     * get list of files and check directory
                     */

                    String[] files = null;
                    File inputFiles = null;

                    /**make sure name ends with a deliminator for correct path later*/
                    if (!pdfFile.endsWith(separator))
                        pdfFile = pdfFile + separator;

                    try {
                        inputFiles = new File(pdfFile);

                        files = inputFiles.list();
                    catch (Exception ee) {
                        LogWriter.writeLog("Exception trying to access file " + ee.getMessage());
                    }

                    /**now work through all pdf files*/
                    long fileCount = files.length;

                    for (int i = 0; i < fileCount; i++) {

                        if (files[i].toLowerCase().endsWith(".pdf"))
                            new HiResThumbnailExtractor(fileType,pdfFile + files[i]);
                    }
                }else
                    System.out.println("The file to be processed has to be a pdf and the output filetype can only be jpg,png or tiff");
            }
        }else{
            System.out.println("Not enough arguments passed in! Usage: \"C:\\examples\\1.pdf\" \"jpg\"");
        }
    }

    /**
     * main constructor to convert PDF to img
     @param fileType
     @param pdfFile
     @throws Exception
     */
    HiResThumbnailExtractor(String fileType, String pdfFilethrows Exception {

        long startTime=System.currentTimeMillis();

        String outputPath = pdfFile.substring(0, pdfFile.toLowerCase().indexOf(".pdf")) + separator;
        File outputPathFile = new File(outputPath);
        if (!outputPathFile.exists() || !outputPathFile.isDirectory()) {
            if (!outputPathFile.mkdirs()) {
                if(debug)
                    System.err.println("Can't create directory " + outputPath);
            }
        }

        //PdfDecoder object provides the conversion
        final PdfDecoder decoder = new PdfDecoder(true);

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

        decoder.openPdfFile(pdfFile);

        /**
         * 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
         */
        Map mapValues = new HashMap();

        /** USEFUL OPTIONS*/
        //do not scale above this figure
        mapValues.put(JPedalSettings.EXTRACT_AT_BEST_QUALITY_MAXSCALING, new Integer(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);

        PdfDecoder.modifyJPedalParameters(mapValues);

        if(debug)
            System.out.println("pdf : " + pdfFile);

        try{
            
            /**
             * 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
             */
            String separation=System.getProperty("org.jpedal.separation");
            if(separation!=null){

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

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

                    decoder.setRenderMode(((Integer)sepValues[seps]).intValue());
                    extractPageAsImage(fileType, outputPath, decoder,"_"+sepValues[seps+1],((Boolean)sepValues[seps+2]).booleanValue())//boolean makes last transparent so we can see white text
                }

            }else //just get the page
                extractPageAsImage(fileType, outputPath, decoder,"",false);

        finally {

            decoder.closePdfFile();
        }
        System.out.println("time="+(System.currentTimeMillis()-startTime)/1000);


    }

    /**
     * convenience method to get a page as a BufferedImage quickly
     * - for bulk conversion, use the other methods
     */
    public static BufferedImage getHiresPage(int pageNo, int scaling, String pdfFile){

        BufferedImage imageToSave = null;

        final PdfDecoder decoder = new PdfDecoder(true);

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

            decoder.openPdfFile(pdfFile);

            PdfPageData pageData = decoder.getPdfPageData();
            int width=scaling*pageData.getCropBoxWidth(pageNo);
            int height=scaling*pageData.getCropBoxHeight(pageNo);

            Map mapValues = new HashMap();

            /** USEFUL OPTIONS*/
            //do not scale above this figure
            mapValues.put(JPedalSettings.EXTRACT_AT_BEST_QUALITY_MAXSCALING, new Integer(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[]{String.valueOf(width),String.valueOf(height)});

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

            PdfDecoder.modifyJPedalParameters(mapValues);

            imageToSave = decoder.getPageAsHiRes(pageNo,null,false);

        catch (PdfException e) {
            e.printStackTrace();
        finally {

            decoder.closePdfFile();
        }
        return imageToSave;
    }

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


        //page range
        int start=1, end=decoder.getPageCount();

        //container if the user is creating a multi-image tiff
        BufferedImage[] multiPages = new BufferedImage[(end-start)];

        /**
         * set of JVM flags which allow user control on process
         */


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

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

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

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

        //allow user to specify value
        String rawJPEGComp=System.getProperty("org.jpedal.compression_jpeg");
        if(rawJPEGComp!=null){
            try{
                JPEGcompression=Float.parseFloat(rawJPEGComp);
            }catch(Exception e){
                e.printStackTrace();
            }
            if(JPEGcompression<|| JPEGcompression>1)
                throw new RuntimeException("Invalid value for JPEG compression - must be between 0 and 1");

        }

        String jpgFlag=System.getProperty("org.jpedal.jpeg_dpi");

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

        for (int pageNo = start; pageNo < end+1; pageNo++) {

            if(debug)
                System.out.println("page : " + pageNo);

            
            /**
             * example1 - ask JPedal to return from decoding if file takes too long (time is in millis)
             * will reset after exit so call for each page
             */
            //decoder.setPageDecodeStatus(DecodeStatus.Timeout,new Integer(20) );
            /**
             * example2 thread which will ask JPedal to time out and return from decoding
             * will reset after exit so call for each page
             */
            /**
             Thread a=new Thread(){
             public void run() {

             while(true){
             //simulate 2 second delay
             try {
             Thread.sleep(2000);
             } catch (InterruptedException e) {
             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
             }

             //tell JPedal to exit asap
             decoder.setPageDecodeStatus(DecodeStatus.Timeout, Boolean.TRUE);

             }
             }
             };

             //simulate a second thread
             a.start();

             //see code after line decoder.getPageAsHiRes(pageNo); for tracking whether JPedal timed out and returned
             /**/

            /**
             * 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 http://www.jpedal.org/support_egHiRes.php for more details on how to use HiResThumbnailExtraction.
             */
            BufferedImage imageToSave = decoder.getPageAsHiRes(pageNo,null,isTransparent);

            String imageFormat = System.getProperty("org.jpedal.imageType");
            if(imageFormat!=null){
                if(isNumber(imageFormat)){
                    int iFormat = Integer.parseInt(imageFormat);
                    if(iFormat>-&& iFormat<14){
                        BufferedImage tempImage = new BufferedImage(imageToSave.getWidth(), imageToSave.getHeight(), iFormat);
                        Graphics2D g = tempImage.createGraphics();
                        g.drawImage(imageToSave, null, null);

                        imageToSave = tempImage;
                    }else{
                        System.err.println("Image Type is not valid. Value should be a digit between 0 - 13 based on the BufferedImage TYPE variables.");
                    }
                }else{
                    System.err.println("Image Type provided is not an Integer. Value should be a digit between 0 - 13 based on the BufferedImage TYPE variables.");
                }
            }

            //show status flag
            /**
             if(decoder.getPageDecodeStatus(DecodeStatus.Timeout))
             System.out.println("Timeout on decoding");
             else
             System.out.println("Done");
             /**/

            decoder.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);


            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);
                String maxPageSize=String.valueOf(end);
                int padding=maxPageSize.length()-pageAsString.length();
                for(int ii=0;ii<padding;ii++)
                    pageAsString='0'+pageAsString;
                
                outputFileName = outputPath + "page" + pageAsString +prefix + "." + fileType;
            }
            File file = new File(outputFileName);

            
            //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
             */
            Iterator colorspacesUsed=decoder.getPageInfo(PageInfo.COLORSPACES);

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

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

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

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

            //put image in array if multi-images  (we save on last page in code below)
            if(isSingleOutputFile)
                multiPages[pageNo-start= imageToSave;

            //we save the image out here
            if (imageToSave != null) {

                /**BufferedImage does not support any dpi concept. A higher dpi can be created
                 * using JAI to convert to a higher dpi image*/

                //shrink the page to 50% with graphics2D transformation
                //- add your own parameters as needed
                //you may want to replace null with a hints object if you
                //want to fine tune quality.

                /** example 1 biliniear scaling
                 AffineTransform scale = new AffineTransform();
                 scale.scale(.5, .5); //50% as a decimal
                 AffineTransformOp scalingOp =new AffineTransformOp(scale, null);
                 image_to_save =scalingOp.filter(image_to_save, null);

                 */

                if(JAIHelper.isJAIused())
                    JAIHelper.confirmJAIOnClasspath();

                if(JAIHelper.isJAIused() && fileType.startsWith("tif")){

                    com.sun.media.jai.codec.TIFFEncodeParam params = new com.sun.media.jai.codec.TIFFEncodeParam();

                    if(compressTiffs)
                        params.setCompression(com.sun.media.jai.codec.TIFFEncodeParam.COMPRESSION_PACKBITS);

                    if(!isSingleOutputFile){
                        FileOutputStream os = new FileOutputStream(outputFileName);

                        javax.media.jai.JAI.create("encode", imageToSave, os, "TIFF", params);
                    }else if(isSingleOutputFile && pageNo == end){
                        OutputStream out = new FileOutputStream(outputFileName);
                        com.sun.media.jai.codec.ImageEncoder encoder = com.sun.media.jai.codec.ImageCodec.createImageEncoder("TIFF", out, params);
                        Vector vector = new Vector();
                        vector.addAll(Arrays.asList(multiPages).subList(1, multiPages.length));

                        params.setExtraImages(vector.iterator());

                        encoder.encode(multiPages[0]);
                        out.close();
                    }
                }else if(isSingleOutputFile){
                    //non-JAI
                else if ((jpgFlag != null || rawJPEGComp!=null&& fileType.startsWith("jp"&& JAIHelper.isJAIused()) {

                    com.sun.image.codec.jpeg.JPEGImageEncoder jpegEncoder = com.sun.image.codec.jpeg.JPEGCodec.createJPEGEncoder(new FileOutputStream(outputFileName));
                    com.sun.image.codec.jpeg.JPEGEncodeParam jpegEncodeParam = jpegEncoder.getDefaultJPEGEncodeParam(imageToSave);

                    if (jpgFlag != null){

                        int dpi = 96;

                        try {
                            dpi = Integer.parseInt(jpgFlag);
                        catch (Exception e) {
                            e.printStackTrace();
                        }

                        jpegEncodeParam.setDensityUnit(com.sun.image.codec.jpeg.JPEGEncodeParam.DENSITY_UNIT_DOTS_INCH);
                        jpegEncodeParam.setXDensity(dpi);
                        jpegEncodeParam.setYDensity(dpi);

                    }

                    if(JPEGcompression>=&& JPEGcompression<=1f){
                        jpegEncodeParam.setQuality(JPEGcompression,false);
                        System.out.println("xx");
                    }

                    jpegEncoder.encode(imageToSave, jpegEncodeParam);
                else {

                    BufferedOutputStream bos= new BufferedOutputStream(new FileOutputStream(new File(outputFileName)));
              ImageIO.write(imageToSave, fileType, bos);
              bos.flush();
              bos.close();
                }
                //if you just want to save the image, use something like
                //javax.imageio.ImageIO.write((java.awt.image.RenderedImage)image_to_save,"png",new java.io.FileOutputStream(output_dir + page + image_name+".png"));

            }

            imageToSave.flush();
            imageToSave=null;

            if(debug){
                System.out.println("Created : " + outputFileName);

            }
        }
    }

    /**test to see if string or number*/
    private static boolean isNumber(String value) {

        //assume true and see if proved wrong
        boolean isNumber=true;

        int charCount=value.length();
        for(int i=0;i<charCount;i++){
            char c=value.charAt(i);
            if((c<'0')|(c>'9')){
                isNumber=false;
                i=charCount;
            }
        }

        return isNumber;
    }
}