/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id$ */

package org.apache.fop.render.awt;

/*
 * originally contributed by
 * Juergen Verwohlt: Juergen.Verwohlt@jCatalog.com,
 * Rainer Steinkuhle: Rainer.Steinkuhle@jCatalog.com,
 * Stanislav Gorkhover: Stanislav.Gorkhover@jCatalog.com
 */

// Java
import java.awt.Color;
import java.awt.Dimension;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.Pageable;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.io.IOException;

import org.apache.xmlgraphics.util.UnitConv;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.FopFactoryConfig;
import org.apache.fop.apps.MimeConstants;
import org.apache.fop.area.Area;
import org.apache.fop.area.PageViewport;
import org.apache.fop.render.awt.viewer.PreviewDialog;
import org.apache.fop.render.awt.viewer.Renderable;
import org.apache.fop.render.awt.viewer.StatusListener;
import org.apache.fop.render.extensions.prepress.PageScale;
import org.apache.fop.render.java2d.Java2DRenderer;

/**
 * The AWTRender outputs the pages generated by the layout engine to a Swing
 * window. This Swing window serves as default viewer for the -awt switch and as
 * an example of how to embed the AWTRenderer into an AWT/Swing application.
 */
public class AWTRenderer extends Java2DRenderer implements Pageable {

    /** The MIME type for AWT-Rendering */
    public static final String MIME_TYPE = MimeConstants.MIME_FOP_AWT_PREVIEW;

    /** flag for debugging */
    public boolean debug;

    /**
     * Will be notified when rendering progresses
     */
    protected StatusListener statusListener;


    /**
     * Creates a new AWTRenderer instance.
     *
     * @param userAgent the user agent that contains configuration data
     */
    public AWTRenderer(FOUserAgent userAgent) {
        this(userAgent, null, false, false);
    }

    /**
     * Creates a new AWTRenderer instance.
     *
     * @param userAgent the user agent that contains configuration data
     * @param renderable a Renderable instance can be set so the Preview Dialog can enable the
     * "Reload" button which causes the current document to be reprocessed and redisplayed.
     * @param previewAsMainWindow true if the preview dialog created by the renderer should be
     * the main window of the application.
     * @param show sets whether the preview dialog should be created and displayed when the
     * rendering has finished.
     */
    public AWTRenderer(FOUserAgent userAgent, Renderable renderable, boolean previewAsMainWindow,
            boolean show) {
        super(userAgent);
        if (show) {
            // MH: Not sure about this??? If show is false, there's no way for this class
            // to create a preview dialog... Previously a "setUserAgent" could be called.
            setStatusListener(PreviewDialog.createPreviewDialog(userAgent, renderable,
                    previewAsMainWindow));
        }
    }

    /**
     * {@inheritDoc}
     * @throws FOPException thrown by java2DRenderer
     */
    public void renderPage(PageViewport pageViewport) throws IOException, FOPException {

        super.renderPage(pageViewport);
        if (statusListener != null) {
            statusListener.notifyPageRendered();
        }
    }

    /** {@inheritDoc} */
    public void stopRenderer() throws IOException {
        super.stopRenderer();
        if (statusListener != null) {
            statusListener.notifyRendererStopped(); // Refreshes view of page
        }
    }

    /**
     * @return the dimensions of the specified page
     * @param pageNum the page number
     * @exception FOPException If the page is out of range or has not been rendered.
     */
    public Dimension getPageImageSize(int pageNum) throws FOPException {
        Rectangle2D bounds = getPageViewport(pageNum).getViewArea();
        pageWidth = (int) Math.round(bounds.getWidth() / 1000f);
        pageHeight = (int) Math.round(bounds.getHeight() / 1000f);
        double scaleX = scaleFactor
                * (UnitConv.IN2MM / FopFactoryConfig.DEFAULT_TARGET_RESOLUTION)
                / userAgent.getTargetPixelUnitToMillimeter();
        double scaleY = scaleFactor
                * (UnitConv.IN2MM / FopFactoryConfig.DEFAULT_TARGET_RESOLUTION)
                / userAgent.getTargetPixelUnitToMillimeter();
        if (getPageViewport(pageNum).getForeignAttributes() != null) {
            String scale = getPageViewport(pageNum).getForeignAttributes().get(
                    PageScale.EXT_PAGE_SCALE);
            Point2D scales = PageScale.getScale(scale);
            if (scales != null) {
                scaleX *= scales.getX();
                scaleY *= scales.getY();
            }
        }
        int bitmapWidth = (int) ((pageWidth * scaleX) + 0.5);
        int bitmapHeight = (int) ((pageHeight * scaleY) + 0.5);
        return new Dimension(bitmapWidth, bitmapHeight);
    }

    /** {@inheritDoc} */
    public PageFormat getPageFormat(int pageIndex)
            throws IndexOutOfBoundsException {
        try {
            if (pageIndex >= getNumberOfPages()) {
                return null;
            }

            PageFormat pageFormat = new PageFormat();

            Paper paper = new Paper();

            Rectangle2D dim = getPageViewport(pageIndex).getViewArea();
            double width = dim.getWidth();
            double height = dim.getHeight();

            // if the width is greater than the height assume lanscape mode
            // and swap the width and height values in the paper format
            if (width > height) {
                paper.setImageableArea(0, 0, height / 1000d, width / 1000d);
                paper.setSize(height / 1000d, width / 1000d);
                pageFormat.setOrientation(PageFormat.LANDSCAPE);
            } else {
                paper.setImageableArea(0, 0, width / 1000d, height / 1000d);
                paper.setSize(width / 1000d, height / 1000d);
                pageFormat.setOrientation(PageFormat.PORTRAIT);
            }
            pageFormat.setPaper(paper);
            return pageFormat;
        } catch (FOPException fopEx) {
            throw new IndexOutOfBoundsException(fopEx.getMessage());
        }
    }

    /** {@inheritDoc} */
    public Printable getPrintable(int pageIndex)
            throws IndexOutOfBoundsException {
        return this;
    }

    /** {@inheritDoc} */
    public boolean supportsOutOfOrder() {
        return false;
    }

    /** {@inheritDoc} */
    public String getMimeType() {
        return MIME_TYPE;
    }

    /**
     * Draws the background and borders and adds a basic debug view // TODO
     * implement visual-debugging as standalone
     *
     * {@inheritDoc}
     * float, float, float, float)
     *
     * @param area the area to get the traits from
     * @param startx the start x position
     * @param starty the start y position
     * @param width the width of the area
     * @param height the height of the area
     */
    protected void drawBackAndBorders(Area area, float startx, float starty,
            float width, float height) {

        if (debug) {
            debugBackAndBorders(area, startx, starty, width, height);
        }

        super.drawBackAndBorders(area, startx, starty, width, height);
    }

    /** Draws a thin border around every area to help debugging */
    private void debugBackAndBorders(Area area, float startx, float starty,
            float width, float height) {

        // saves the graphics state in a stack
        saveGraphicsState();

        Color col = new Color(0.7f, 0.7f, 0.7f);
        state.updateColor(col);
        state.updateStroke(0.4f, EN_SOLID);
        state.getGraph().draw(
                new Rectangle2D.Float(startx, starty, width, height));

        // restores the last graphics state from the stack
        restoreGraphicsState();
    }

    /** @return the StatusListener. */
    public StatusListener getStatusListener() {
        return statusListener;
    }

    /**
     * Sets a StatusListener this renderer uses to notify about events.
     * @param statusListener The StatusListener to set.
     */
    public void setStatusListener(StatusListener statusListener) {
        this.statusListener = statusListener;
    }

}
