/*
 * 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.layoutmgr;

import java.util.List;
import java.util.ListIterator;

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

import org.apache.fop.fo.Constants;
import org.apache.fop.fo.pagination.RegionBody;
import org.apache.fop.layoutmgr.BreakingAlgorithm.KnuthNode;
import org.apache.fop.traits.MinOptMax;
import org.apache.fop.util.ListUtil;

/**
 * Abstract base class for breakers (page breakers, static region handlers etc.).
 */
public abstract class AbstractBreaker {

    /** logging instance */
    protected static final Log log = LogFactory.getLog(AbstractBreaker.class);

    protected LayoutManager originalRestartAtLM;
    protected Position positionAtBreak;
    protected List<ListElement> firstElementsForRestart;
    protected PageSequenceLayoutManager pslm;

    /**
     * A page break position.
     */
    public static class PageBreakPosition extends LeafPosition {
        // Percentage to adjust (stretch or shrink)
        double bpdAdjust;
        int difference;
        int footnoteFirstListIndex;
        int footnoteFirstElementIndex;
        int footnoteLastListIndex;
        int footnoteLastElementIndex;

        PageBreakPosition(LayoutManager lm, int breakIndex,
                          int ffli, int ffei, int flli, int flei,
                          double bpdA, int diff) {
            super(lm, breakIndex);
            bpdAdjust = bpdA;
            difference = diff;
            footnoteFirstListIndex = ffli;
            footnoteFirstElementIndex = ffei;
            footnoteLastListIndex = flli;
            footnoteLastElementIndex = flei;
        }
    }

    public static class FloatPosition extends LeafPosition {
        double bpdAdjust; // Percentage to adjust (stretch or shrink)
        int difference;

        FloatPosition(LayoutManager lm, int breakIndex, double bpdA, int diff) {
            super(lm, breakIndex);
            bpdAdjust = bpdA;
            difference = diff;
        }
    }

    /**
     * Helper method, mainly used to improve debug/trace output
     * @param breakClassId  the {@link Constants} enum value.
     * @return the break class name
     */
    static String getBreakClassName(int breakClassId) {
        switch (breakClassId) {
        case Constants.EN_ALL: return "ALL";
        case Constants.EN_ANY: return "ANY";
        case Constants.EN_AUTO: return "AUTO";
        case Constants.EN_COLUMN: return "COLUMN";
        case Constants.EN_EVEN_PAGE: return "EVEN PAGE";
        case Constants.EN_LINE: return "LINE";
        case Constants.EN_NONE: return "NONE";
        case Constants.EN_ODD_PAGE: return "ODD PAGE";
        case Constants.EN_PAGE: return "PAGE";
        default: return "??? (" + String.valueOf(breakClassId) + ")";
        }
    }

    /**
     * Helper class, extending the functionality of the
     * basic {@link BlockKnuthSequence}.
     */
    public static class BlockSequence extends BlockKnuthSequence {

        private static final long serialVersionUID = -5348831120146774118L;

        /** Number of elements to ignore at the beginning of the list. */
        int ignoreAtStart;
        /** Number of elements to ignore at the end of the list. */
        int ignoreAtEnd;

        /**
         * startOn represents where on the page/which page layout
         * should start for this BlockSequence.  Acceptable values:
         * Constants.EN_ANY (can continue from finished location
         * of previous BlockSequence?), EN_COLUMN, EN_ODD_PAGE,
         * EN_EVEN_PAGE.
         */
        private final int startOn;

        private final int displayAlign;

        /**
         * Creates a new BlockSequence.
         * @param  startOn  the kind of page the sequence should start on.
         *                  One of {@link Constants#EN_ANY}, {@link Constants#EN_COLUMN},
         *                  {@link Constants#EN_ODD_PAGE}, or {@link Constants#EN_EVEN_PAGE}.
         * @param displayAlign the value for the display-align property
         */
        public BlockSequence(int startOn, int displayAlign) {
            super();
            this.startOn =  startOn;
            this.displayAlign = displayAlign;
        }

        /**
         * @return the kind of page the sequence should start on.
         *         One of {@link Constants#EN_ANY}, {@link Constants#EN_COLUMN},
         *         {@link Constants#EN_ODD_PAGE}, or {@link Constants#EN_EVEN_PAGE}.
         */
        public int getStartOn() {
            return this.startOn;
        }

        /** @return the value for the display-align property */
        public int getDisplayAlign() {
            return this.displayAlign;
        }

        /**
         * Finalizes a Knuth sequence.
         * @return a finalized sequence.
         */
        @Override
        public KnuthSequence endSequence() {
            return endSequence(null);
        }

        /**
         * Finalizes a Knuth sequence.
         * @param breakPosition a Position instance for the last penalty (may be null)
         * @return a finalized sequence.
         */
        public KnuthSequence endSequence(Position breakPosition) {
            // remove glue and penalty item at the end of the paragraph
            while (this.size() > ignoreAtStart
                    && !((KnuthElement) ListUtil.getLast(this)).isBox()) {
                ListUtil.removeLast(this);
            }
            if (this.size() > ignoreAtStart) {
                // add the elements representing the space at the end of the last line
                // and the forced break
                this.add(new KnuthPenalty(0, KnuthElement.INFINITE,
                        false, null, false));
                this.add(new KnuthGlue(0, 10000000, 0, null, false));
                this.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
                        false, breakPosition, false));
                ignoreAtEnd = 3;
                return this;
            } else {
                this.clear();
                return null;
            }
        }

        /**
         * Finalizes a this {@link BlockSequence}, adding a terminating
         * penalty-glue-penalty sequence
         * @param breakPosition a Position instance pointing to the last penalty
         * @return the finalized {@link BlockSequence}
         */
        public BlockSequence endBlockSequence(Position breakPosition) {
            KnuthSequence temp = endSequence(breakPosition);
            if (temp != null) {
                BlockSequence returnSequence = new BlockSequence(startOn, displayAlign);
                returnSequence.addAll(temp);
                returnSequence.ignoreAtEnd = this.ignoreAtEnd;
                return returnSequence;
            } else {
                return null;
            }
        }

    }

    // used by doLayout and getNextBlockList*
    protected List<BlockSequence> blockLists;

    private boolean empty = true;
    /** blockListIndex of the current BlockSequence in blockLists */
    protected int blockListIndex;


    /** desired text alignment */
    protected int alignment;

    private int alignmentLast;

    /** footnote separator length */
    protected MinOptMax footnoteSeparatorLength = MinOptMax.ZERO;

    /** @return current display alignment */
    protected abstract int getCurrentDisplayAlign();

    /** @return true if content not exhausted */
    protected abstract boolean hasMoreContent();

    /**
     * Tell the layout manager to add all the child areas implied
     * by Position objects which will be returned by the
     * Iterator.
     *
     * @param posIter the position iterator
     * @param context the context
     */
    protected abstract void addAreas(PositionIterator posIter, LayoutContext context);

    /** @return top level layout manager */
    protected abstract LayoutManager getTopLevelLM();

    /** @return current child layout manager */
    protected abstract LayoutManager getCurrentChildLM();

    /**
     * Controls the behaviour of the algorithm in cases where the first element of a part
     * overflows a line/page.
     * @return true if the algorithm should try to send the element to the next line/page.
     */
    protected boolean isPartOverflowRecoveryActivated() {
        return true;
    }

    /**
     * @return true if one a single part should be produced if possible (ex. for block-containers)
     */
    protected boolean isSinglePartFavored() {
        return false;
    }

    /**
     * Returns the PageProvider if any. PageBreaker overrides this method because each
     * page may have a different available BPD which needs to be accessible to the breaking
     * algorithm.
     * @return the applicable PageProvider, or null if not applicable
     */
    protected PageProvider getPageProvider() {
        return null;
    }

    /**
     * Creates and returns a PageBreakingLayoutListener for the PageBreakingAlgorithm to
     * notify about layout problems.
     * @return the listener instance or null if no notifications are needed
     */
    protected PageBreakingAlgorithm.PageBreakingLayoutListener createLayoutListener() {
        return null;
    }

    /**
     * Get a sequence of KnuthElements representing the content
     * of the node assigned to the LM
     *
     * @param context   the LayoutContext used to store layout information
     * @param alignment the desired text alignment
     * @return          the list of KnuthElements
     */
    protected abstract List<ListElement> getNextKnuthElements(LayoutContext context,
                                                               int alignment);

    /**
     * Get a sequence of KnuthElements representing the content
     * of the node assigned to the LM
     *
     * @param context   the LayoutContext used to store layout information
     * @param alignment the desired text alignment
     * @param positionAtIPDChange last element on the part before an IPD change
     * @param restartAtLM the layout manager from which to restart, if IPD
     * change occurs between two LMs
     * @return          the list of KnuthElements
     */
    protected List<ListElement> getNextKnuthElements(LayoutContext context, int alignment,
            Position positionAtIPDChange, LayoutManager restartAtLM) {
        throw new UnsupportedOperationException("TODO: implement acceptable fallback");
    }

    /** @return true if there's no content that could be handled. */
    public boolean isEmpty() {
        return empty;
    }

    /**
     * Start part.
     * @param list a block sequence
     * @param breakClass a break class
     */
    protected void startPart(BlockSequence list, int breakClass, boolean emptyContent) {
        //nop
    }

    /**
     * This method is called when no content is available for a part. Used to force empty pages.
     */
    protected void handleEmptyContent() {
        //nop
    }

    /**
     * Finish part.
     * @param alg a page breaking algorithm
     * @param pbp a page break posittion
     */
    protected abstract void finishPart(PageBreakingAlgorithm alg, PageBreakPosition pbp);

    /**
     * Creates the top-level LayoutContext for the breaker operation.
     * @return the top-level LayoutContext
     */
    protected LayoutContext createLayoutContext() {
        return LayoutContext.newInstance();
    }

    /**
     * Used to update the LayoutContext in subclasses prior to starting a new element list.
     * @param context the LayoutContext to update
     */
    protected void updateLayoutContext(LayoutContext context) {
        //nop
    }

    /**
     * Used for debugging purposes. Notifies all registered observers about the element list.
     * Override to set different parameters.
     * @param elementList the Knuth element list
     */
    protected void observeElementList(List elementList) {
        ElementListObserver.observe(elementList, "breaker", null);
    }

    /**
     * Starts the page breaking process.
     * @param flowBPD the constant available block-progression-dimension (used for every part)
     * @param autoHeight true if warnings about overflows should be disabled because the
     *                   the BPD is really undefined (for footnote-separators, for example)
     */
    public boolean doLayout(int flowBPD, boolean autoHeight) {
        LayoutContext childLC = createLayoutContext();
        childLC.setStackLimitBP(MinOptMax.getInstance(flowBPD));
        alignment = Constants.EN_START;
        alignmentLast = Constants.EN_START;
        childLC.setBPAlignment(alignment);

        BlockSequence blockList;
        blockLists = new java.util.ArrayList<BlockSequence>();

        log.debug("PLM> flow BPD =" + flowBPD);

        int nextSequenceStartsOn = Constants.EN_ANY;
        while (hasMoreContent()) {
            blockLists.clear();

            try {
                //*** Phase 1: Get Knuth elements ***
                nextSequenceStartsOn = getNextBlockList(childLC, nextSequenceStartsOn);
                empty = empty && blockLists.size() == 0;

                //*** Phases 2 and 3 ***
                log.debug("PLM> blockLists.size() = " + blockLists.size());
                for (blockListIndex = 0; blockListIndex < blockLists.size(); blockListIndex++) {
                    blockList = blockLists.get(blockListIndex);

                    //debug code start
                    if (log.isDebugEnabled()) {
                        log.debug("  blockListIndex = " + blockListIndex);
                        log.debug("  sequence starts on " + getBreakClassName(blockList.startOn));
                    }
                    observeElementList(blockList);
                    //debug code end

                    //*** Phase 2: Alignment and breaking ***
                    log.debug("PLM> start of algorithm (" + this.getClass().getName()
                            + "), flow BPD =" + flowBPD);
                    PageBreakingAlgorithm alg = new PageBreakingAlgorithm(getTopLevelLM(),
                             getPageProvider(), createLayoutListener(),
                             alignment, alignmentLast, footnoteSeparatorLength,
                             isPartOverflowRecoveryActivated(), autoHeight, isSinglePartFavored());

                    alg.setConstantLineWidth(flowBPD);
                    int optimalPageCount = alg.findBreakingPoints(blockList, 1, true,
                            BreakingAlgorithm.ALL_BREAKS);
                    boolean ipdChangesOnNextPage = (alg.getIPDdifference() != 0);
                    boolean onLastPageAndIPDChanges = false;
                    if (!ipdChangesOnNextPage) {
                        onLastPageAndIPDChanges = (lastPageHasIPDChange(optimalPageCount)
                                && !thereIsANonRestartableLM(alg)
                                && (shouldRedoLayout() || (wasLayoutRedone() && optimalPageCount > 1)));
                    }
                    if (shouldRedoLayoutWithoutPagePositionOnly(ipdChangesOnNextPage, optimalPageCount, alg)) {
                        return false;
                    }
                    if (alg.handlingFloat()) {
                        nextSequenceStartsOn = handleFloatLayout(alg, optimalPageCount, blockList, childLC);
                    } else if (ipdChangesOnNextPage || onLastPageAndIPDChanges) {
                        boolean visitedBefore = false;
                        if (onLastPageAndIPDChanges) {
                            visitedBefore = wasLayoutRedone();
                            prepareToRedoLayout(alg, optimalPageCount, blockList, blockList);
                        }

                        firstElementsForRestart = null;
                        RestartAtLM restartAtLMClass = new RestartAtLM();
                        LayoutManager restartAtLM = restartAtLMClass.getRestartAtLM(this, alg, ipdChangesOnNextPage,
                                onLastPageAndIPDChanges, visitedBefore, blockList, 1);
                        if (restartAtLMClass.invalidPosition) {
                            return false;
                        }
                        if (restartAtLM == null || restartAtLM.getChildLMs().isEmpty()) {
                            firstElementsForRestart = null;
                            LayoutManager restartAtLM2 = new RestartAtLM().getRestartAtLM(this, alg,
                                    ipdChangesOnNextPage, onLastPageAndIPDChanges, visitedBefore, blockList, 0);
                            if (restartAtLM2 != null) {
                                restartAtLM = restartAtLM2;
                            }
                        }
                        if (ipdChangesOnNextPage) {
                            addAreas(alg, optimalPageCount, blockList, blockList);
                        }
                        blockLists.clear();
                        blockListIndex = -1;
                        nextSequenceStartsOn = getNextBlockList(childLC, Constants.EN_COLUMN, positionAtBreak,
                                restartAtLM, firstElementsForRestart);
                    } else {
                        log.debug("PLM> optimalPageCount= " + optimalPageCount
                                + " pageBreaks.size()= " + alg.getPageBreaks().size());

                        //*** Phase 3: Add areas ***
                        doPhase3(alg, optimalPageCount, blockList, blockList);
                    }
                }
            } catch (PagePositionOnlyException e) {
                return false;
            }
        }

        // done
        blockLists = null;
        return true;
    }

    static class PagePositionOnlyException extends RuntimeException {
    }

    private boolean shouldRedoLayoutWithoutPagePositionOnly(boolean ipdChangesOnNextPage, int optimalPageCount,
                                                            PageBreakingAlgorithm alg) {
        if ((ipdChangesOnNextPage || hasMoreContent() || optimalPageCount > 1)
                && pslm != null && pslm.getCurrentPage().isPagePositionOnly) {
            if (getPageProvider().foUserAgent.isLegacySkipPagePositionOnly()) {
                return true;
            }
            RegionBody rb = (RegionBody)pslm.getCurrentPage().getSimplePageMaster().getRegion(Constants.FO_REGION_BODY);
            if (rb.getColumnCount() == 1) {
                return true;
            }
            int restartPoint = getPageProvider().getStartingPartIndexForLastPage(optimalPageCount);
            if (restartPoint > 0) {
                PageBreakPosition pbp = alg.getPageBreaks().get(restartPoint - 1);
                int newStartPos = alg.par.getFirstBoxIndex(pbp.getLeafPos() + 1);
                return newStartPos > 0;
            }
        }
        return false;
    }

    /**
     * Returns {@code true} if the given position or one of its descendants
     * corresponds to a non-restartable LM.
     *
     * @param position a position
     * @return {@code true} if there is a non-restartable LM in the hierarchy
     */
    protected boolean containsNonRestartableLM(Position position) {
        LayoutManager lm = position.getLM();
        if (lm != null && !lm.isRestartable()) {
            return true;
        } else {
            Position subPosition = position.getPosition();
            return subPosition != null && containsNonRestartableLM(subPosition);
        }
    }

    /**
     * Phase 3 of Knuth algorithm: Adds the areas
     * @param alg PageBreakingAlgorithm instance which determined the breaks
     * @param partCount number of parts (pages) to be rendered
     * @param originalList original Knuth element list
     * @param effectiveList effective Knuth element list (after adjustments)
     */
    protected abstract void doPhase3(PageBreakingAlgorithm alg, int partCount,
            BlockSequence originalList, BlockSequence effectiveList);

    /**
     * Phase 3 of Knuth algorithm: Adds the areas
     * @param alg PageBreakingAlgorithm instance which determined the breaks
     * @param partCount number of parts (pages) to be rendered
     * @param originalList original Knuth element list
     * @param effectiveList effective Knuth element list (after adjustments)
     */
    protected void addAreas(PageBreakingAlgorithm alg, int partCount,
            BlockSequence originalList, BlockSequence effectiveList) {
        addAreas(alg, 0, partCount, originalList, effectiveList);
    }

    protected void addAreas(PageBreakingAlgorithm alg, int startPart, int partCount,
            BlockSequence originalList, BlockSequence effectiveList) {
        addAreas(alg, startPart, partCount, originalList, effectiveList, LayoutContext.newInstance());
    }

    /**
     * Phase 3 of Knuth algorithm: Adds the areas
     * @param alg PageBreakingAlgorithm instance which determined the breaks
     * @param startPart index of the first part (page) to be rendered
     * @param partCount number of parts (pages) to be rendered
     * @param originalList original Knuth element list
     * @param effectiveList effective Knuth element list (after adjustments)
     */
    protected void addAreas(PageBreakingAlgorithm alg, int startPart, int partCount,
            BlockSequence originalList, BlockSequence effectiveList, final LayoutContext childLC) {
        int startElementIndex = 0;
        int endElementIndex = 0;
        int lastBreak = -1;
        for (int p = startPart; p < startPart + partCount; p++) {
            PageBreakPosition pbp = alg.getPageBreaks().get(p);

            // Check the last break position for forced breaks
            int lastBreakClass;
            if (p == 0) {
                lastBreakClass = effectiveList.getStartOn();
            } else {
                ListElement lastBreakElement = effectiveList.getElement(endElementIndex);
                if (lastBreakElement.isPenalty()) {
                    KnuthPenalty pen = (KnuthPenalty) lastBreakElement;
                    if (pen.getPenalty() == KnuthPenalty.INFINITE) {
                        /**
                         * That means that there was a keep.within-page="always", but that
                         * it's OK to break at a column. TODO The break class is being
                         * abused to implement keep.within-column and keep.within-page.
                         * This is very misleading and must be revised.
                         */
                        lastBreakClass = Constants.EN_COLUMN;
                    } else {
                        lastBreakClass = pen.getBreakClass();
                    }
                } else {
                    lastBreakClass = Constants.EN_COLUMN;
                }
            }

            // the end of the new part
            endElementIndex = pbp.getLeafPos();

            // ignore the first elements added by the
            // PageSequenceLayoutManager
            startElementIndex += (startElementIndex == 0) ? effectiveList.ignoreAtStart : 0;

            log.debug("PLM> part: " + (p + 1)
                    + ", start at pos " + startElementIndex
                    + ", break at pos " + endElementIndex
                    + ", break class = " + getBreakClassName(lastBreakClass));

            startPart(effectiveList, lastBreakClass, startElementIndex > endElementIndex);

            int displayAlign = getCurrentDisplayAlign();

            // The following is needed by SpaceResolver.performConditionalsNotification()
            // further down as there may be important Position elements in the element list trailer
            int notificationEndElementIndex = endElementIndex;

            // ignore the last elements added by the
            // PageSequenceLayoutManager
            endElementIndex -= (endElementIndex == (originalList.size() - 1)) ? effectiveList.ignoreAtEnd : 0;

            // ignore the last element in the page if it is a KnuthGlue
            // object
            if (((KnuthElement) effectiveList.get(endElementIndex)).isGlue()) {
                endElementIndex--;
            }

            // ignore KnuthGlue and KnuthPenalty objects
            // at the beginning of the line
            startElementIndex = alg.par.getFirstBoxIndex(startElementIndex);

            if (startElementIndex <= endElementIndex) {
                if (log.isDebugEnabled()) {
                    log.debug("     addAreas from " + startElementIndex
                            + " to " + endElementIndex);
                }
                // set the space adjustment ratio
                childLC.setSpaceAdjust(pbp.bpdAdjust);
                // add space before if display-align is center or bottom
                // add space after if display-align is distribute and
                // this is not the last page
                if (pbp.difference != 0 && displayAlign == Constants.EN_CENTER) {
                    childLC.setSpaceBefore(pbp.difference / 2);
                } else if (pbp.difference != 0 && displayAlign == Constants.EN_AFTER) {
                    childLC.setSpaceBefore(pbp.difference);
                }

                // Handle SpaceHandling(Break)Positions, see SpaceResolver!
                SpaceResolver.performConditionalsNotification(effectiveList, startElementIndex,
                        notificationEndElementIndex, lastBreak);
                // Add areas now!
                addAreas(new KnuthPossPosIter(effectiveList, startElementIndex, endElementIndex + 1), childLC);
            } else {
                // no content for this part
                handleEmptyContent();
            }

            finishPart(alg, pbp);

            lastBreak = endElementIndex;
            startElementIndex = pbp.getLeafPos() + 1;
        }
        if (alg.handlingFloat()) {
            addAreasForFloats(alg, startPart, partCount, originalList, effectiveList, childLC, lastBreak,
                    startElementIndex, endElementIndex);
        }
    }
    /**
     * Notifies the layout managers about the space and conditional length situation based on
     * the break decisions.
     * @param effectiveList Element list to be painted
     * @param startElementIndex start index of the part
     * @param endElementIndex end index of the part
     * @param lastBreak index of the last break element
     */
    /**
     * Handles span changes reported through the <code>LayoutContext</code>.
     * Only used by the PSLM and called by <code>getNextBlockList()</code>.
     * @param childLC the LayoutContext
     * @param nextSequenceStartsOn previous value for break handling
     * @return effective value for break handling
     */
    protected int handleSpanChange(LayoutContext childLC, int nextSequenceStartsOn) {
        return nextSequenceStartsOn;
    }

    /**
     * Gets the next block list (sequence) and adds it to a list of block lists if it's not empty.
     * @param childLC LayoutContext to use
     * @param nextSequenceStartsOn indicates on what page the next sequence should start
     * @return the page on which the next content should appear after a hard break
     */
    protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn) {
        return getNextBlockList(childLC, nextSequenceStartsOn, null, null, null);
    }

    /**
     * Gets the next block list (sequence) and adds it to a list of block lists
     * if it's not empty.
     *
     * @param childLC LayoutContext to use
     * @param nextSequenceStartsOn indicates on what page the next sequence
     * should start
     * @param positionAtIPDChange last element on the part before an IPD change
     * @param restartAtLM the layout manager from which to restart, if IPD
     * change occurs between two LMs
     * @param firstElements elements from non-restartable LMs on the new page
     * @return the page on which the next content should appear after a hard
     * break
     */
    protected int getNextBlockList(LayoutContext childLC, int nextSequenceStartsOn,
            Position positionAtIPDChange, LayoutManager restartAtLM,
            List<ListElement> firstElements) {
        updateLayoutContext(childLC);
        //Make sure the span change signal is reset
        childLC.signalSpanChange(Constants.NOT_SET);

        BlockSequence blockList;
        List<ListElement> returnedList;
        if (firstElements == null) {
            returnedList = getNextKnuthElements(childLC, alignment);
        } else if (positionAtIPDChange == null) {
            /*
             * No restartable element found after changing IPD break. Simply add the
             * non-restartable elements found after the break.
             */
            returnedList = firstElements;
            /*
             * Remove the last 3 penalty-filler-forced break elements that were added by
             * the Knuth algorithm. They will be re-added later on.
             */
            if (returnedList.size() > 2) {
                ListIterator iter = returnedList.listIterator(returnedList.size());
                for (int i = 0; i < 3; i++) {
                    iter.previous();
                    iter.remove();
                }
            }
        } else {
            returnedList = getNextKnuthElements(childLC, alignment, positionAtIPDChange,
                    restartAtLM);
            returnedList.addAll(0, firstElements);
        }
        if (returnedList != null) {
            if (returnedList.isEmpty()) {
                nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn);
                return nextSequenceStartsOn;
            }
            blockList = new BlockSequence(nextSequenceStartsOn, getCurrentDisplayAlign());

            //Only implemented by the PSLM
            nextSequenceStartsOn = handleSpanChange(childLC, nextSequenceStartsOn);

            Position breakPosition = null;
            if (ElementListUtils.endsWithForcedBreak(returnedList)) {
                KnuthPenalty breakPenalty = (KnuthPenalty) ListUtil
                        .removeLast(returnedList);
                breakPosition = breakPenalty.getPosition();
                log.debug("PLM> break - " + getBreakClassName(breakPenalty.getBreakClass()));
                switch (breakPenalty.getBreakClass()) {
                case Constants.EN_PAGE:
                    nextSequenceStartsOn = Constants.EN_ANY;
                    break;
                case Constants.EN_COLUMN:
                    //TODO Fix this when implementing multi-column layout
                    nextSequenceStartsOn = Constants.EN_COLUMN;
                    break;
                case Constants.EN_ODD_PAGE:
                    nextSequenceStartsOn = Constants.EN_ODD_PAGE;
                    break;
                case Constants.EN_EVEN_PAGE:
                    nextSequenceStartsOn = Constants.EN_EVEN_PAGE;
                    break;
                default:
                    throw new IllegalStateException("Invalid break class: "
                            + breakPenalty.getBreakClass());
                }
                if (ElementListUtils.isEmptyBox(returnedList)) {
                    ListUtil.removeLast(returnedList);
                }
            }
            blockList.addAll(returnedList);
            BlockSequence seq;
            seq = blockList.endBlockSequence(breakPosition);
            if (seq != null) {
                blockLists.add(seq);
            }
        }
        return nextSequenceStartsOn;
    }

    protected boolean shouldRedoLayout() {
        return false;
    }

    protected void prepareToRedoLayout(PageBreakingAlgorithm alg, int partCount,
            BlockSequence originalList, BlockSequence effectiveList) {
        return;
    }

    protected boolean wasLayoutRedone() {
        return false;
    }

    private boolean thereIsANonRestartableLM(PageBreakingAlgorithm alg) {
        KnuthNode optimalBreak = alg.getBestNodeForLastPage();
        if (optimalBreak != null) {
            int positionIndex = optimalBreak.position;
            KnuthElement elementAtBreak = alg.getElement(positionIndex);
            Position positionAtBreak = elementAtBreak.getPosition();
            if (!(positionAtBreak instanceof SpaceResolver.SpaceHandlingBreakPosition)) {
                return false;
            }
            /* Retrieve the original position wrapped into this space position */
            positionAtBreak = positionAtBreak.getPosition();
            if (positionAtBreak != null && containsNonRestartableLM(positionAtBreak)) {
                return true;
            }
        }
        return false;
    }

    protected boolean lastPageHasIPDChange(int optimalPageCount) {
        return false;
    }

    protected int handleFloatLayout(PageBreakingAlgorithm alg, int optimalPageCount, BlockSequence blockList,
            LayoutContext childLC) {
        throw new IllegalStateException();
    }

    protected void addAreasForFloats(PageBreakingAlgorithm alg, int startPart, int partCount,
            BlockSequence originalList, BlockSequence effectiveList, final LayoutContext childLC,
            int lastBreak, int startElementIndex, int endElementIndex) {
        throw new IllegalStateException();
    }
}
