/*
 * @(#)EmbedPDFApplet.java  1.0  2008-09-18
 * 
 * Copyright (c) 2008 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package ch.randelshofer.pdf;

import ch.randelshofer.gui.GlassPaneProgressView;
import ch.randelshofer.gui.ProgressObserver;
import org.monte.media.gui.Worker;
import org.monte.media.io.BoundedRangeInputStream;
import ch.randelshofer.util.Applets;
import org.jhotdraw.util.ResourceBundleUtil;
import com.sun.pdfview.*;
import com.sun.pdfview.font.BuiltinFont;
import java.awt.Color;
import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import javax.swing.*;
import javax.swing.border.LineBorder;

/**
 * A Swing-based applet that displays a PDF file.
 *
 * @author Werner Randelshofer, Hausmatt 10, CH-6405 Immensee
 * @version 1.0 September 18, 2008 Created.
 */
public class EmbedPDFApplet extends javax.swing.JApplet implements Runnable, OpenURIListener {

    private File tmpFile;
    protected GlassPaneProgressView progressView;
    private PDFFile pdfFile;
    protected JPDFFilePanel pdfDocumentPanel;
    private ResourceBundleUtil labels;
    protected URL pdfURL;

    public EmbedPDFApplet() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception ex) {
            // nothing to do
        }

       
        String version=getAppletVersion();
        String info = "<html><font color=#aaaaaa size=-2>EmbedPDF" + (version==null?" ":" "+version);
        JLabel appletInfo = new JLabel(info);
        appletInfo.setVerticalAlignment(JLabel.BOTTOM);
        appletInfo.setHorizontalAlignment(JLabel.RIGHT);
        getContentPane().add(appletInfo);
        labels = ResourceBundleUtil.getBundle("ch.randelshofer.pdf.Labels");
        JComponent contentPane = (JComponent) getContentPane();
        contentPane.setBorder(new LineBorder(new Color(0x999999)));
        contentPane.setBackground(Color.WHITE);
        progressView = new GlassPaneProgressView("EmbedPDF " + getAppletVersion(), labels.getString("applet.starting"));
        setGlassPane(progressView);
        progressView.setVisible(true);
    }

    /** Initializes the applet EmbedPDFApplet. Assumes that progressView is
     * showing on the glass pane.
     */
    @Override
    public void init() {
        progressView.setNote(labels.getString("applet.initializing"));
        String value;
        value= Applets.getParameter(this, "fonts");
        if (value!=null&&value.length()>0) {
            try {
                if (! value.endsWith("/")) value=value+"/";
               URL fontsURL= new URL(getCodeBase(),value);
               BuiltinFont.fontBaseURL=fontsURL;
            } catch (MalformedURLException ex) {
                progressView.setNote(null);
                progressView.setError("<html>" + htmlEncode(labels.getFormatted("applet.illegalParameter", "fonts", value)));
            }
        }
         value = Applets.getParameter(this, "pdf");
        if (value != null) {
            pdfURL = null;
            try {
                try {
                    pdfURL = new URL(getDocumentBase(), value);
                } catch (NullPointerException ex) {
                    pdfURL = new File(value).toURI().toURL();
                }
            } catch (MalformedURLException ex) {
                progressView.setNote(null);
                progressView.setError("<html>" + htmlEncode(labels.getFormatted("applet.illegalParameter", "pdf", pdfURL)));
            }
        }
        new Thread(this).start();
    }

    @Override
    public void start() {
    }

    /**
     * Reads the specified document using the current thread.
     * 
     * @param url
     */
    protected PDFFile readPDF(URL url, ProgressObserver p) throws IOException {
        try {
            // Try working with a file-cached byte buffer:
            tmpFile = File.createTempFile("Cached PDF File", ".pdf");
            tmpFile.deleteOnExit();
            FileOutputStream tmpOut = new FileOutputStream(tmpFile);

            URLConnection connection = url.openConnection();
            int contentLength = -1;
            try {
                contentLength = connection.getContentLength();
            } catch (AccessControlException ace) {
                // 
            }

            InputStream in = connection.getInputStream();
            try {
                if (contentLength != -1) {
                    BoundedRangeInputStream brin = new BoundedRangeInputStream(in);
                    brin.setMaximum(contentLength);
                    p.setModel(brin);
                    p.setIndeterminate(false);
                    in = brin;
                } else {
                    p.setIndeterminate(true);
                }

                {
                    p.setNote(labels.getString("applet.loadingPDF"));
                    byte[] buf = new byte[512];
                    int len;
                    while (true) {
                        len = in.read(buf);
                        if (len == -1) {
                            break;
                        }
                        tmpOut.write(buf, 0, len);
                    }
                    tmpOut.close();
                }
            } finally {
                in.close();
            }

            RandomAccessFile raf = new RandomAccessFile(tmpFile, "r");
            FileChannel channel = raf.getChannel();
            ByteBuffer bb = channel.map(FileChannel.MapMode.READ_ONLY,
                    0, channel.size());
            p.setNote(labels.getString("applet.readingPages"));
            p.setIndeterminate(true);
            return new PDFFile(bb);
        } catch (SecurityException e) {
            // Work with a memory-cached byte buffer:
            ByteArrayOutputStream tmpOut = new ByteArrayOutputStream();

            URLConnection connection = url.openConnection();
            int contentLength = -1;
            try {
                contentLength = connection.getContentLength();
            } catch (AccessControlException ace) {
                // 
            }

            InputStream in = connection.getInputStream();
            try {
                if (contentLength != -1) {
                    BoundedRangeInputStream brin = new BoundedRangeInputStream(in);
                    brin.setMaximum(contentLength);
                    p.setModel(brin);
                    p.setIndeterminate(false);
                    in = brin;
                } else {
                    p.setIndeterminate(true);
                }

                {
                    p.setNote(labels.getString("applet.loadingPDF"));
                    byte[] buf = new byte[1024];
                    int len;
                    while (true) {
                        len = in.read(buf);
                        if (len == -1) {
                            break;
                        }
                        tmpOut.write(buf, 0, len);
                    }
                    tmpOut.close();
                }
            } finally {
                in.close();
            }

            ByteBuffer bb = ByteBuffer.wrap(tmpOut.toByteArray(), 0, tmpOut.size());
            p.setNote(labels.getString("applet.readingPages"));
            p.setIndeterminate(true);
            return new PDFFile(bb);
        }
    }

    /** This method is called from within the init() method to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
    }// </editor-fold>//GEN-END:initComponents

    /** Runs in the background. Assumes that progressView is showing in the
     * glass pane. 
     */
    @Override
    public void run() {
        Thread worker = new Thread() {

            @Override
            public void run() {
                try {
                    SwingUtilities.invokeAndWait(new Runnable() {

                        @Override
                        public void run() {
                            pdfDocumentPanel = new JPDFFilePanel();
                            pdfDocumentPanel.setSubpixAAEnabled( //
                                    Applets.getParameter(EmbedPDFApplet.this, "enableSubpixelAA", false));
                            pdfDocumentPanel.setOpenWindowEnabled(//
                                    Applets.getParameter(EmbedPDFApplet.this, "enableOpenWindow", true));
                            try {
                                pdfDocumentPanel.setAppletContext(getAppletContext());
                            } catch (NullPointerException e) {
                                // don't set an applet context
                            }
                            pdfDocumentPanel.setDocumentURL(pdfURL);
                            pdfDocumentPanel.getPagePanel().addOpenURIListener(EmbedPDFApplet.this);
                            getContentPane().removeAll();
                            add(pdfDocumentPanel);

                            invalidate();
                            validate();
                            installDropTargets();
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        worker.start();

        if (pdfURL != null) {
            try {
                pdfFile = readPDF(pdfURL, progressView);
            } catch (IOException ex) {
                progressView.setNote(null);
                progressView.setError("<html>" + htmlEncode(labels.getFormatted("applet.couldntLoadPDF", ex.getLocalizedMessage())));
            }
        }

        try {
            worker.join();
        } catch (InterruptedException ex) {
            // nothing to do
        }
        SwingUtilities.invokeLater(new Runnable() {
        

            @Override
            public void run() {
                if (pdfFile != null) {
                    pdfDocumentPanel.setDocumentURL(pdfURL);
                    pdfDocumentPanel.setPDF(pdfFile);
                }

                // get rid of glass pane
                JPanel p = new JPanel();
                setGlassPane(p);
                p.setVisible(false);
            }
        });

    }

    protected void installDropTargets() {
    }

    private String htmlEncode(String str) {
        str = str.replace("&", "&amp;");
        str = str.replace("<", "&lt;");
        str = str.replace(">", "&gt;");
        str = str.replace("\n", "<br>");
        return str;
    }

    public String getAppletVersion() {
        return EmbedPDFApplet.class.getPackage().getImplementationVersion();
    }

    @Override
    public String getAppletInfo() {
        return "EmbedPDF " + getAppletVersion() + "\nCopyright 2008\n© Werner Randelshofer\nAll Rights reserved.";
    }

    @Override
    public String[][] getParameterInfo() {
        String pinfo[][] = {
            {"pdf", "url", "URL of the PDF document."},
            {"fonts", "url", "URL of the folder which contains the built-in PDF fonts."},
            {"enableSubpixelAA", "boolean", "Whether subpixel antialiasing is enabled. Default: false."},
            {"enableOpenWindow", "boolean", "Whether the PDF document may be opened in another window. Default: true."},};
        return pinfo;
    }

    public String[][] getAPIInfo() {
        String pinfo[][] = {
            {"setPDF(url):void", "Loads a PDF document from the specified URL."}//
        };
        return pinfo;
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    // End of variables declaration//GEN-END:variables

    @Override
    public void openURI(URI uri, String target) {
        try {
            getAppletContext().showDocument(uri.toURL(), target);
        } catch (MalformedURLException ex) {
            ex.printStackTrace();
        }
    }

    /** JavaScript API sets the URL for a PDF document. */
    public void setPDF(final String url) {
        setGlassPane(progressView);
        
        progressView.setProgress(0);
        progressView.setVisible(true);
        pdfDocumentPanel.setPDF(null);

        try {
            pdfURL = new URL(getDocumentBase(), url);
        } catch (MalformedURLException e) {
            progressView.setNote(null);
            progressView.setError(labels.format("applet.couldntLoadPDF", url));
            progressView.setIndeterminate(false);
            progressView.setProgress(progressView.getMaximum());
            e.printStackTrace();
            return;
        }


        final Worker<PDFFile> w = new Worker<PDFFile>() {

            @Override
            protected PDFFile construct() throws Exception {
                return readPDF(pdfURL, progressView);
            }

            @Override
            protected void done(PDFFile value) {
                pdfDocumentPanel.setPDF(value);
                progressView.setVisible(false);
            }

            @Override
            protected void failed(Throwable error) {
                progressView.setNote(null);
                progressView.setError(labels.format("applet.couldntLoadPDF", url));
                progressView.setIndeterminate(false);
                progressView.setProgress(progressView.getMaximum());
                System.err.println("EmbedPDF.setPDF(\"" + url + "\") full URL=" + pdfURL);
                error.printStackTrace();
                pdfDocumentPanel.setPDF(null);
            }
        };

        // For some reason, Java applets may not open a new URL connection
        // when called from JavaScript. We use doPrivileged as a workaround.
        AccessController.doPrivileged(new PrivilegedAction() {

            @Override
            public Object run() {
                w.start();
                return null;
            }
        });
    }
}
