/*
 * 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: StaticContentLayoutManager.java 808157 2009-08-26 18:50:10Z vhennebert $ */

package org.apache.fop.layoutmgr;

import java.util.LinkedList;
import java.util.List;

import org.apache.fop.area.Area;
import org.apache.fop.area.Block;
import org.apache.fop.area.RegionReference;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.pagination.PageSequence;
import org.apache.fop.fo.pagination.SideRegion;
import org.apache.fop.fo.pagination.StaticContent;
import org.apache.fop.layoutmgr.PageBreakingAlgorithm.PageBreakingLayoutListener;
import org.apache.fop.layoutmgr.inline.TextLayoutManager;

/**
 * LayoutManager for an fo:flow object.
 * Its parent LM is the PageSequenceLayoutManager.
 * This LM is responsible for getting columns of the appropriate size
 * and filling them with block-level areas generated by its children.
 */
public class StaticContentLayoutManager extends BlockStackingLayoutManager {

    private RegionReference targetRegion;
    private Block targetBlock;
    private SideRegion regionFO;

    private int contentAreaIPD = 0;
    private int contentAreaBPD = -1;

    /**
     * Creates a new StaticContentLayoutManager.
     * @param pslm PageSequenceLayoutManager this layout manager belongs to
     * @param node static-content FO
     * @param reg side region to layout into
     */
    public StaticContentLayoutManager(PageSequenceLayoutManager pslm,
            StaticContent node, SideRegion reg) {
        super(node);
        setParent(pslm);
        regionFO = reg;
        targetRegion = getCurrentPV().getRegionReference(regionFO.getNameId());
    }

    /**
     * Creates a new StaticContentLayoutManager.
     * @param pslm PageSequenceLayoutManager this layout manager belongs to
     * @param node static-content FO
     * @param block the block to layout into
     */
    public StaticContentLayoutManager(PageSequenceLayoutManager pslm,
            StaticContent node, Block block) {
        super(node);
        setParent(pslm);
        targetBlock = block;
    }

    /** {@inheritDoc} */
    public List getNextKnuthElements(LayoutContext context, int alignment) {
        throw new IllegalStateException();
    }

    /**
     * {@inheritDoc}
     */
    public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
        AreaAdditionUtil.addAreas(this, parentIter, layoutContext);

        flush();
        targetRegion = null;
    }


    /**
     * Add child area to a the correct container, depending on its
     * area class. A Flow can fill at most one area container of any class
     * at any one time. The actual work is done by BlockStackingLM.
     * {@inheritDoc}
     */
    public void addChildArea(Area childArea) {
        if (getStaticContentFO().getFlowName().equals("xsl-footnote-separator")) {
            targetBlock.addBlock((Block)childArea);
        } else {
            targetRegion.addBlock((Block)childArea);
        }
    }

    /**
     * {@inheritDoc}
     */
    public Area getParentArea(Area childArea) {
        if (getStaticContentFO().getFlowName().equals("xsl-footnote-separator")) {
            return targetBlock;
        } else {
            return targetRegion;
        }
    }

    /**
     * Does the layout for a side region. Called by PageSequenceLayoutManager.
     */
    public void doLayout() {
        int targetIPD = 0;
        int targetBPD = 0;
        int targetAlign = EN_AUTO;
        boolean autoHeight = false;
        StaticContentBreaker breaker;

        if (getStaticContentFO().getFlowName().equals("xsl-footnote-separator")) {
            targetIPD = targetBlock.getIPD();
            targetBPD = targetBlock.getBPD();
            if (targetBPD == 0) {
                autoHeight = true;
            }
            targetAlign = EN_BEFORE;
        } else {
            targetIPD = targetRegion.getIPD();
            targetBPD = targetRegion.getBPD();
            targetAlign = regionFO.getDisplayAlign();
        }
        setContentAreaIPD(targetIPD);
        setContentAreaBPD(targetBPD);
        breaker = new StaticContentBreaker(this, targetIPD, targetAlign);
        breaker.doLayout(targetBPD, autoHeight);
        if (breaker.isOverflow()) {
            if (!autoHeight) {
                String page = getPSLM().getCurrentPage().getPageViewport().getPageNumberString();

                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
                        getStaticContentFO().getUserAgent().getEventBroadcaster());
                boolean canRecover = (regionFO.getOverflow() != EN_ERROR_IF_OVERFLOW);
                boolean needClip = (regionFO.getOverflow() == Constants.EN_HIDDEN
                        || regionFO.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW);
                eventProducer.regionOverflow(this, regionFO.getName(),
                        page,
                        breaker.getOverflowAmount(), needClip, canRecover,
                        getStaticContentFO().getLocator());
            }
        }
    }

    /**
     * Convenience method that returns the Static Content node.
     * @return the static content node
     */
    protected StaticContent getStaticContentFO() {
        return (StaticContent) fobj;
    }

    private class StaticContentBreaker extends AbstractBreaker {
        private StaticContentLayoutManager lm;
        private int displayAlign;
        private int ipd;
        private int overflow = 0;

        public StaticContentBreaker(StaticContentLayoutManager lm, int ipd,
                int displayAlign) {
            this.lm = lm;
            this.ipd = ipd;
            this.displayAlign = displayAlign;
        }

        /** {@inheritDoc} */
        protected void observeElementList(List elementList) {
            String elementListID = getStaticContentFO().getFlowName();
            String pageSequenceID = ((PageSequence)lm.getParent().getFObj()).getId();
            if (pageSequenceID != null && pageSequenceID.length() > 0) {
                elementListID += "-" + pageSequenceID;
            }
            ElementListObserver.observe(elementList, "static-content", elementListID);
        }

        /** {@inheritDoc} */
        protected boolean isPartOverflowRecoveryActivated() {
            //For side regions, this must be disabled because of wanted overflow.
            return false;
        }

        public boolean isOverflow() {
            return (this.overflow != 0);
        }

        public int getOverflowAmount() {
            return this.overflow;
        }

        /** {@inheritDoc} */
        protected PageBreakingLayoutListener createLayoutListener() {
            return new PageBreakingLayoutListener() {

                public void notifyOverflow(int part, int amount, FObj obj) {
                    if (StaticContentBreaker.this.overflow == 0) {
                        StaticContentBreaker.this.overflow = amount;
                    }
                }

            };
        }

        protected LayoutManager getTopLevelLM() {
            return lm;
        }

        protected LayoutContext createLayoutContext() {
            LayoutContext lc = super.createLayoutContext();
            lc.setRefIPD(ipd);
            return lc;
        }

        protected List getNextKnuthElements(LayoutContext context, int alignment) {
            LayoutManager curLM; // currently active LM
            List returnList = new LinkedList();

            while ((curLM = getChildLM()) != null) {
                LayoutContext childLC = new LayoutContext(0);
                childLC.setStackLimitBP(context.getStackLimitBP());
                childLC.setRefIPD(context.getRefIPD());
                childLC.setWritingMode(context.getWritingMode());

                List returnedList = null;
                //The following is a HACK! Ignore leading and trailing white space
                boolean ignore = curLM instanceof TextLayoutManager;
                if (!curLM.isFinished()) {
                    returnedList = curLM.getNextKnuthElements(childLC, alignment);
                }
                if (returnedList != null && !ignore) {
                    lm.wrapPositionElements(returnedList, returnList);
                }
            }
            SpaceResolver.resolveElementList(returnList);
            setFinished(true);
            return returnList;
        }

        protected int getCurrentDisplayAlign() {
            return displayAlign;
        }

        protected boolean hasMoreContent() {
            return !lm.isFinished();
        }

        protected void addAreas(PositionIterator posIter, LayoutContext context) {
            AreaAdditionUtil.addAreas(lm, posIter, context);
        }

        protected void doPhase3(PageBreakingAlgorithm alg, int partCount,
                BlockSequence originalList, BlockSequence effectiveList) {
            if (partCount > 1) {
                PageBreakPosition pos = (PageBreakPosition)alg.getPageBreaks().getFirst();
                int firstPartLength = ElementListUtils.calcContentLength(effectiveList,
                        effectiveList.ignoreAtStart, pos.getLeafPos());
                overflow += alg.totalWidth - firstPartLength;
            }
            //Rendering all parts (not just the first) at once for the case where the parts that
            //overflow should be visible.
            alg.removeAllPageBreaks();
            //Directly add areas after finding the breaks
            this.addAreas(alg, 1, originalList, effectiveList);
        }

        protected void finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp) {
            //nop for static content
        }

        protected LayoutManager getCurrentChildLM() {
            return null; //TODO NYI
        }
    }

    /**
     * Returns the IPD of the content area
     * @return the IPD of the content area
     */
    public int getContentAreaIPD() {
        return contentAreaIPD;
    }

    /** {@inheritDoc} */
    protected void setContentAreaIPD(int contentAreaIPD) {
        this.contentAreaIPD = contentAreaIPD;
    }

    /**
     * Returns the BPD of the content area
     * @return the BPD of the content area
     */
    public int getContentAreaBPD() {
        return contentAreaBPD;
    }

    private void setContentAreaBPD(int contentAreaBPD) {
        this.contentAreaBPD = contentAreaBPD;
    }

    /** {@inheritDoc} */
    public Keep getKeepTogether() {
        return Keep.KEEP_AUTO;
    }

    /** {@inheritDoc} */
    public Keep getKeepWithNext() {
        return Keep.KEEP_AUTO;
    }

    /** {@inheritDoc} */
    public Keep getKeepWithPrevious() {
        return Keep.KEEP_AUTO;
    }

}

