/*
 * 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: PageableRenderer.java 1761019 2016-09-16 10:43:45Z ssteiner $ */

package org.apache.fop.render.print;

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 java.util.Map;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.MimeConstants;
import org.apache.fop.area.PageViewport;
import org.apache.fop.render.java2d.Java2DRenderer;

/**
 * Renderer that prints through java.awt.PrintJob.
 * The actual printing is handled by Java2DRenderer
 * since both PrintRenderer and AWTRenderer need to
 * support printing.
 */
public class PageableRenderer extends Java2DRenderer implements Pageable {

    /**
     * Printing parameter: the pages to be printed (all, even or odd),
     * datatype: the strings "all", "even" or "odd" or one of PagesMode.*
     */
    public static final String PAGES_MODE = "even-odd";

    /**
     * Printing parameter: the page number (1-based) of the first page to be printed,
     * datatype: a positive Integer
     */
    public static final String START_PAGE = "start-page";

    /**
     * Printing parameter: the page number (1-based) of the last page to be printed,
     * datatype: a positive Integer
     */
    public static final String END_PAGE = "end-page";


    /** first valid page number (1-based) */
    protected int startNumber;
    /** last valid page number (1-based) */
    protected int endNumber = -1;

    /** indicates which pages are valid: odd, even or all */
    protected PagesMode mode = PagesMode.ALL;

    private PageFilter pageFilter;

    /**
     * Creates a new PageableRenderer.
     *
     * @param userAgent the user agent that contains configuration details. This cannot be null.
     */
    public PageableRenderer(FOUserAgent userAgent) {
        super(userAgent);
        Map rendererOptions = getUserAgent().getRendererOptions();
        processOptions(rendererOptions);
        this.pageFilter = new DefaultPageFilter();
    }

    /** {@inheritDoc} */
    public String getMimeType() {
        return MimeConstants.MIME_FOP_PRINT;
    }

    private void processOptions(Map rendererOptions) {
        Object o = rendererOptions.get(PageableRenderer.PAGES_MODE);
        if (o != null) {
            if (o instanceof PagesMode) {
                this.mode = (PagesMode)o;
            } else if (o instanceof String) {
                this.mode = PagesMode.byName((String)o);
            } else {
                throw new IllegalArgumentException(
                        "Renderer option " + PageableRenderer.PAGES_MODE
                        + " must be an 'all', 'even', 'odd' or a PagesMode instance.");
            }
        }

        o = rendererOptions.get(PageableRenderer.START_PAGE);
        if (o != null) {
            this.startNumber = getPositiveInteger(o);
        }
        o = rendererOptions.get(PageableRenderer.END_PAGE);
        if (o != null) {
            this.endNumber = getPositiveInteger(o);
        }
        if (this.endNumber >= 0 && this.endNumber < this.startNumber) {
            this.endNumber = this.startNumber;
        }
    }

    /**
     * Converts an object into a positive integer value if possible. The method throws an
     * {@link IllegalArgumentException} if the value is invalid.
     * @param o the object to be converted
     * @return the positive integer
     */
    protected int getPositiveInteger(Object o) {
        if (o instanceof Integer) {
            Integer i = (Integer)o;
            if (i < 1) {
                throw new IllegalArgumentException(
                        "Value must be a positive Integer");
            }
            return i;
        } else if (o instanceof String) {
            return Integer.parseInt((String)o);
        } else {
            throw new IllegalArgumentException(
                    "Value must be a positive integer");
        }
    }

    /** {@inheritDoc} */
    public void stopRenderer() throws IOException {
        super.stopRenderer();

        if (endNumber == -1) {
            // was not set on command line
            endNumber = getNumberOfPages();
        }
    }

    /** {@inheritDoc} */
    protected void rememberPage(PageViewport pageViewport) {
        if (this.pageFilter.isValid(pageViewport)) {
            super.rememberPage(pageViewport);
        }
    }

    private interface PageFilter {
        boolean isValid(PageViewport page);
    }

    private class DefaultPageFilter implements PageFilter {

        public boolean isValid(PageViewport page) {
            int pageNum = page.getPageIndex() + 1;
            assert pageNum >= 0;
            if (pageNum < startNumber || (endNumber >= 0 && pageNum > endNumber)) {
                return false;
            } else if (mode != PagesMode.ALL) {
                if (mode == PagesMode.EVEN && (pageNum % 2 != 0)) {
                    return false;
                } else if (mode == PagesMode.ODD && (pageNum % 2 == 0)) {
                    return false;
                }
            }
            return true;
        }
    }

    /** {@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 landscape 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;
    }
}
