// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.print;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;

import javax.swing.JPanel;
import javax.swing.JViewport;
import javax.swing.RepaintManager;

import org.openstreetmap.josm.tools.Logging;

/**
 * A generic print preview component
 *
 * Given a Printable, a PageFormat and a zoom factor, this component
 * will render a scrollable and zoomable print preview. If no Printable
 * is defined, it will fill the printable area of the page format in
 * plain gray.
 *
 * There is a special zoom-to-page condition where the component will
 * automatically adjust the zoom level such that the whole page preview
 * will just fit into the size of the component.
 * @author Kai Pastor
 */
class PrintPreview extends JPanel {

    /**
     * The PageFormat chosen for printing (and preview)
     */
    protected transient PageFormat format;

    /**
     * The current zoom factor for the preview
     */
    protected double zoom = 1.0;

    /**
     * If true, the preview is always zoomed such that
     * the whole page fits into the preview area.
     */
    protected boolean zoomToPage = true;

    /**
     * When this flag is true, no painting operations will be performed.
     */
    protected boolean paintingDisabled;

    /**
     * the printable object for rendering preview contents
     */
    protected transient Printable printable;

    /**
     * Constructs a new preview component
     */
    PrintPreview() {
        super();
    }

    /**
     * Constructs a new preview component for a printable
     *
     * @param p the printable object for rendering preview contents, or null
     */
    PrintPreview(Printable p) {
        super();
        printable = p;
    }

    /**
     * Sets (and unsets) the printable object
     *
     * @param p a printable object, or null
     */
    public void setPrintable(Printable p) {
        printable = p;
        repaint();
    }

    /**
     * Sets the page format
     *
     * @param format the PageFormat chosen for printing (and preview)
     */
    public void setPageFormat(PageFormat format) {
        this.format = format;
        revalidate();
        repaint();
    }

    /**
     * Sets the preview zoom relativ to the actual size
     *
     * @param zoom the zoom factor
     */
    public void setZoom(double zoom) {
        setZoom(zoom, false);
    }

    /**
     * Zoom into the preview
     *
     * Doubles the current zoom factor, if smaller than 5.0.
     */
    public void zoomIn() {
        double z = getZoom();
        if (z < 5.0) {
            setZoom(2.0 * z);
        }
    }

    /**
     * Zoom out of the preview
     *
     * Set the zoom factor to half its current value, if bigger than 0.1.
     */
    public void zoomOut() {
        double z = getZoom();
        if (z > 0.1) {
            setZoom(0.5 * z);
        }
    }

    /**
     * Zoom to fit the page size
     *
     * Set the zoom factor such that the whole page fits into the preview area.
     */
    public void zoomToPage() {
        if (format == null)
            return;
        Container parent = getParent();
        Dimension dim;
        if (parent instanceof JViewport) {
            dim = getParent().getSize(); // could get rid of scrollbars
        } else {
            dim = getVisibleRect().getSize();
        }
        int resolution = Toolkit.getDefaultToolkit().getScreenResolution();
        double widthZoom = 72.0 * dim.getWidth() / resolution / format.getWidth();
        double heightZoom = 72.0 * dim.getHeight() / resolution / format.getHeight();
        setZoom(Math.min(widthZoom, heightZoom), true);
    }

    /**
     * Sets the preview zoom relativ to the actual size
     *
     * @param zoom the zoom factor
     * @param zoomToPage when set to true, the zoom factor will be
     *   adjusted on resize operations such that the whole page always
     *   fits to the preview area.
     */
    protected void setZoom(double zoom, boolean zoomToPage) {
        if (format == null || zoom == this.zoom) {
            return;
        }

        Dimension oldDim = getZoomedPageDimension();
        Rectangle oldView = getVisibleRect();

        paintingDisabled = true; // avoid flickering during zooming

        this.zoom = zoom;
        this.zoomToPage = zoomToPage;
        revalidate(); // Make JScrollPane aware of the change in size
        RepaintManager.currentManager(this).validateInvalidComponents();

        Dimension dim = getZoomedPageDimension();
        Rectangle view = getVisibleRect();

        view.x = (int) ((0.5*oldView.width+oldView.x) /
                Math.max(oldView.width, oldDim.width)*Math.max(view.width, dim.width)-0.5*view.width);
        view.y = (int) ((0.5*oldView.height+oldView.y) /
                Math.max(oldView.height, oldDim.height)*Math.max(view.height, dim.height)-0.5*view.height);

        scrollRectToVisible(view);
        paintingDisabled = false;
        repaint(view); // repaint now when zooming is completed
    }

    /**
     * Returns the actual current zoom
     *
     * This factor might be different from the last explicitly set factor
     * when zoomToPage is true.
     *
     * @return the zoom factor
     */
    public double getZoom() {
        if (format != null && (zoomToPage || zoom < 0.01)) {
            // actually this is zoom-to-page
            Dimension dim = getParent().getSize();
            int resolution = Toolkit.getDefaultToolkit().getScreenResolution();
            double widthZoom = 72.0 * dim.getWidth() / resolution / format.getWidth();
            double heightZoom = 72.0 * dim.getHeight() / resolution / format.getHeight();
            return Math.min(widthZoom, heightZoom);
        }
        return zoom;
    }

    /**
     * Get the current dimension of the page preview
     *
     * @return dimension in pixels
     */
    public Dimension getZoomedPageDimension() {
        int resolution = Toolkit.getDefaultToolkit().getScreenResolution();
        double z = getZoom();
        int width = (int) (z * resolution * format.getWidth() / 72.0);
        int height = (int) (z * resolution * format.getHeight() / 72.0);
        return new Dimension(width, height);
    }

    /**
     * Get the current preferred size of the component
     *
     * @return indicates a zero size when zoomToPage is active,
     *   the zoomed page dimension otherwise
     */
    @Override
    public Dimension getPreferredSize() {
        if (format == null || zoomToPage || zoom < 0.01) {
            return new Dimension(0, 0);
        }
        return getZoomedPageDimension();
    }

    /**
     * Paints the component
     *
     * This will do nothing, not even call super.paintComponent(g), if
     * painting is disabled in this component.
     *
     * @param g a Graphics to draw to
     */
    @Override
    public void paintComponent(Graphics g) {
        if (paintingDisabled) {
            return;
        }
        super.paintComponent(g);
        if (format == null) {
            return;
        }

        Graphics2D g2d = (Graphics2D) g;
        AffineTransform at = g2d.getTransform();

        Dimension out = getZoomedPageDimension();
        double scale = Math.min(
          out.getHeight()/format.getHeight(), out.getWidth()/format.getWidth());
        double left = 0.5 * (getWidth() - scale * format.getWidth());
        double top = 0.5 * (getHeight() - scale * format.getHeight());

        g2d.translate(left, top);
        g2d.setColor(Color.black);
        g2d.drawRect(-1, -1, out.width+1, out.height+1);
        g2d.setColor(Color.white);
        g2d.fillRect(0, 0, out.width, out.height);

        g2d.scale(scale, scale);
        g2d.clip(new Rectangle2D.Double(
                format.getImageableX(),
                format.getImageableY(),
                format.getImageableWidth(),
                format.getImageableHeight()));
        if (printable != null) {
            try {
                printable.print(g2d, format, 0);
            } catch (PrinterException e) {
                // should never happen since we are not printing
                Logging.error(e);
            }
        } else {
            g2d.setColor(Color.gray);
            g2d.fillRect(0, 0, (int) format.getWidth(), (int) format.getHeight());
        }

        g2d.setTransform(at);
    }
}
