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

package org.jpedal.examples;

import org.jpedal.PdfDecoderInt;
import org.jpedal.PdfDecoderServer;
import org.jpedal.exception.PdfException;
import org.jpedal.fonts.FontMappings;
import org.jpedal.objects.PdfFileInformation;
import org.jpedal.objects.PdfPageData;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfObject;
import org.jpedal.parser.image.ImageCommands;
import org.jpedal.render.DynamicVectorRenderer;

import java.util.HashMap;
import java.util.Map;

/**
 <h2>PdfUtilities</h2>
 <p>
 * This class provides a simple Java API to access general PDF features in JPEDAL via a simple API
 */
public class PdfUtilities {

    /**
     * return a count of Postscript commands from page -
     *
     @return <p>
     <pre>PdfUtilities pdfUtils=new PdfUtilities("C:/pdfs/mypdf.pdf");
     * //pdfUtils.setPassword("password");
     * if (pdfUtils.openPDFFile()) {
     *  int commandsOnPage=pdfUtils.getCommandCountForPageStream();
     * }
     *
     * pdfUtils.closePDFfile();</pre>
     */
    public int getCommandCountForPageStream(final int page) {

        decode_pdf.setExtractionMode(0);
        decode_pdf.decodePage(page);

        return decode_pdf.getDynamicRenderer().getValue(DynamicVectorRenderer.TOKEN_NUMBER);

    }


    public enum PageUnits Pixels, Inches, Centimetres }

    public enum PageSizeType MediaBox, CropBox }

    private PdfFileInformation currentFileInformation;

    private byte[] byteArray;

    private boolean isOpen;

    private boolean hasEmbeddedFonts;
    private boolean testIfFontsEmbedded;

    private PdfPageData currentPageData;

    /**
     * the decoder object which decodes the pdf and returns a data object
     */
    private PdfDecoderInt decode_pdf;
    
    private String password;

    private String fileName;


    public PdfUtilities(final String fileName) {
        this.fileName = fileName;

        init();

    }

    public PdfUtilities(final byte[] byteArray) {
        this.byteArray = byteArray;

        init();
    }

    /**
     @param password the USER or OWNER password for the PDF file
     */
    public void setPassword(final String password) {

        this.password = password;

    }

    private void init() {

        FontMappings.setFontReplacements();
        
        decode_pdf = new PdfDecoderServer(false)//false as no GUI display needed

        //Check root dir has separator
//        String user_dir = System.getProperty( "user.dir" );
//        if(!user_dir.endsWith(separator)) {
//            user_dir += separator;
//        }
    }
    
    /**
     * number of pages in PDF file (starting at 1)
     *
     @return page count<p>
     <pre>PdfUtilities pdfUtils=new PdfUtilities("C:/pdfs/mypdf.pdf");
     * //pdfUtils.setPassword("password");
     * if (pdfUtils.openPDFFile()) {
     * int pageCount=pdfUtils.getPageCount();
     * }
     *
     * pdfUtils.closePDFfile();</pre>
     */
    public int getPageCount() {

        checkFileOpened();

        return decode_pdf.getPageCount();
    }

    /**
     * get image data on pages in PDF file (starting at 1)
     * (Method IS NOT THREADSAFE)
     *
     @return page count<p>
     <pre>PdfUtilities pdfUtils=new PdfUtilities("C:/pdfs/mypdf.pdf");
     * //pdfUtils.setPassword("password");
     * if (pdfUtils.openPDFFile()) {
     * String pageImages=pdfUtils.getXImageDataForPage(int page);
     * }
     *
     * pdfUtils.closePDFfile();</pre>
     */
    public String getXImageDataForPage(int page) {

        ImageCommands.trackImages = true;

        checkFileOpened();  // creates instance of decode_pdf

        decode_pdf.decodePage(page);
        
        closePDFfile();  //closes instance of decode_pdf

        ImageCommands.trackImages = false;

        return decode_pdf.getInfo(PdfDictionary.Image);
    }

    /**
     * routine to open the PDF File so we can access
     *
     @return true if successful
     @throws PdfException
     */
    public boolean openPDFFile() throws PdfException {

        isOpen = false;
       
        try {
            if (fileName != null) {
                if (password == null) {
                    decode_pdf.openPdfFile(fileName);
                else {
                    decode_pdf.openPdfFile(fileName, password);
                }
            else if (byteArray != null) {
                if (password == null) {
                    decode_pdf.openPdfArray(byteArray);
                else {
                    decode_pdf.openPdfArray(byteArray, password);
                }
            }
            
            /*
             * check security settings
             */
            if (decode_pdf.isEncrypted() && !decode_pdf.isPasswordSupplied()) {

                if (password == null) {
                    throw new PdfException("Unable to open encrypted PDF file - call setPassword(passsword) ");
                else {
                    throw new PdfException("Unable to open encrypted PDF file with password " + password);
                }
            else {

                isOpen = true;
                
                /*get the Pdf file information object to extract info from*/
                currentFileInformation = decode_pdf.getFileInformationData();
                this.currentPageData = decode_pdf.getPdfPageData();
            }
        catch (final PdfException e) {
            throw new PdfException(e.getMessage());
        }

        return isOpen;
    }

    /**
     * ensure PDF file is closed once no longer needed and all resources released
     */
    public void closePDFfile() {

        checkFileOpened();

        if (decode_pdf != null && decode_pdf.isOpen()) {

            decode_pdf.flushObjectValues(true);

            decode_pdf.closePdfFile();
        }

        //reset in case instance reused
        hasEmbeddedFonts = false;
        testIfFontsEmbedded = false;

    }

    /**
     * return boolean to show if Pdf file contains embedded fonts
     <p>
     <pre>PdfUtilities pdfUtils=new PdfUtilities("C:/pdfs/mypdf.pdf");
     * //pdfUtils.setPassword("password");
     * if (pdfUtils.openPDFFile()) {
     *  boolean usesEmbeddedFonts=pdfUtils.hasEmbeddedFonts();
     * }
     *
     * pdfUtils.closePDFfile();</pre>
     */
    public boolean hasEmbeddedFonts() {

        checkFileOpened();

        if (!testIfFontsEmbedded) {

            testIfFontsEmbedded = true;

            final int pageCount = getPageCount();

            for (int page = 1; page < pageCount + 1; page++) {
                decode_pdf.decodePage(page);

                hasEmbeddedFonts = decode_pdf.hasEmbeddedFonts();

                // exit on first true
                if (hasEmbeddedFonts) {
                    page = pageCount;
                }
            }
        }

        return hasEmbeddedFonts;
    }

    /**
     * return a Map containing the pair value properties (if present) from the open PDF file -
     *
     @return <p>
     <pre>PdfUtilities pdfUtils=new PdfUtilities("C:/pdfs/mypdf.pdf");
     * //pdfUtils.setPassword("password");
     * if (pdfUtils.openPDFFile()) {
     *  Map mapOfValuePairs=pdfUtils.getDocumentPropertyStringValuesAsMap();
     * }
     *
     * pdfUtils.closePDFfile();</pre>
     */
    public Map<String, String> getDocumentPropertyStringValuesAsMap() {

        checkFileOpened();

        final String[] names = PdfFileInformation.getFieldNames();
        final int count = names.length;

        final String[] keys = currentFileInformation.getFieldValues();

        final Map<String, String> map = new HashMap();
        for (int i = 0; i < count; i++) {
            map.put(names[i], keys[i]);
        }

        return map;
    }

    /**
     * access the XML Document properties String (if present) from the open PDF file
     *
     @return String containing the raw text data
     <p>
     <pre>PdfUtilities pdfUtils=new PdfUtilities("C:/pdfs/mypdf.pdf");
     * //pdfUtils.setPassword("password");
     * if (pdfUtils.openPDFFile()) {
     *  String XMLStringData=pdfUtils.getDocumentPropertyFieldsInXML();
     * }
     *
     * pdfUtils.closePDFfile();</pre>
     */
    public String getDocumentPropertyFieldsInXML() {

        checkFileOpened();

        return currentFileInformation.getFileXMLMetaData();

    }

    /**
     @param page  is pageNumber
     @param units units to use for pageSize dimensions (pixels, Inches, Centimetres) except rotation which is always in degrees
     @param type  which values to select (MediaBox, CropBox)
     @return a float[] with 5 values:- x,y,w,h, pageRotation
     <p>
     <pre>PdfUtilities pdfUtils=new PdfUtilities("C:/pdfs/mypdf.pdf");
     * //pdfUtils.setPassword("password");
     * if (pdfUtils.openPDFFile()) {
     *  float[] pageDimensions=pdfUtils.getPageDimensions(pageNum, PageUnits.Inches, PageSizeType.CropBox);
     * }
     *
     * pdfUtils.closePDFfile();</pre>
     */
    public float[] getPageDimensions(final int page, final PageUnits units, final PageSizeType type) {

        checkFileOpened();

        final float[] pageSize = new float[5];

        float factor = 0f;

        switch (units) {
            case Pixels:
                factor = 1f;
                break;
            case Inches:
                factor = 72f;
                break;
            case Centimetres:
                factor = 72f 2.54f;
                break;
        }

        switch (type) {
            case MediaBox:
                pageSize[0= currentPageData.getMediaBoxX(page/ factor;
                pageSize[1= currentPageData.getMediaBoxY(page/ factor;
                pageSize[2= currentPageData.getMediaBoxWidth(page/ factor;
                pageSize[3= currentPageData.getMediaBoxHeight(page/ factor;
                break;

            case CropBox:
                pageSize[0= currentPageData.getCropBoxX(page/ factor;
                pageSize[1= currentPageData.getCropBoxY(page/ factor;
                pageSize[2= currentPageData.getCropBoxWidth(page/ factor;
                pageSize[3= currentPageData.getCropBoxHeight(page/ factor;

                break;
        }

        pageSize[4= currentPageData.getRotation(page);

        return pageSize;
    }

    private void checkFileOpened() {
        if (!isOpen) {
            throw new RuntimeException("PDF file needs to be opened with openPDFFile() method first");
        }
    }

    /**
     @return an unsigned 32-bit integer containing a set of permission flags
     <p>
     <pre>PdfUtilities pdfUtils=new PdfUtilities("C:/pdfs/mypdf.pdf");
     * //pdfUtils.setPassword("password");
     * if (pdfUtils.openPDFFile()) {
     *  int P = pdfUtils.getPdfFilePermissions();
     * }
     *
     * pdfUtils.closePDFfile();</pre>
     */
    public int getPdfFilePermissions() {
        int P = -1;
        if (decode_pdf != null) {
            final PdfObject encryptObj = decode_pdf.getIO().getPDFObject(PdfDictionary.Encrypt);
            if (encryptObj != null) {
                P = encryptObj.getInt(PdfDictionary.P);
            }
        }
        return P;
    }

    /**
     @param P a 32-bit integer containing permission flags extracted from PDF
     *          <p>
     *          <pre>PdfUtilities pdfUtils=new PdfUtilities("C:/pdfs/mypdf.pdf");
     *          //pdfUtils.setPassword("password");
     *          if (pdfUtils.openPDFFile()) {
     *           int P = pdfUtils.getPdfFilePermissions();
     *           PdfUtilities.showPermissionsAsString(P);
     *          }
     *
     *          pdfUtils.closePDFfile();</pre>
     */
    public static void showPermissionsAsString(final int P) {

        //power of 2 lookup table
        final int[] lookup = new int[32];
        int power = 1;
        for (int ptr = 0; ptr < 32; ptr++) {
            lookup[ptr= power;
            power *= 2;
        }

        System.out.println("PDF Security settings in Encryption Object");
        System.out.println("------------------------------------------");
        System.out.println("raw P value = " + Integer.toBinaryString(P));

        final Object[] flags = {3"Printing"4"Modify"5"Copy/extract"6"Add/modify Annotation",
                9"Fill in existing forms"10"Extraction for accessibility"11"Document Assembly"12"Printing High-quality"};

        final int count = flags.length;
        for (int ptr = 0; ptr < count; ptr += 2) {

            final int bitToTest = (Integerflags[ptr];
            final String value = (Stringflags[ptr + 1];
            if ((P & lookup[bitToTest]) == lookup[bitToTest]) {
                System.out.println(value + " (bit " + bitToTest + ") is allowed");
            else {
                System.out.println(value + " (bit " + bitToTest + ") is NOT allowed");
            }
        }
    }
}