/*
 * 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: ExternalDocumentLayoutManager.java 1531769 2013-10-13 23:31:46Z lbernardo $ */

package org.apache.fop.layoutmgr;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.image.loader.ImageException;
import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.xmlgraphics.image.loader.ImageManager;
import org.apache.xmlgraphics.image.loader.util.ImageUtil;

import org.apache.fop.ResourceEventProducer;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.area.AreaTreeHandler;
import org.apache.fop.area.Block;
import org.apache.fop.area.BodyRegion;
import org.apache.fop.area.CTM;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.PageSequence;
import org.apache.fop.area.PageViewport;
import org.apache.fop.area.RegionViewport;
import org.apache.fop.area.inline.Image;
import org.apache.fop.area.inline.InlineViewport;
import org.apache.fop.datatypes.FODimension;
import org.apache.fop.datatypes.URISpecification;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.extensions.ExternalDocument;
import org.apache.fop.layoutmgr.inline.ImageLayout;
import org.apache.fop.traits.WritingMode;

/**
 * LayoutManager for an external-document extension element.  This class is instantiated by
 * area.AreaTreeHandler for each fo:external-document found in the
 * input document.
 */
public class ExternalDocumentLayoutManager extends AbstractPageSequenceLayoutManager {

    private static Log log = LogFactory.getLog(ExternalDocumentLayoutManager.class);

    private ImageLayout imageLayout;

    /**
     * Constructor
     *
     * @param ath the area tree handler object
     * @param document fox:external-document to process
     */
    public ExternalDocumentLayoutManager(AreaTreeHandler ath, ExternalDocument document) {
        super(ath, document);
    }

    /**
     * @return the ExternalDocument being managed by this layout manager
     */
    protected ExternalDocument getExternalDocument() {
        return (ExternalDocument)pageSeq;
    }

    /** {@inheritDoc} */
    public PageSequenceLayoutManager getPSLM() {
        throw new IllegalStateException("getPSLM() is illegal for " + getClass().getName());
    }

    /** {@inheritDoc} */
    public void activateLayout() {
        initialize();

        FOUserAgent userAgent = pageSeq.getUserAgent();
        ImageManager imageManager = userAgent.getImageManager();

        String uri = URISpecification.getURL(getExternalDocument().getSrc());
        Integer firstPageIndex = ImageUtil.getPageIndexFromURI(uri);
        boolean hasPageIndex = (firstPageIndex != null);

        try {
            ImageInfo info = imageManager.getImageInfo(uri, userAgent.getImageSessionContext());

            Object moreImages = info.getCustomObjects().get(ImageInfo.HAS_MORE_IMAGES);
            boolean hasMoreImages = moreImages != null && !Boolean.FALSE.equals(moreImages);

            Dimension intrinsicSize = info.getSize().getDimensionMpt();
            ImageLayout layout = new ImageLayout(getExternalDocument(), this, intrinsicSize);

            PageSequence pageSequence = new PageSequence(null);
            transferExtensions(pageSequence);
            areaTreeHandler.getAreaTreeModel().startPageSequence(pageSequence);
            if (log.isDebugEnabled()) {
                log.debug("Starting layout");
            }

            makePageForImage(info, layout);

            if (!hasPageIndex && hasMoreImages) {
                if (log.isTraceEnabled()) {
                    log.trace("Starting multi-page processing...");
                }
                URI originalURI;
                try {
                    originalURI = new URI(URISpecification.escapeURI(uri));
                    int pageIndex = 1;
                    while (hasMoreImages) {
                        URI tempURI = new URI(originalURI.getScheme(),
                                originalURI.getSchemeSpecificPart(),
                                "page=" + Integer.toString(pageIndex + 1));
                        if (log.isTraceEnabled()) {
                            log.trace("Subimage: " + tempURI.toASCIIString());
                        }
                        ImageInfo subinfo = imageManager.getImageInfo(
                                tempURI.toASCIIString(), userAgent.getImageSessionContext());

                        moreImages = subinfo.getCustomObjects().get(ImageInfo.HAS_MORE_IMAGES);
                        hasMoreImages = moreImages != null && !Boolean.FALSE.equals(moreImages);

                        intrinsicSize = subinfo.getSize().getDimensionMpt();
                        layout = new ImageLayout(
                                getExternalDocument(), this, intrinsicSize);

                        makePageForImage(subinfo, layout);

                        pageIndex++;
                    }
                } catch (URISyntaxException e) {
                    getResourceEventProducer().uriError(this, uri, e,
                            getExternalDocument().getLocator());
                }
            }
        } catch (FileNotFoundException fnfe) {
            getResourceEventProducer().imageNotFound(this, uri, fnfe,
                    getExternalDocument().getLocator());
        } catch (IOException ioe) {
            getResourceEventProducer().imageIOError(this, uri, ioe,
                    getExternalDocument().getLocator());
        } catch (ImageException ie) {
            getResourceEventProducer().imageError(this, uri, ie,
                    getExternalDocument().getLocator());
        }
    }

    private ResourceEventProducer getResourceEventProducer() {
        return ResourceEventProducer.Provider.get(
                getExternalDocument().getUserAgent().getEventBroadcaster());
    }

    private void makePageForImage(ImageInfo info, ImageLayout layout) {
        this.imageLayout = layout;
        curPage = makeNewPage(false);
        fillPage(info.getOriginalURI());
        finishPage();
    }

    private void fillPage(String uri) {

        Dimension imageSize = this.imageLayout.getViewportSize();

        Block blockArea = new Block();
        blockArea.setIPD(imageSize.width);
        LineArea lineArea = new LineArea();

        Image imageArea = new Image(uri);
        TraitSetter.setProducerID(imageArea, fobj.getId());
        transferForeignAttributes(imageArea);

        InlineViewport vp = new InlineViewport(imageArea, fobj.getBidiLevel());
        TraitSetter.setProducerID(vp, fobj.getId());
        vp.setIPD(imageSize.width);
        vp.setBPD(imageSize.height);
        vp.setContentPosition(imageLayout.getPlacement());
        vp.setBlockProgressionOffset(0);

        //Link them all together...
        lineArea.addInlineArea(vp);
        lineArea.updateExtentsFromChildren();
        blockArea.addLineArea(lineArea);
        curPage.getPageViewport().getCurrentFlow().addBlock(blockArea);
        curPage.getPageViewport().getCurrentSpan().notifyFlowsFinished();
    }

    /** {@inheritDoc} */
    public void finishPageSequence() {
        if (pageSeq.hasId()) {
            idTracker.signalIDProcessed(pageSeq.getId());
        }

        pageSeq.getRoot().notifyPageSequenceFinished(currentPageNum,
                (currentPageNum - startPageNum) + 1);
        areaTreeHandler.notifyPageSequenceFinished(pageSeq,
                (currentPageNum - startPageNum) + 1);

        if (log.isDebugEnabled()) {
            log.debug("Ending layout");
        }
    }

    /** {@inheritDoc} */
    protected Page createPage(int pageNumber, boolean isBlank) {
        String pageNumberString = pageSeq.makeFormattedPageNumber(pageNumber);

        Dimension imageSize = this.imageLayout.getViewportSize();

        // Set up the CTM on the page reference area based on writing-mode
        // and reference-orientation
        Rectangle referenceRect;
        if (pageSeq.getReferenceOrientation() % 180 == 0) {
            referenceRect = new Rectangle(0, 0, imageSize.width, imageSize.height);
        } else {
            referenceRect = new Rectangle(0, 0, imageSize.height, imageSize.width);
        }
        FODimension reldims = new FODimension(0, 0);
        // [TBD] BIDI ALERT
        CTM pageCTM = CTM.getCTMandRelDims(pageSeq.getReferenceOrientation(),
                                           WritingMode.LR_TB, referenceRect, reldims);

        Page page = new Page(referenceRect, pageNumber, pageNumberString, isBlank);

        PageViewport pv = page.getPageViewport();
        org.apache.fop.area.Page pageArea = new org.apache.fop.area.Page();
        pv.setPage(pageArea);

        RegionViewport rv = new RegionViewport(referenceRect);

        if (pageSeq.getReferenceOrientation() % 180 == 0) {
            rv.setIPD(referenceRect.width);
            rv.setBPD(referenceRect.height);
        }  else {
            rv.setIPD(referenceRect.height);
            rv.setBPD(referenceRect.width);
        }
        rv.setClip(true);

        BodyRegion body = new BodyRegion(Constants.FO_REGION_BODY,
                "fop-image-region", rv, 1, 0);
        body.setIPD(imageSize.width);
        body.setBPD(imageSize.height);
        body.setCTM(pageCTM);
        rv.setRegionReference(body);
        pageArea.setRegionViewport(
                Constants.FO_REGION_BODY, rv);
        //Set unique key obtained from the AreaTreeHandler
        pv.setKey(areaTreeHandler.generatePageViewportKey());

        //Also creates first normal flow region
        pv.createSpan(false);

        return page;
    }

}
