/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.apps.svgbrowser;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Locale;
import java.util.ResourceBundle;

import javax.swing.JDialog;
import javax.swing.event.MouseInputAdapter;

import org.apache.batik.bridge.ViewBox;
import org.apache.batik.gvt.CanvasGraphicsNode;
import org.apache.batik.gvt.CompositeGraphicsNode;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.apache.batik.swing.gvt.JGVTComponent;
import org.apache.batik.swing.gvt.Overlay;
import org.apache.batik.swing.svg.SVGDocumentLoaderAdapter;
import org.apache.batik.swing.svg.SVGDocumentLoaderEvent;
import org.apache.batik.util.gui.resource.ResourceManager;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGSVGElement;

/**
 * This class represents a Dialog that displays a Thumbnail of the current SVG
 * document.
 *
 * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
 * @version $Id: ThumbnailDialog.java,v 1.10 2003/04/11 13:54:38 vhardy Exp $
 */
public class ThumbnailDialog extends JDialog {

    /**
     * The resource file name
     */
    protected final static String RESOURCES =
        "org.apache.batik.apps.svgbrowser.resources.ThumbnailDialog";

    /**
     * The resource bundle
     */
    protected static ResourceBundle bundle;

    /**
     * The resource manager
     */
    protected static ResourceManager resources;

    static {
        bundle = ResourceBundle.getBundle(RESOURCES, Locale.getDefault());
        resources = new ResourceManager(bundle);
    }

    /** The canvas that owns the SVG document to display. */
    protected JSVGCanvas svgCanvas;

    /** The canvas that displays the thumbnail. */
    protected JGVTComponent svgThumbnailCanvas;

    /** A flag bit that indicates a document has been loaded. */
    protected boolean documentChanged;

    /** The overlay used to display the area of interest. */
    protected AreaOfInterestOverlay overlay;

    /**
     * Constructs a new <tt>ThumbnailDialog</tt> for the specified canvas.
     *
     * @param canvas the canvas that owns the SVG document to display
     */
    public ThumbnailDialog(Frame owner, JSVGCanvas svgCanvas) {
        super(owner, resources.getString("Dialog.title"));

        addWindowListener(new ThumbnailListener());

        // register listeners to maintain consistency
        this.svgCanvas = svgCanvas;
        svgCanvas.addGVTTreeRendererListener(new ThumbnailGVTListener());
        svgCanvas.addSVGDocumentLoaderListener(new ThumbnailDocumentListener());        svgCanvas.addComponentListener(new ThumbnailCanvasComponentListener());


        // create the thumbnail
        svgThumbnailCanvas = new JGVTComponent();
        overlay = new AreaOfInterestOverlay();
        svgThumbnailCanvas.getOverlays().add(overlay);
        svgThumbnailCanvas.setPreferredSize(new Dimension(150, 150));
        svgThumbnailCanvas.addComponentListener(new ThumbnailComponentListener());
        AreaOfInterestListener listener = new AreaOfInterestListener();
        svgThumbnailCanvas.addMouseListener(listener);
        svgThumbnailCanvas.addMouseMotionListener(listener);
        getContentPane().add(svgThumbnailCanvas, BorderLayout.CENTER);
    }

    /**
     * Updates the thumbnail component.
     */
    protected void updateThumbnailGraphicsNode() {
        svgThumbnailCanvas.setGraphicsNode(svgCanvas.getGraphicsNode());
        updateThumbnailRenderingTransform();
    }

    protected CanvasGraphicsNode getCanvasGraphicsNode(GraphicsNode gn) {
        if (!(gn instanceof CompositeGraphicsNode))
            return null;
        CompositeGraphicsNode cgn = (CompositeGraphicsNode)gn;
        gn = (GraphicsNode)cgn.getChildren().get(0);
        if (!(gn instanceof CanvasGraphicsNode))
            return null;
        return (CanvasGraphicsNode)gn;
    }

    /**
     * Updates the thumbnail component rendering transform.
     */
    protected void updateThumbnailRenderingTransform() {
        SVGDocument svgDocument = svgCanvas.getSVGDocument();
        if (svgDocument != null) {
            SVGSVGElement elt = svgDocument.getRootElement();
            Dimension dim = svgThumbnailCanvas.getSize();

            AffineTransform Tx
                = ViewBox.getViewTransform(null, elt, dim.width, dim.height);
            if (Tx.isIdentity()) {
                // no viewBox has been specified, create a scale transform
                Dimension2D docSize = svgCanvas.getSVGDocumentSize();
                double sx = dim.width / docSize.getWidth();
                double sy = dim.height / docSize.getHeight();
                double s = Math.min(sx, sy);
                Tx = AffineTransform.getScaleInstance(s, s);
            }

            GraphicsNode gn = svgCanvas.getGraphicsNode();
            CanvasGraphicsNode cgn = getCanvasGraphicsNode(gn);
            AffineTransform vTx = cgn.getViewingTransform();
            if ((vTx != null) && !vTx.isIdentity()) {
                try {
                    AffineTransform invVTx = vTx.createInverse();
                    Tx.concatenate(invVTx);
                } catch (NoninvertibleTransformException nite) {
                    /* nothing */
                }
            }

            svgThumbnailCanvas.setRenderingTransform(Tx);
            overlay.synchronizeAreaOfInterest();
        }
    }

    /**
     * Used to determine whether or not the GVT tree of the thumbnail has to be
     * updated.
     */
    protected class ThumbnailDocumentListener extends SVGDocumentLoaderAdapter {

        public void documentLoadingStarted(SVGDocumentLoaderEvent e) {
            documentChanged = true;
        }
    }

    /**
     * Used to perform a translation using the area of interest.
     */
    protected class AreaOfInterestListener extends MouseInputAdapter {

        protected int sx, sy;
        protected boolean in;

        public void mousePressed(MouseEvent evt) {
            sx = evt.getX();
            sy = evt.getY();
            in = overlay.contains(sx, sy);
            overlay.setPaintingTransform(new AffineTransform());
        }

        public void mouseDragged(MouseEvent evt) {
            if (in) {
                int dx = evt.getX() - sx;
                int dy = evt.getY() - sy;
                overlay.setPaintingTransform
                    (AffineTransform.getTranslateInstance(dx, dy));
                svgThumbnailCanvas.repaint();
            }
        }

        public void mouseReleased(MouseEvent evt) {
            if (in) {
                in = false;

                int dx = evt.getX() - sx;
                int dy = evt.getY() - sy;
                AffineTransform at = overlay.getOverlayTransform();
                Point2D pt0 = new Point2D.Float(0, 0);
                Point2D pt = new Point2D.Float(dx, dy);

                try {
                    at.inverseTransform(pt0, pt0);
                    at.inverseTransform(pt, pt);
                    double tx = pt0.getX() - pt.getX();
                    double ty = pt0.getY() - pt.getY();
                    at = svgCanvas.getRenderingTransform();
                    at.preConcatenate
                        (AffineTransform.getTranslateInstance(tx, ty));
                    svgCanvas.setRenderingTransform(at);
                } catch (NoninvertibleTransformException ex) { }
            }
        }
    }

    /**
     * Used to update the overlay and/or the GVT tree of the thumbnail.
     */
    protected class ThumbnailGVTListener extends GVTTreeRendererAdapter {

        public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
            if (documentChanged) {
                updateThumbnailGraphicsNode();
                documentChanged = false;
            } else {
                overlay.synchronizeAreaOfInterest();
                svgThumbnailCanvas.repaint();
            }
        }

        public void gvtRenderingCancelled(GVTTreeRendererEvent e) {
	    if (documentChanged) {
		svgThumbnailCanvas.setGraphicsNode(null);
		svgThumbnailCanvas.setRenderingTransform(new AffineTransform());
	    }
        }

        public void gvtRenderingFailed(GVTTreeRendererEvent e) {
	    if (documentChanged) {
		svgThumbnailCanvas.setGraphicsNode(null);
		svgThumbnailCanvas.setRenderingTransform(new AffineTransform());
	    }
	}
    }

    /**
     * Used the first time the thumbnail dialog is shown to make visible the
     * current GVT tree being displayed by the original SVG component.
     */
    protected class ThumbnailListener extends WindowAdapter {

        public void windowOpened(WindowEvent evt) {
            updateThumbnailGraphicsNode();
        }
    }

    /**
     * Used to allow the SVG document being displayed by the thumbnail to be
     * resized properly.
     */
    protected class ThumbnailComponentListener extends ComponentAdapter {

        public void componentResized(ComponentEvent e) {
            updateThumbnailRenderingTransform();
        }
    }

    /**
     * Used to allow the SVG document being displayed by the thumbnail to be
     * resized properly when parent resizes.
     */
    protected class ThumbnailCanvasComponentListener extends ComponentAdapter {

        public void componentResized(ComponentEvent e) {
            updateThumbnailRenderingTransform();
        }
    }

    /**
     * An overlay that represents the current area of interest.
     */
    protected class AreaOfInterestOverlay implements Overlay {

        protected Shape s;
        protected AffineTransform at;
        protected AffineTransform paintingTransform = new AffineTransform();

        public boolean contains(int x, int y) {
            return (s != null) ? s.contains(x, y) : false;
        }

        public AffineTransform getOverlayTransform() {
            return at;
        }

        public void setPaintingTransform(AffineTransform rt) {
            this.paintingTransform = rt;
        }

        public AffineTransform getPaintingTransform() {
            return paintingTransform;
        }

        public void synchronizeAreaOfInterest() {
            paintingTransform = new AffineTransform();
            Dimension dim = svgCanvas.getSize();
            s = new Rectangle2D.Float(0, 0, dim.width, dim.height);
            try {
		at = svgCanvas.getRenderingTransform().createInverse();
		at.preConcatenate(svgThumbnailCanvas.getRenderingTransform());
                s = at.createTransformedShape(s);
            } catch (NoninvertibleTransformException ex) {
                dim = svgThumbnailCanvas.getSize();
                s = new Rectangle2D.Float(0, 0, dim.width, dim.height);
            }
        }

        public void paint(Graphics g) {
            if (s != null) {
                Graphics2D g2d = (Graphics2D)g;
                g2d.transform(paintingTransform);
                g2d.setColor(new Color(255, 255, 255, 128));
                g2d.fill(s);
                g2d.setColor(Color.black);
                g2d.setStroke(new BasicStroke());
                g2d.draw(s);
            }
        }
    }
}
