/*
 * 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: BlockContainerLayoutManager.java 1877372 2020-05-05 08:12:02Z ssteiner $ */

package org.apache.fop.layoutmgr;

import java.awt.Point;
import java.awt.geom.Rectangle2D;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

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

import org.apache.fop.area.Area;
import org.apache.fop.area.Block;
import org.apache.fop.area.BlockViewport;
import org.apache.fop.area.CTM;
import org.apache.fop.area.Trait;
import org.apache.fop.datatypes.FODimension;
import org.apache.fop.datatypes.Length;
import org.apache.fop.fo.flow.BlockContainer;
import org.apache.fop.fo.properties.CommonAbsolutePosition;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.KeepProperty;
import org.apache.fop.traits.MinOptMax;
import org.apache.fop.traits.SpaceVal;

/**
 * LayoutManager for a block-container FO.
 */
public class BlockContainerLayoutManager extends SpacedBorderedPaddedBlockLayoutManager
        implements BreakOpportunity {

    /**
     * logging instance
     */
    private static Log log = LogFactory.getLog(BlockContainerLayoutManager.class);

    private BlockViewport viewportBlockArea;
    private Block referenceArea;

    private CommonAbsolutePosition abProps;
    private FODimension relDims;
    private CTM absoluteCTM;
    private Length width;
    private Length height;
    //private int vpContentIPD;
    private int vpContentBPD;

    // When viewport should grow with the content.
    private boolean autoHeight = true;
    private boolean inlineElementList;

    /* holds the (one-time use) fo:block space-before
    and -after properties.  Large fo:blocks are split
    into multiple Area.Blocks to accomodate the subsequent
    regions (pages) they are placed on.  space-before
    is applied at the beginning of the first
    Block and space-after at the end of the last Block
    used in rendering the fo:block.
    */
    //TODO space-before|after: handle space-resolution rules
    private MinOptMax foBlockSpaceBefore;
    private MinOptMax foBlockSpaceAfter;

    private int horizontalOverflow;
    private double contentRectOffsetX;
    private double contentRectOffsetY;

    /**
     * Create a new block container layout manager.
     * @param node block-container node to create the layout manager for.
     */
    public BlockContainerLayoutManager(BlockContainer node) {
        super(node);
        setGeneratesBlockArea(true);
    }

    /** {@inheritDoc} */
    @Override
    public void initialize() {
        abProps = getBlockContainerFO().getCommonAbsolutePosition();
        foBlockSpaceBefore = new SpaceVal(getBlockContainerFO().getCommonMarginBlock()
                    .spaceBefore, this).getSpace();
        foBlockSpaceAfter = new SpaceVal(getBlockContainerFO().getCommonMarginBlock()
                    .spaceAfter, this).getSpace();
        startIndent = getBlockContainerFO().getCommonMarginBlock().startIndent.getValue(this);
        endIndent = getBlockContainerFO().getCommonMarginBlock().endIndent.getValue(this);

        if (blockProgressionDirectionChanges()) {
            height = getBlockContainerFO().getInlineProgressionDimension()
                            .getOptimum(this).getLength();
            width = getBlockContainerFO().getBlockProgressionDimension()
                            .getOptimum(this).getLength();
        } else {
            height = getBlockContainerFO().getBlockProgressionDimension()
                            .getOptimum(this).getLength();
            width = getBlockContainerFO().getInlineProgressionDimension()
                            .getOptimum(this).getLength();
        }

        // use optimum space values
        adjustedSpaceBefore = getBlockContainerFO().getCommonMarginBlock()
            .spaceBefore.getSpace().getOptimum(this).getLength().getValue(this);
        adjustedSpaceAfter = getBlockContainerFO().getCommonMarginBlock()
            .spaceAfter.getSpace().getOptimum(this).getLength().getValue(this);
    }

    @Override
    protected CommonBorderPaddingBackground getCommonBorderPaddingBackground() {
        return getBlockContainerFO().getCommonBorderPaddingBackground();
    }

    private void resetSpaces() {
        this.discardBorderBefore = false;
        this.discardBorderAfter = false;
        this.discardPaddingBefore = false;
        this.discardPaddingAfter = false;
        this.effSpaceBefore = null;
        this.effSpaceAfter = null;
    }

    /** @return the content IPD */
    protected int getRotatedIPD() {
        return getBlockContainerFO().getInlineProgressionDimension()
                .getOptimum(this).getLength().getValue(this);
    }

    private boolean needClip() {
        int overflow = getBlockContainerFO().getOverflow();
        return (overflow == EN_HIDDEN || overflow == EN_ERROR_IF_OVERFLOW);
    }

    private int getBPIndents() {
        int indents = 0;
        /* TODO This is wrong isn't it?
        indents += getBlockContainerFO().getCommonMarginBlock()
                    .spaceBefore.getOptimum(this).getLength().getValue(this);
        indents += getBlockContainerFO().getCommonMarginBlock()
                    .spaceAfter.getOptimum(this).getLength().getValue(this);
        */
        indents += getBlockContainerFO().getCommonBorderPaddingBackground()
                    .getBPPaddingAndBorder(false, this);
        return indents;
    }

    protected boolean isAbsoluteOrFixed() {
        return (abProps.absolutePosition == EN_ABSOLUTE
                || abProps.absolutePosition == EN_FIXED);
    }

    private boolean isFixed() {
        return (abProps.absolutePosition == EN_FIXED);
    }

    /** {@inheritDoc} */
    @Override
    public int getContentAreaBPD() {
        if (autoHeight) {
            return -1;
        } else {
            return this.vpContentBPD;
        }
    }

    /** {@inheritDoc} */
    @Override
    public List getNextKnuthElements(LayoutContext context, int alignment) {
        return getNextKnuthElements(context, alignment, null, null, null);
    }

    /**
     * Overridden to handle writing-mode, and different stack limit
     * setup.
     * {@inheritDoc}
     */
    @Override
    protected LayoutContext makeChildLayoutContext(LayoutContext context) {
        LayoutContext childLC = LayoutContext.newInstance();
        childLC.setStackLimitBP(
                context.getStackLimitBP().minus(MinOptMax.getInstance(relDims.bpd)));
        childLC.setRefIPD(relDims.ipd);
        childLC.setWritingMode(getBlockContainerFO().getWritingMode());
        return childLC;
    }

    /** {@inheritDoc} */
    @Override
    public List getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack,
        Position restartPosition, LayoutManager restartAtLM) {

        resetSpaces();
        // special treatment for position="absolute|fixed"
        if (isAbsoluteOrFixed()) {
            return getNextKnuthElementsAbsolute(context);
        }

        boolean isRestart = (lmStack != null);
        boolean emptyStack = (!isRestart || lmStack.isEmpty());

        setupAreaDimensions(context);

        List<ListElement> returnedList;
        List<ListElement> contentList = new LinkedList<ListElement>();
        List<ListElement> returnList = new LinkedList<ListElement>();

        if (!breakBeforeServed(context, returnList)) {
            return returnList;
        }

        addFirstVisibleMarks(returnList, context, alignment);

        if (autoHeight && inlineElementList) {

            LayoutManager curLM; // currently active LM
            LayoutManager prevLM = null; // previously active LM

            LayoutContext childLC;
            if (isRestart) {
                if (emptyStack) {
                    assert restartAtLM != null && restartAtLM.getParent() == this;
                    curLM = restartAtLM;
                } else {
                    curLM = (LayoutManager) lmStack.pop();
                }
                setCurrentChildLM(curLM);
            } else {
                curLM = getChildLM();
            }

            while (curLM != null) {
                childLC = makeChildLayoutContext(context);

                // get elements from curLM
                if (!isRestart || emptyStack) {
                    if (isRestart) {
                        curLM.reset();
                    }
                    returnedList = getNextChildElements(curLM, context, childLC, alignment,
                            null, null, null);
                } else {
                    returnedList = getNextChildElements(curLM, context, childLC, alignment,
                            lmStack, restartPosition, restartAtLM);
                    // once encountered, irrelevant for following child LMs
                    emptyStack = true;
                }
                if (contentList.isEmpty() && childLC.isKeepWithPreviousPending()) {
                    //Propagate keep-with-previous up from the first child
                    context.updateKeepWithPreviousPending(childLC.getKeepWithPreviousPending());
                    childLC.clearKeepWithPreviousPending();
                }
                if (returnedList.size() == 1
                        && ElementListUtils.startsWithForcedBreak(returnedList)) {
                    // a descendant of this block has break-before
                    contentList.addAll(returnedList);

                    // "wrap" the Position inside each element
                    // moving the elements from contentList to returnList
                    wrapPositionElements(contentList, returnList);

                    return returnList;
                } else {
                    if (prevLM != null) {
                        // there is a block handled by prevLM
                        // before the one handled by curLM
                        addInBetweenBreak(contentList, context, childLC);
                    }
                    contentList.addAll(returnedList);
                    if (returnedList.isEmpty()) {
                        //Avoid NoSuchElementException below (happens with empty blocks)
                        continue;
                    }
                    if (ElementListUtils.endsWithForcedBreak(returnedList)) {
                        // a descendant of this block has break-after
                        if (curLM.isFinished() && !hasNextChildLM()) {
                            // there is no other content in this block;
                            // it's useless to add space after before a page break
                            setFinished(true);
                        }

                        wrapPositionElements(contentList, returnList);
                        return returnList;
                    }
                }
                // propagate and clear
                context.updateKeepWithNextPending(childLC.getKeepWithNextPending());
                childLC.clearKeepsPending();
                prevLM = curLM;
                curLM = getChildLM();
            }
            wrapPositionElements(contentList, returnList);
        } else {
            returnList.add(generateNonInlinedBox());
        }

        addLastVisibleMarks(returnList, context, alignment);

        addKnuthElementsForBreakAfter(returnList, context);

        context.updateKeepWithNextPending(getKeepWithNext());

        setFinished(true);
        return returnList;
    }

    private void setupAreaDimensions(LayoutContext context) {
        autoHeight = false;
        int maxbpd = context.getStackLimitBP().getOpt();
        int allocBPD;
        BlockContainer fo = getBlockContainerFO();
        if (height.getEnum() == EN_AUTO
                || (!height.isAbsolute() && getAncestorBlockAreaBPD() <= 0)) {
            //auto height when height="auto" or "if that dimension is not specified explicitly
            //(i.e., it depends on content's block-progression-dimension)" (XSL 1.0, 7.14.1)
            allocBPD = maxbpd;
            autoHeight = true;
            //Cannot easily inline element list when ref-or<>"0"
            inlineElementList = (fo.getReferenceOrientation() == 0);
        } else {
            allocBPD = height.getValue(this); //this is the content-height
            allocBPD += getBPIndents();
        }
        vpContentBPD = allocBPD - getBPIndents();

        referenceIPD = context.getRefIPD();
        if (width.getEnum() == EN_AUTO) {
            updateContentAreaIPDwithOverconstrainedAdjust();
        } else {
            int contentWidth = width.getValue(this);
            updateContentAreaIPDwithOverconstrainedAdjust(contentWidth);
        }

        contentRectOffsetX = 0;
        contentRectOffsetY = 0;

        int level = fo.getBidiLevel();
        if ((level < 0) || ((level & 1) == 0)) {
            contentRectOffsetX += fo.getCommonMarginBlock().startIndent.getValue(this);
        } else {
            contentRectOffsetX += fo.getCommonMarginBlock().endIndent.getValue(this);
        }
        contentRectOffsetY += fo.getCommonBorderPaddingBackground().getBorderBeforeWidth(false);
        contentRectOffsetY += fo.getCommonBorderPaddingBackground().getPaddingBefore(false, this);

        updateRelDims();

        int availableIPD = referenceIPD - getIPIndents();
        if (getContentAreaIPD() > availableIPD) {
            BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
                    fo.getUserAgent().getEventBroadcaster());
            eventProducer.objectTooWide(this, fo.getName(),
                    getContentAreaIPD(), context.getRefIPD(),
                    fo.getLocator());
        }
    }

    private KnuthBox generateNonInlinedBox() {

        MinOptMax range = MinOptMax.getInstance(relDims.ipd);
        BlockContainerBreaker breaker = new BlockContainerBreaker(this, range);
        breaker.doLayout(relDims.bpd, autoHeight);
        boolean contentOverflows = breaker.isOverflow();
        if (autoHeight) {
            //Update content BPD now that it is known
            int newHeight = breaker.deferredAlg.totalWidth;
            if (blockProgressionDirectionChanges()) {
                setContentAreaIPD(newHeight);
            } else {
                vpContentBPD = newHeight;
            }
            updateRelDims();
        }

        Position bcPosition = new BlockContainerPosition(this, breaker);
        KnuthBox knuthBox = new KnuthBox(vpContentBPD, notifyPos(bcPosition), false);
        //TODO Handle min/opt/max for block-progression-dimension
        /* These two elements will be used to add stretchability to the above box
        returnList.add(new KnuthPenalty(0, KnuthElement.INFINITE,
                               false, returnPosition, false));
        returnList.add(new KnuthGlue(0, 1 * constantLineHeight, 0,
                               LINE_NUMBER_ADJUSTMENT, returnPosition, false));
        */

        if (contentOverflows) {
            BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
                    getBlockContainerFO().getUserAgent().getEventBroadcaster());
            boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW);
            eventProducer.viewportBPDOverflow(this, getBlockContainerFO().getName(),
                    breaker.getOverflowAmount(), needClip(), canRecover,
                    getBlockContainerFO().getLocator());
        }
        return knuthBox;
    }

    private boolean blockProgressionDirectionChanges() {
        return getBlockContainerFO().getReferenceOrientation() % 180 != 0;
    }

    /** {@inheritDoc} */
    @Override
    public boolean isRestartable() {
        return true;
    }

    private List<ListElement> getNextKnuthElementsAbsolute(LayoutContext context) {
        autoHeight = false;

        boolean bpDirectionChanges = blockProgressionDirectionChanges();
        Point offset = getAbsOffset();
        int allocBPD;
        int allocIPD;
        if (height.getEnum() == EN_AUTO
                || (!height.isAbsolute() && getAncestorBlockAreaBPD() <= 0)) {
            //auto height when height="auto" or "if that dimension is not specified explicitly
            //(i.e., it depends on content's blockprogression-dimension)" (XSL 1.0, 7.14.1)
            allocBPD = 0;
            if (abProps.bottom.getEnum() != EN_AUTO) {
                int availHeight;
                if (isFixed()) {
                    availHeight = (int)getCurrentPV().getViewArea().getHeight();
                } else {
                    availHeight = context.getStackLimitBP().getOpt();
                }
                allocBPD = availHeight;
                allocBPD -= offset.y;
                if (abProps.bottom.getEnum() != EN_AUTO) {
                    allocBPD -= abProps.bottom.getValue(this);
                    if (allocBPD < 0) {
                        //TODO Fix absolute b-c layout, layout may need to be defferred until
                        //after page breaking when the size of the containing box is known.
                        /* Warning disabled due to a interpretation mistake.
                         * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2
                        log.error("The current combination of top and bottom properties results"
                                + " in a negative extent for the block-container. 'bottom' may be"
                                + " at most " + (allocBPD + abProps.bottom.getValue(this)) + " mpt,"
                                + " but was actually " + abProps.bottom.getValue(this) + " mpt."
                                + " The nominal available height is " + availHeight + " mpt.");
                        */
                        allocBPD = 0;
                    }
                } else {
                    if (allocBPD < 0) {
                        /* Warning disabled due to a interpretation mistake.
                         * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2
                        log.error("The current combination of top and bottom properties results"
                                + " in a negative extent for the block-container. 'top' may be"
                                + " at most " + availHeight + " mpt,"
                                + " but was actually " + offset.y + " mpt."
                                + " The nominal available height is " + availHeight + " mpt.");
                        */
                        allocBPD = 0;
                    }
                }
            } else {
                allocBPD = context.getStackLimitBP().getOpt();
                if (!bpDirectionChanges) {
                    autoHeight = true;
                }
            }
        } else {
            allocBPD = height.getValue(this); //this is the content-height
            allocBPD += getBPIndents();
        }
        if (width.getEnum() == EN_AUTO) {
            int availWidth;
            if (isFixed()) {
                availWidth = (int)getCurrentPV().getViewArea().getWidth();
            } else {
                availWidth = context.getRefIPD();
            }
            allocIPD = availWidth;
            if (abProps.left.getEnum() != EN_AUTO) {
                allocIPD -= abProps.left.getValue(this);
            }
            if (abProps.right.getEnum() != EN_AUTO) {
                allocIPD -= abProps.right.getValue(this);
                if (allocIPD < 0) {
                    /* Warning disabled due to a interpretation mistake.
                     * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2
                    log.error("The current combination of left and right properties results"
                            + " in a negative extent for the block-container. 'right' may be"
                            + " at most " + (allocIPD + abProps.right.getValue(this)) + " mpt,"
                            + " but was actually " + abProps.right.getValue(this) + " mpt."
                            + " The nominal available width is " + availWidth + " mpt.");
                    */
                    allocIPD = 0;
                }
            } else {
                if (allocIPD < 0) {
                    /* Warning disabled due to a interpretation mistake.
                     * See: http://marc.theaimsgroup.com/?l=fop-dev&m=113189981926163&w=2
                    log.error("The current combination of left and right properties results"
                            + " in a negative extent for the block-container. 'left' may be"
                            + " at most " + allocIPD + " mpt,"
                            + " but was actually " + abProps.left.getValue(this) + " mpt."
                            + " The nominal available width is " + availWidth + " mpt.");
                    */
                    allocIPD = 0;
                }
                if (bpDirectionChanges) {
                    autoHeight = true;
                }
            }
        } else {
            allocIPD = width.getValue(this); //this is the content-width
            allocIPD += getIPIndents();
        }

        vpContentBPD = allocBPD - getBPIndents();
        setContentAreaIPD(allocIPD - getIPIndents());

        contentRectOffsetX = 0;
        contentRectOffsetY = 0;
        updateRelDims();

        MinOptMax range = MinOptMax.getInstance(relDims.ipd);
        BlockContainerBreaker breaker = new BlockContainerBreaker(this, range);
        breaker.doLayout((autoHeight ? 0 : relDims.bpd), autoHeight);
        boolean contentOverflows = breaker.isOverflow();
        if (autoHeight) {
            //Update content BPD now that it is known
            int newHeight = breaker.deferredAlg.totalWidth;
            if (bpDirectionChanges) {
                setContentAreaIPD(newHeight);
            } else {
                vpContentBPD = newHeight;
            }
            updateRelDims();
        }
        List<ListElement> returnList = new LinkedList<ListElement>();
        if (!breaker.isEmpty()) {
            Position bcPosition = new BlockContainerPosition(this, breaker);
            returnList.add(new KnuthBox(0, notifyPos(bcPosition), false));

            //TODO Maybe check for page overflow when autoHeight=true
            if (!autoHeight & (contentOverflows)) {
                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider.get(
                        getBlockContainerFO().getUserAgent().getEventBroadcaster());
                boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW);
                eventProducer.viewportBPDOverflow(this, getBlockContainerFO().getName(),
                        breaker.getOverflowAmount(), needClip(), canRecover,
                        getBlockContainerFO().getLocator());
            }
            // this handles the IPD (horizontal) overflow
            if (this.horizontalOverflow > 0) {
                BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider
                        .get(getBlockContainerFO().getUserAgent().getEventBroadcaster());
                boolean canRecover = (getBlockContainerFO().getOverflow() != EN_ERROR_IF_OVERFLOW);
                eventProducer.viewportIPDOverflow(this, getBlockContainerFO().getName(),
                        this.horizontalOverflow, needClip(), canRecover, getBlockContainerFO().getLocator());
            }
        }

        setFinished(true);
        return returnList;
    }

    private void updateRelDims() {
        Rectangle2D rect = new Rectangle2D.Double(
                contentRectOffsetX, contentRectOffsetY,
                getContentAreaIPD(),
                this.vpContentBPD);
        relDims = new FODimension(0, 0);
        absoluteCTM = CTM.getCTMandRelDims(
                getBlockContainerFO().getReferenceOrientation(),
                getBlockContainerFO().getWritingMode(),
                rect, relDims);
    }

    private class BlockContainerPosition extends NonLeafPosition {

        private BlockContainerBreaker breaker;

        public BlockContainerPosition(LayoutManager lm, BlockContainerBreaker breaker) {
            super(lm, null);
            this.breaker = breaker;
        }

        public BlockContainerBreaker getBreaker() {
            return this.breaker;
        }

    }

    private class BlockContainerBreaker extends AbstractBreaker {

        private BlockContainerLayoutManager bclm;
        private MinOptMax ipd;

        //Info for deferred adding of areas
        private PageBreakingAlgorithm deferredAlg;
        private BlockSequence deferredOriginalList;
        private BlockSequence deferredEffectiveList;

        public BlockContainerBreaker(BlockContainerLayoutManager bclm, MinOptMax ipd) {
            this.bclm = bclm;
            this.ipd = ipd;
        }

        /** {@inheritDoc} */
        protected void observeElementList(List elementList) {
            ElementListObserver.observe(elementList, "block-container",
                    bclm.getBlockContainerFO().getId());
        }

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

        /** {@inheritDoc} */
        protected boolean isSinglePartFavored() {
            return true;
        }

        public int getDifferenceOfFirstPart() {
            PageBreakPosition pbp = this.deferredAlg.getPageBreaks().getFirst();
            return pbp.difference;
        }

        public boolean isOverflow() {
            return !isEmpty()
                    && ((deferredAlg.getPageBreaks().size() > 1)
                        || (deferredAlg.totalWidth - deferredAlg.totalShrink)
                            > deferredAlg.getLineWidth());
        }

        public int getOverflowAmount() {
            return (deferredAlg.totalWidth - deferredAlg.totalShrink)
                - deferredAlg.getLineWidth();
        }

        protected LayoutManager getTopLevelLM() {
            return bclm;
        }

        protected LayoutContext createLayoutContext() {
            LayoutContext lc = super.createLayoutContext();
            lc.setRefIPD(ipd.getOpt());
            lc.setWritingMode(getBlockContainerFO().getWritingMode());
            return lc;
        }

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

            while ((curLM = getChildLM()) != null) {
                LayoutContext childLC = makeChildLayoutContext(context);

                List returnedList = null;
                if (!curLM.isFinished()) {
                    returnedList = curLM.getNextKnuthElements(childLC, alignment);
                }
                if (returnedList != null) {
                    bclm.wrapPositionElements(returnedList, returnList);
                }
            }
            SpaceResolver.resolveElementList(returnList);
            setFinished(true);
            return returnList;
        }

        protected int getCurrentDisplayAlign() {
            return getBlockContainerFO().getDisplayAlign();
        }

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

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

        protected void doPhase3(PageBreakingAlgorithm alg, int partCount,
                BlockSequence originalList, BlockSequence effectiveList) {
            //Defer adding of areas until addAreas is called by the parent LM
            this.deferredAlg = alg;
            this.deferredOriginalList = originalList;
            this.deferredEffectiveList = effectiveList;
        }

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

        protected LayoutManager getCurrentChildLM() {
            return curChildLM;
        }

        public void addContainedAreas(LayoutContext layoutContext) {
            if (isEmpty()) {
                return;
            }
            //Rendering all parts (not just the first) at once for the case where the parts that
            //overflow should be visible.
            this.deferredAlg.removeAllPageBreaks();
            this.addAreas(this.deferredAlg,
                          0,
                          this.deferredAlg.getPageBreaks().size(),
                          this.deferredOriginalList, this.deferredEffectiveList,
                          LayoutContext.offspringOf(layoutContext));
        }

    }

    private Point getAbsOffset() {
        int x = 0;
        int y = 0;
        if (abProps.left.getEnum() != EN_AUTO) {
            x = abProps.left.getValue(this);
        } else if (abProps.right.getEnum() != EN_AUTO
                && width.getEnum() != EN_AUTO) {
            x = getReferenceAreaIPD()
                - abProps.right.getValue(this) - width.getValue(this);
        }
        if (abProps.top.getEnum() != EN_AUTO) {
            y = abProps.top.getValue(this);
        } else if (abProps.bottom.getEnum() != EN_AUTO
                && height.getEnum() != EN_AUTO) {
            y = getReferenceAreaBPD()
                - abProps.bottom.getValue(this) - height.getValue(this);
        }
        return new Point(x, y);
    }

    /** {@inheritDoc} */
    @Override
    public void addAreas(PositionIterator parentIter, LayoutContext layoutContext) {
        getParentArea(null);

        // if this will create the first block area in a page
        // and display-align is bottom or center, add space before
        if (layoutContext.getSpaceBefore() > 0) {
            addBlockSpacing(0.0, MinOptMax.getInstance(layoutContext.getSpaceBefore()));
        }

        LayoutManager childLM;
        LayoutManager lastLM = null;
        LayoutContext lc = LayoutContext.offspringOf(layoutContext);
        lc.setSpaceAdjust(layoutContext.getSpaceAdjust());
        // set space after in the LayoutContext for children
        if (layoutContext.getSpaceAfter() > 0) {
            lc.setSpaceAfter(layoutContext.getSpaceAfter());
        }
        BlockContainerPosition bcpos = null;
        PositionIterator childPosIter;

        // "unwrap" the NonLeafPositions stored in parentIter
        // and put them in a new list;
        List<Position> positionList = new LinkedList<Position>();
        Position pos;
        Position firstPos = null;
        Position lastPos = null;
        while (parentIter.hasNext()) {
            pos = parentIter.next();
            if (pos.getIndex() >= 0) {
                if (firstPos == null) {
                    firstPos = pos;
                }
                lastPos = pos;
            }
            Position innerPosition = pos;
            if (pos instanceof NonLeafPosition) {
                innerPosition = pos.getPosition();
            }
            if (pos instanceof BlockContainerPosition) {
                if (bcpos != null) {
                    throw new IllegalStateException("Only one BlockContainerPosition allowed");
                }
                bcpos = (BlockContainerPosition)pos;
                //Add child areas inside the reference area
                //bcpos.getBreaker().addContainedAreas();
            } else if (innerPosition == null) {
                //ignore (probably a Position for a simple penalty between blocks)
            } else if (innerPosition.getLM() == this
                    && !(innerPosition instanceof MappingPosition)) {
                // pos was created by this BlockLM and was inside a penalty
                // allowing or forbidding a page break
                // nothing to do
            } else {
                // innerPosition was created by another LM
                positionList.add(innerPosition);
                lastLM = innerPosition.getLM();
            }
        }

        addId();

        registerMarkers(true, isFirst(firstPos), isLast(lastPos));

        if (bcpos == null) {
            // the Positions in positionList were inside the elements
            // created by the LineLM
            childPosIter = new PositionIterator(positionList.listIterator());

            while ((childLM = childPosIter.getNextChildLM()) != null) {
                // set last area flag
                lc.setFlags(LayoutContext.LAST_AREA,
                        (layoutContext.isLastArea() && childLM == lastLM));
                lc.setStackLimitBP(layoutContext.getStackLimitBP());
                // Add the line areas to Area
                childLM.addAreas(childPosIter, lc);
            }
        } else {
            //Add child areas inside the reference area
            bcpos.getBreaker().addContainedAreas(layoutContext);
        }

        registerMarkers(false, isFirst(firstPos), isLast(lastPos));

        TraitSetter.addSpaceBeforeAfter(viewportBlockArea, layoutContext.getSpaceAdjust(),
                effSpaceBefore, effSpaceAfter);
        flush();

        viewportBlockArea = null;
        referenceArea = null;
        resetSpaces();

        notifyEndOfLayout();
    }

    /**
     * Get the parent area for children of this block container.
     * This returns the current block container area
     * and creates it if required.
     *
     * {@inheritDoc}
     */
    @Override
    public Area getParentArea(Area childArea) {
        if (referenceArea == null) {
            boolean switchedProgressionDirection = blockProgressionDirectionChanges();
            boolean allowBPDUpdate = autoHeight && !switchedProgressionDirection;
            int level = getBlockContainerFO().getBidiLevel();

            viewportBlockArea = new BlockViewport(allowBPDUpdate);
            viewportBlockArea.setChangeBarList(getChangeBarList());
            viewportBlockArea.addTrait(Trait.IS_VIEWPORT_AREA, Boolean.TRUE);
            if (level >= 0) {
                viewportBlockArea.setBidiLevel(level);
            }
            viewportBlockArea.setIPD(getContentAreaIPD());
            if (allowBPDUpdate) {
                viewportBlockArea.setBPD(0);
            } else {
                viewportBlockArea.setBPD(this.vpContentBPD);
            }
            transferForeignAttributes(viewportBlockArea);

            TraitSetter.setProducerID(viewportBlockArea, getBlockContainerFO().getId());
            TraitSetter.setLayer(viewportBlockArea, getBlockContainerFO().getLayer());
            TraitSetter.addBorders(viewportBlockArea,
                    getBlockContainerFO().getCommonBorderPaddingBackground(),
                    discardBorderBefore, discardBorderAfter, false, false, this);
            TraitSetter.addPadding(viewportBlockArea,
                    getBlockContainerFO().getCommonBorderPaddingBackground(),
                    discardPaddingBefore, discardPaddingAfter, false, false, this);
            TraitSetter.addMargins(viewportBlockArea,
                    getBlockContainerFO().getCommonBorderPaddingBackground(),
                    startIndent, endIndent,
                    this);

            viewportBlockArea.setCTM(absoluteCTM);
            viewportBlockArea.setClip(needClip());

            if (abProps.absolutePosition == EN_ABSOLUTE
                    || abProps.absolutePosition == EN_FIXED) {
                Point offset = getAbsOffset();
                viewportBlockArea.setXOffset(offset.x);
                viewportBlockArea.setYOffset(offset.y);
            } else {
                //nop
            }

            referenceArea = new Block();
            referenceArea.setChangeBarList(getChangeBarList());
            referenceArea.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
            if (level >= 0) {
                referenceArea.setBidiLevel(level);
            }
            TraitSetter.setProducerID(referenceArea, getBlockContainerFO().getId());

            if (abProps.absolutePosition == EN_ABSOLUTE) {
                viewportBlockArea.setPositioning(Block.ABSOLUTE);
            } else if (abProps.absolutePosition == EN_FIXED) {
                viewportBlockArea.setPositioning(Block.FIXED);
            }

            // Set up dimensions
            // Must get dimensions from parent area
            /*Area parentArea =*/ parentLayoutManager.getParentArea(referenceArea);
            //int referenceIPD = parentArea.getIPD();
            referenceArea.setIPD(relDims.ipd);
            // Get reference IPD from parentArea
            setCurrentArea(viewportBlockArea); // ??? for generic operations
        }
        return referenceArea;
    }

    /**
     * Add the child to the block container.
     *
     * {@inheritDoc}
     */
    @Override
    public void addChildArea(Area childArea) {
        if (referenceArea != null) {
            referenceArea.addBlock((Block) childArea);
        }
    }

    /**
     * Force current area to be added to parent area.
     * {@inheritDoc}
     */
    @Override
    protected void flush() {
        viewportBlockArea.addBlock(referenceArea, autoHeight);

        TraitSetter.addBackground(viewportBlockArea,
                getBlockContainerFO().getCommonBorderPaddingBackground(),
                this);

        super.flush();
    }

    /** {@inheritDoc} */
    @Override
    public int negotiateBPDAdjustment(int adj, KnuthElement lastElement) {
        // TODO Auto-generated method stub
        return 0;
    }

    /** {@inheritDoc} */
    @Override
    public void discardSpace(KnuthGlue spaceGlue) {
        // TODO Auto-generated method stub
    }

    /** {@inheritDoc} */
    @Override
    public KeepProperty getKeepTogetherProperty() {
        return getBlockContainerFO().getKeepTogether();
    }

    /** {@inheritDoc} */
    @Override
    public KeepProperty getKeepWithPreviousProperty() {
        return getBlockContainerFO().getKeepWithPrevious();
    }

    /** {@inheritDoc} */
    @Override
    public KeepProperty getKeepWithNextProperty() {
        return getBlockContainerFO().getKeepWithNext();
    }

    /**
     * @return the BlockContainer node
     */
    protected BlockContainer getBlockContainerFO() {
        return (BlockContainer) fobj;
    }

    // --------- Property Resolution related functions --------- //

    /** {@inheritDoc} */
    @Override
    public boolean getGeneratesReferenceArea() {
        return true;
    }

    /** {@inheritDoc} */
    @Override
    public boolean getGeneratesBlockArea() {
        return true;
    }

    /** {@inheritDoc} */
    public boolean handleOverflow(int milliPoints) {
        if (milliPoints > this.horizontalOverflow) {
            this.horizontalOverflow = milliPoints;
        }
        return true;
    }

}


