package com.invantive.util;

import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.util.PDFImageWriter;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Convert a PDF document to an image.
 */
public class Pdf2Image
{
  String _password = null;
  String _imageFormat = "png";
  String _pdfFile = null;
  int    _imageType = BufferedImage.TYPE_INT_RGB;
  int    _startPage = 1;
  int    _endPage = Integer.MAX_VALUE;
  int    _resolution = 300;
  float  _compressionQuality = 0.0f;
  
  Pdf2Image()
  {
  }

  private static final String STANDARD_METADATA_FORMAT = "javax_imageio_1.0";

  private boolean addResolution(IIOMetadata meta, int resolution)
  {
    if (meta.isStandardMetadataFormatSupported())
    {
      IIOMetadataNode root = (IIOMetadataNode)meta.getAsTree(STANDARD_METADATA_FORMAT);
      IIOMetadataNode dim = getChildNode(root, "Dimension");
      IIOMetadataNode child;
      child = getChildNode(dim, "HorizontalPixelSize");
      if (child == null)
      {
        child = new IIOMetadataNode("HorizontalPixelSize");
        dim.appendChild(child);
      }
      child.setAttribute("value", Double.toString(resolution / 25.4));
      child = getChildNode(dim, "VerticalPixelSize");
      if (child == null)
      {
        child = new IIOMetadataNode("VerticalPixelSize");
        dim.appendChild(child);
      }
      child.setAttribute("value", Double.toString(resolution / 25.4));
      try
      {
        meta.mergeTree(STANDARD_METADATA_FORMAT, root);
      }
      catch (IIOInvalidTreeException e)
      {
        throw new RuntimeException("Cannot update image metadata: " + e.getMessage());
      }
      return true;
    }
    return false;
  }

  private static IIOMetadataNode getChildNode(Node n, String name)
  {
    NodeList nodes = n.getChildNodes();
    for (int i = 0; i < nodes.getLength(); i++)
    {
      Node child = nodes.item(i);
      if (name.equals(child.getNodeName()))
      {
        return (IIOMetadataNode)child;
      }
    }
    return null;
  }

  private IIOMetadata createMetadata
  ( RenderedImage image
  , ImageWriter imageWriter
  , ImageWriteParam writerParams
  , int resolution
  )
  {
    ImageTypeSpecifier type;
    if (writerParams.getDestinationType() != null)
    {
      type = writerParams.getDestinationType();
    }
    else
    {
      type = ImageTypeSpecifier.createFromRenderedImage( image );
    }
    IIOMetadata meta = imageWriter.getDefaultImageMetadata( type, writerParams );
    return addResolution(meta, resolution) ? meta : null;
  }

  // Only supports 0 and 90 degrees.
  public static BufferedImage rotate(BufferedImage img, int angle) 
  {
    if (angle == 0)
    {
      return img;
    }
		int w = img.getWidth();
		int h = img.getHeight();
    int newWidth = w;
    int newHeight = h;
    if (angle == 90)
    {
      newWidth = h;
      newHeight = w;
    }
    else
    {
      throw new RuntimeException("Unsupported angle " + angle);
    }
		BufferedImage dimg = new BufferedImage(newWidth, newHeight, img.getType());
		Graphics2D g = dimg.createGraphics();
    g.translate(h/2, w/2);
		g.rotate(Math.toRadians(angle));
    g.translate(-newHeight/2, -newWidth/2);
		g.drawImage(img, null, 0, 0);
		return dimg;
	}
  
  void writeImage
  ( PDDocument document, String imageFormat, String password
  , int startPage, int endPage, String outputPrefix, int imageType, int resolution
  , String placeholderPageNumber
  , float compressionQuality
  )
  throws java.io.IOException
  {
    List pages = document.getDocumentCatalog().getAllPages();
    for (int i = startPage - 1; i < endPage && i < pages.size(); i++ )
    {
      ImageOutputStream output = null;
      ImageWriter imageWriter = null;
      try
      {
        PDPage page = (PDPage)pages.get(i);
        BufferedImage image = page.convertToImage(imageType, resolution);
        int rotation = page.findRotation();
        image = rotate(image, rotation);
        //
        String fileName = outputPrefix.replaceAll(placeholderPageNumber, new Integer(i + 1).toString()) + "." + imageFormat;
        //System.out.println( "Writing: " + fileName );
        output = ImageIO.createImageOutputStream( new File( fileName ) );
        boolean foundWriter = false;
        Iterator writerIter = ImageIO.getImageWritersByFormatName( imageFormat );
        while( writerIter.hasNext() && !foundWriter )
        {
          try
          {
            imageWriter = (ImageWriter)writerIter.next();
            ImageWriteParam writerParams = imageWriter.getDefaultWriteParam();
            //System.err.println("Found writer " + imageWriter.toString());
            //
            // Please note that the standard PNG writer does not compress very well.
            //
            if (writerParams.canWriteCompressed() )
            {
              //System.err.println("Types=" + writerParams.getCompressionTypes());
              writerParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
              // Minimize size.
              writerParams.setCompressionQuality(0.0f);
            }
            if (writerParams.canWriteProgressive())
            {
              writerParams.setProgressiveMode(ImageWriteParam.MODE_DEFAULT);
            }
            IIOMetadata meta = createMetadata( image, imageWriter, writerParams, resolution);
            imageWriter.setOutput( output );
            imageWriter.write( null, new IIOImage( image, null, meta ), writerParams );
            foundWriter = true;
          }
          catch( IIOException io )
          {
            throw new IOException( io.getMessage() );
          }
          finally
          {
            if (imageWriter != null)
            {
              imageWriter.dispose();
            }
          }
        }
        if (!foundWriter )
        {
          throw new RuntimeException("Could not find writer.");
        }
      }
      finally
      {
        if (output != null)
        {
          output.flush();
          output.close();
        }
      }
    }
  }
  
  void setPassword
  ( String password )
  {
    _password = password;
  }
    
  void setPdfFile
  ( String pdfFile )
  {
    _pdfFile = pdfFile;
  }
    
  void setImageType
  ( int imageType )
  {
    // Possible values:
    // BufferedImage.TYPE_BYTE_BINARY;
    // BufferedImage.TYPE_BYTE_INDEXED;
    // BufferedImage.TYPE_BYTE_GRAY;
    // BufferedImage.TYPE_INT_RGB;
    // BufferedImage.TYPE_INT_ARGB;
    _imageType = imageType;
  }
    
  void setStartPage
  ( int startPage )
  {
    _startPage = startPage;
  }
    
  void setEndPage
  ( int endPage )
  {
    _endPage = endPage;
  }
    
  void setResolution
  ( int resolution )
  {
    _resolution = resolution;
  }
    
  void setImageFormat
  ( String imageFormat )
  {
    _imageFormat = imageFormat;
  }
    
  void setCompressionQuality
  ( float compressionQuality )
  {
    _compressionQuality = compressionQuality;
  }
    
  void savePdfFileAsImage
  ( String pdfFile
  , String PngFilePrefix
  , String placeHolderPageNumber
  )
  throws org.apache.pdfbox.exceptions.InvalidPasswordException
  , java.io.IOException
  , org.apache.pdfbox.exceptions.CryptographyException
  {
    PDDocument document = null;
    try
    {
      document = PDDocument.load( pdfFile );
      if (document.isEncrypted() )
      {
        document.decrypt( _password );
      }
      // Fonts are currently not correctly handled. See PDFBOX-516.
      writeImage
      ( document
      , _imageFormat
      , _password
      , _startPage
      , _endPage
      , PngFilePrefix
      , _imageType
      , _resolution
      , placeHolderPageNumber
      , _compressionQuality
      );
    }
    finally
    {
      if (document != null)
      {
        document.close();
      }
    }
  }
}
