/*
 * Copyright 1999-2005 The Apache Software Foundation.
 * 
 * Licensed 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: LineLayoutManager.java,v 1.17 2004/04/02 10:38:29 cbowditch Exp $ */

package org.apache.fop.layoutmgr;

import org.apache.fop.datatypes.Length;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.flow.Block;
import org.apache.fop.fo.properties.CommonHyphenation;
import org.apache.fop.hyphenation.Hyphenation;
import org.apache.fop.hyphenation.Hyphenator;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.Resolvable;

import java.util.ListIterator;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;

import org.apache.fop.traits.MinOptMax;

/**
 * LayoutManager for lines. It builds one or more lines containing
 * inline areas generated by its sub layout managers.
 * A break is found for each line which may contain one of more
 * breaks from the child layout managers.
 * Once a break is found then it is return for the parent layout
 * manager to handle.
 * When the areas are being added to the page this manager
 * creates a line area to contain the inline areas added by the
 * child layout managers.
 */
public class LineLayoutManager extends InlineStackingLayoutManager {
    private Block fobj; 
    
    /**
     * Create a new Line Layout Manager.
     * This is used by the block layout manager to create
     * line managers for handling inline areas flowing into line areas.
     *
     * @param lh the default line height
     * @param l the default lead, from top to baseline
     * @param f the default follow, from baseline to bottom
     */
    public LineLayoutManager(Block node, int lh, int l, int f, int ms) {
        super(node);
        fobj = node;
        // the child FObj are owned by the parent BlockLM
        // this LM has all its childLMs preloaded
        fobjIter = null;
        lineHeight = lh;
        lead = l;
        follow = f;
        middleShift = ms;
        initialize(); // Normally done when started by parent!
    }

    /**
     * @see org.apache.fop.layoutmgr.AbstractLayoutManager#initProperties()
     */
    protected void initProperties() {
        bTextAlignment = fobj.getTextAlign();
        bTextAlignmentLast = fobj.getTextAlignLast();
        textIndent = fobj.getTextIndent();
        hyphProps = fobj.getCommonHyphenation();
        
        //
        if (bTextAlignment != EN_JUSTIFY && bTextAlignmentLast == EN_JUSTIFY) {
            effectiveAlignment = 0;
        } else {
            effectiveAlignment = bTextAlignment;
        }
    }

    /**
     * Private class to store information about inline breaks.
     * Each value holds the start and end indexes into a List of
     * inline break positions.
     */
    private static class LineBreakPosition extends LeafPosition {
        // int iPos;
        int iParIndex; // index of the Paragraph this Position refers to
        double dAdjust; // Percentage to adjust (stretch or shrink)
        double ipdAdjust; // Percentage to adjust (stretch or shrink)
        int startIndent;
        int lineHeight;
        int baseline;

        LineBreakPosition(LayoutManager lm, int index, int iBreakIndex,
                          double ipdA, double adjust, int ind, int lh, int bl) {
            super(lm, iBreakIndex);
            // iPos = iBreakIndex;
            iParIndex = index;
            ipdAdjust = ipdA;
            dAdjust = adjust;
            startIndent = ind;
            lineHeight = lh;
            baseline = bl;
        }
        
    }


    /** Break positions returned by inline content. */
    private List vecInlineBreaks = new ArrayList();

    private BreakPoss prevBP = null; // Last confirmed break position
    private int bTextAlignment = EN_JUSTIFY;
    private int bTextAlignmentLast;
    private int effectiveAlignment;
    private Length textIndent;
    private CommonHyphenation hyphProps;

    private int lineHeight;
    private int lead;
    private int follow;
    // offset of the middle baseline with respect to the main baseline
    private int middleShift;

    private ArrayList knuthParagraphs = null;
    private ArrayList breakpoints = null;
    private int iReturnedLBP = 0;
    private int iStartElement = 0;
    private int iEndElement = 0;

    //     parameters of Knuth's algorithm:
    // penalty value for flagged penalties
    private int flaggedPenalty = 50;

    // this constant is used to create elements when text-align is center:
    // every TextLM descendant of LineLM must use the same value, 
    // otherwise the line breaking algorithm does not find the right
    // break point
    public static final int DEFAULT_SPACE_WIDTH = 3336;


    // this class is used to remember
    // which was the first element in the paragraph
    // returned by each LM
    private class Update {
        private InlineLevelLayoutManager inlineLM;
        private int iFirstIndex;

        public Update(InlineLevelLayoutManager lm, int index) {
            inlineLM = lm;
            iFirstIndex = index;
        }
    }

    // this class represents a paragraph
    public class Paragraph extends ArrayList {
        // number of KnuthElements added by the LineLayoutManager
        public int ignoreAtStart = 0;
        public int ignoreAtEnd = 0;
        // minimum space at the end of the last line (in millipoints)
        public int lineFillerWidth;

        public void startParagraph(int lineWidth) {
            // set the minimum amount of empty space at the end of the
            // last line
            if (bTextAlignment == EN_CENTER) {
                lineFillerWidth = 0; 
            } else {
                lineFillerWidth = (int)(lineWidth / 12); 
            }

            // add auxiliary elements at the beginning of the paragraph
            if (bTextAlignment == EN_CENTER && bTextAlignmentLast != EN_JUSTIFY) {
                this.add(new KnuthGlue(0, 3 * DEFAULT_SPACE_WIDTH, 0,
                                       null, false));
                ignoreAtStart ++;
            }

            // add the element representing text indentation
            // at the beginning of the first paragraph
            if (knuthParagraphs.size() == 0
                && textIndent.getValue() != 0) {
                this.add(new KnuthBox(textIndent.getValue(), 0, 0, 0,
                                      null, false));
                ignoreAtStart ++;
            }
        }

        public void endParagraph() {
            // remove glue and penalty item at the end of the paragraph
            while (this.size() > ignoreAtStart
                   && !((KnuthElement) this.get(this.size() - 1)).isBox()) {
                this.remove(this.size() - 1);
            }
            if (this.size() > ignoreAtStart) {
                if (bTextAlignment == EN_CENTER
                    && bTextAlignmentLast != EN_JUSTIFY) {
                    this.add(new KnuthGlue(0, 3 * DEFAULT_SPACE_WIDTH, 0,
                                           null, false));
                    this.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
                                              false, null, false));
                    ignoreAtEnd = 2;
                } else if (bTextAlignmentLast != EN_JUSTIFY) {
                    // 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(lineFillerWidth, 10000000, 0,
                                           null, false));
                    this.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
                                              false, null, false));
                    ignoreAtEnd = 3;
                } else {
                    // add only the element representing the forced break
                    this.add(new KnuthPenalty(0, -KnuthElement.INFINITE,
                                              false, null, false));
                    ignoreAtEnd = 1;
                }
                knuthParagraphs.add(this);
            }
        }

        public KnuthElement getLast() {
            int idx = size();
            if (idx == 0) {
                return null; 
            }
            return (KnuthElement) get(idx - 1);
        }

        public KnuthElement removeLast() {
            int idx = size();
            if (idx == 0) {
                return null; 
            }
            return (KnuthElement) remove(idx - 1);
        }
    }


    /**
     * Call child layout managers to generate content.
     * This gets the next break which is a full line.
     *
     * @param context the layout context for finding breaks
     * @return the next break position
     */
    public BreakPoss getNextBreakPoss(LayoutContext context) {
        // Get a break from currently active child LM
        // Set up constraints for inline level managers
        InlineLevelLayoutManager curLM ; // currently active LM

        // IPD remaining in line
        MinOptMax availIPD = context.getStackLimit();

        LayoutContext inlineLC = new LayoutContext(context);

        clearPrevIPD();
        int iPrevLineEnd = vecInlineBreaks.size();

        if (iPrevLineEnd == 0 && bTextAlignment == EN_START) {
            availIPD.subtract(new MinOptMax(textIndent.getValue()));
        }
        prevBP = null;

        //PHASE 1: Create Knuth elements
        
        if (knuthParagraphs == null) {
            // it's the first time this method is called
            knuthParagraphs = new ArrayList();

            // here starts Knuth's algorithm
            KnuthElement thisElement = null;
            LinkedList returnedList = null;

            // convert all the text in a sequence of paragraphs made
            // of KnuthBox, KnuthGlue and KnuthPenalty objects
            boolean bPrevWasKnuthBox = false;
            KnuthBox prevBox = null;

            Paragraph knuthPar = new Paragraph();
            knuthPar.startParagraph(availIPD.opt);
            while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) {
                if ((returnedList
                     = curLM.getNextKnuthElements(inlineLC,
                                                  effectiveAlignment))
                    != null) {
                    // look at the first element
                    thisElement = (KnuthElement) returnedList.getFirst();
                    if (thisElement.isBox() && !thisElement.isAuxiliary()
                        && bPrevWasKnuthBox) {
                        prevBox = (KnuthBox) knuthPar.removeLast();
                        // if there are two consecutive KnuthBoxes the
                        // first one does not represent a whole word,
                        // so it must be given one more letter space
                        if (!prevBox.isAuxiliary()) {
                            // if letter spacing is constant,
                            // only prevBox needs to be replaced;
                            knuthPar.add(((InlineLevelLayoutManager)
                                              prevBox.getLayoutManager())
                                             .addALetterSpaceTo(prevBox));
                        } else {
                            // prevBox is the last element
                            // in the sub-sequence
                            //   <box> <aux penalty> <aux glue> <aux box>
                            // the letter space is added to <aux glue>,
                            // while the other elements are not changed
                            KnuthBox auxBox = prevBox;
                            KnuthGlue auxGlue
                                = (KnuthGlue) knuthPar.removeLast();
                            KnuthPenalty auxPenalty
                                = (KnuthPenalty) knuthPar.removeLast();
                            prevBox = (KnuthBox) knuthPar.getLast();
                            knuthPar.add(auxPenalty);
                            knuthPar.add(((InlineLevelLayoutManager)
                                              prevBox.getLayoutManager())
                                             .addALetterSpaceTo(prevBox));
                            knuthPar.add(auxBox);
                        }
                    }

                    // look at the last element
                    KnuthElement lastElement = (KnuthElement) returnedList.getLast();
                    boolean bForceLinefeed = false;
                    if (lastElement.isBox()) {
                        bPrevWasKnuthBox = true;
                    } else {
                        bPrevWasKnuthBox = false;
                        if (lastElement.isPenalty()
                            && ((KnuthPenalty) lastElement).getP()
                                == -KnuthPenalty.INFINITE) {
                            // a penalty item whose value is -inf
                            // represents a preserved linefeed,
                            // wich forces a line break
                            bForceLinefeed = true;
                            returnedList.removeLast();
                        }
                    }

                    // add the new elements to the paragraph
                    knuthPar.addAll(returnedList);
                    if (bForceLinefeed) {
                        if (knuthPar.size() == 0) {
                            //only a forced linefeed on this line 
                            //-> compensate with a zero width box
                            knuthPar.add(new KnuthBox(0, 0, 0, 0,
                                    null, false));
                        }
                        knuthPar.endParagraph();
                        knuthPar = new Paragraph();
                        knuthPar.startParagraph(availIPD.opt);
                        bPrevWasKnuthBox = false;
                    }
                } else {
                    // curLM returned null; this can happen
                    // if it has nothing more to layout,
                    // so just iterate once more to see
                    // if there are other children
                }
            }
            knuthPar.endParagraph();
        } else {
            // this method has been called before
            // all line breaks are already calculated
        }

        // return finished when there's no content
        if (knuthParagraphs.size() == 0) {
            setFinished(true);
            return null;
        }

        //PHASE 2: Create line breaks

        LineBreakPosition lbp = null;
        if (breakpoints == null) {
            // find the optimal line breaking points for each paragraph
            breakpoints = new ArrayList();
            ListIterator paragraphsIterator
                = knuthParagraphs.listIterator(knuthParagraphs.size());
            Paragraph currPar = null;
            while (paragraphsIterator.hasPrevious()) {
                currPar = (Paragraph) paragraphsIterator.previous();
                findBreakingPoints(currPar, context.getStackLimit().opt);
            }
        }

        //PHASE 3: Return lines

        // get a break point from the list
        lbp = (LineBreakPosition) breakpoints.get(iReturnedLBP ++);
        if (iReturnedLBP == breakpoints.size()) {
            setFinished(true);
        }

        BreakPoss curLineBP = new BreakPoss(lbp);
        curLineBP.setFlag(BreakPoss.ISLAST, isFinished());
        curLineBP.setStackingSize(new MinOptMax(lbp.lineHeight));
        return curLineBP;
    }

    /**
     * Find a set of breaking points.
     * This method is called only once by getNextBreakPoss, and it 
     * subsequently calls the other findBreakingPoints() method with 
     * different parameters, until a set of breaking points is found.
     *
     * @param par       the list of elements that must be parted
     *                  into lines
     * @param lineWidth the desired length ot the lines
     */
    private void findBreakingPoints(Paragraph par, int lineWidth) {
        // maximum adjustment ratio permitted
        float maxAdjustment = 1;

        // first try
        if (!findBreakingPoints(par, lineWidth, maxAdjustment, false)) {
            // the first try failed, now try something different
            log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment);
            if (hyphProps.hyphenate == Constants.EN_TRUE) {
                // consider every hyphenation point as a legal break
                findHyphenationPoints(par);
            } else {
                // try with a higher threshold
                maxAdjustment = 5;
            }

            if (!findBreakingPoints(par, lineWidth, maxAdjustment, false)) {
                // the second try failed too, try with a huge threshold;
                // if this fails too, use a different algorithm
                log.debug("No set of breaking points found with maxAdjustment = " + maxAdjustment
                          + (hyphProps.hyphenate == Constants.EN_TRUE ? " and hyphenation" : ""));
                maxAdjustment = 20;
                if (!findBreakingPoints(par, lineWidth, maxAdjustment, true)) {
                    log.debug("No set of breaking points found, using first-fit algorithm");
                }
            }
        }
    }
    
    private boolean findBreakingPoints(Paragraph par, int lineWidth,
            double threshold, boolean force) {
        KnuthParagraph knuthPara = new KnuthParagraph(par);
        int lines = knuthPara.findBreakPoints(lineWidth, threshold, force);
        if (lines == 0) {
            return false;
        }
        
        for (int i = lines-1; i >= 0; i--) {
            int line = i+1;
            if (log.isTraceEnabled()) {
                log.trace("Making line from " + knuthPara.getStart(i) + " to " + 
                           knuthPara.getEnd(i));
            }
            // compute indent and adjustment ratio, according to
            // the value of text-align and text-align-last

            int difference = knuthPara.getDifference(i);
            if (line == lines) {
                difference += par.lineFillerWidth;
            }    
            int textAlign = (line < lines)
                ? bTextAlignment : bTextAlignmentLast;
            int indent = (textAlign == EN_CENTER)
                ? difference / 2
                : (textAlign == EN_END) ? difference : 0;
            indent += (line == 1 && knuthParagraphs.indexOf(par) == 0)
                ? textIndent.getValue() : 0;
            double ratio = (textAlign == EN_JUSTIFY)
                ? knuthPara.getAdjustRatio(i) : 0;

            int start = knuthPara.getStart(i);
            int end = knuthPara.getEnd(i);
            makeLineBreakPosition(par, start, end, 0, ratio, indent);
        }
        return true;        
    }

    private void makeLineBreakPosition(Paragraph par,
                                       int firstElementIndex, int lastElementIndex,
                                       int insertIndex, double ratio, int indent) {
        // line height calculation

        int halfLeading = (lineHeight - lead - follow) / 2;
        // height above the main baseline
        int lineLead = lead + halfLeading;
        // maximum size of top and bottom alignment
        int maxtb = follow + halfLeading;
        // max size of middle alignment above and below the middle baseline
        int middlefollow = maxtb;

        ListIterator inlineIterator
            = par.listIterator(firstElementIndex);
        for (int j = firstElementIndex;
             j <= lastElementIndex;
             j++) {
            KnuthElement element = (KnuthElement) inlineIterator.next();
            if (element.isBox()) {
                if (((KnuthBox) element).getLead() > lineLead) {
                    lineLead = ((KnuthBox) element).getLead();
                }
                if (((KnuthBox) element).getTotal() > maxtb) {
                    maxtb = ((KnuthBox) element).getTotal();
                }
                if (((KnuthBox) element).getMiddle() > lineLead + middleShift) {
                    lineLead += ((KnuthBox) element).getMiddle()
                                - lineLead - middleShift;
                }
                if (((KnuthBox) element).getMiddle() > middlefollow - middleShift) {
                    middlefollow += ((KnuthBox) element).getMiddle()
                                    - middlefollow + middleShift;
                }
            }
        }

        if (maxtb - lineLead > middlefollow) {
                    middlefollow = maxtb - lineLead;
        }

        breakpoints.add(insertIndex,
                        new LineBreakPosition(this,
                                              knuthParagraphs.indexOf(par),
                                              lastElementIndex ,
                                              ratio, 0, indent,
                                              lineLead + middlefollow,
                                              lineLead));
    }



    /**
     * find hyphenation points for every word int the current paragraph
     * @ param currPar the paragraph whose words will be hyphenated
     */
    private void findHyphenationPoints(Paragraph currPar){
        // hyphenate every word
        ListIterator currParIterator
            = currPar.listIterator(currPar.ignoreAtStart);
        // list of TLM involved in hyphenation
        LinkedList updateList = new LinkedList();
        KnuthElement firstElement = null;
        KnuthElement nextElement = null;
        // current InlineLevelLayoutManager
        InlineLevelLayoutManager currLM = null;
        // number of KnuthBox elements containing word fragments
        int boxCount;
        // number of auxiliary KnuthElements between KnuthBoxes
        int auxCount;
        StringBuffer sbChars = null;

        // find all hyphenation points
        while (currParIterator.hasNext()) {
            firstElement = (KnuthElement) currParIterator.next();
            // 
            if (firstElement.getLayoutManager() != currLM) {
                currLM = (InlineLevelLayoutManager) firstElement.getLayoutManager();
                if (currLM != null) { 
                    updateList.add(new Update(currLM, currParIterator.previousIndex()));
                } else {
                    break;
                }
            }

            // collect word fragments, ignoring auxiliary elements;
            // each word fragment was created by a different TextLM
            if (firstElement.isBox() && !firstElement.isAuxiliary()) {
                boxCount = 1;
                auxCount = 0;
                sbChars = new StringBuffer();
                currLM.getWordChars(sbChars, firstElement.getPosition());
                // look if next elements are boxes too
                while (currParIterator.hasNext()) {
                    nextElement = (KnuthElement) currParIterator.next();
                    if (nextElement.isBox() && !nextElement.isAuxiliary()) {
                        // a non-auxiliary KnuthBox: append word chars
                        if (currLM != nextElement.getLayoutManager()) {
                            currLM = (InlineLevelLayoutManager) nextElement.getLayoutManager();
                            updateList.add(new Update(currLM, currParIterator.previousIndex()));
                        }
                        // append text to recreate the whole word
                        boxCount ++;
                        currLM.getWordChars(sbChars, nextElement.getPosition());
                    } else if (!nextElement.isAuxiliary()) {
                        // a non-auxiliary non-box KnuthElement: stop
                        // go back to the last box or auxiliary element
                        currParIterator.previous(); 
                        break;
                    } else {
                        // an auxiliary KnuthElement: simply ignore it
                        auxCount ++;
                    }
                }
                log.trace(" Word to hyphenate: " + sbChars.toString());
                // find hyphenation points
                HyphContext hc = getHyphenContext(sbChars);
                // ask each LM to hyphenate its word fragment
                if (hc != null) {
                    KnuthElement element = null;
                    for (int i = 0; i < (boxCount + auxCount); i++) {
                        currParIterator.previous();
                    }
                    for (int i = 0; i < (boxCount + auxCount); i++) {
                        element = (KnuthElement) currParIterator.next();
                        if (element.isBox() && !element.isAuxiliary()) {
                            ((InlineLevelLayoutManager)
                             element.getLayoutManager()).hyphenate(element.getPosition(), hc);
                        } else {
                            // nothing to do, element is an auxiliary KnuthElement
                        }
                    }
                }
            }
        }

        // create iterator for the updateList
        ListIterator updateListIterator = updateList.listIterator();
        Update currUpdate = null;
        //int iPreservedElements = 0;
        int iAddedElements = 0;
        //int iRemovedElements = 0;

        while (updateListIterator.hasNext()) {
            // ask the LMs to apply the changes and return 
            // the new KnuthElements to replace the old ones
            currUpdate = (Update) updateListIterator.next();
            int fromIndex = currUpdate.iFirstIndex;
            int toIndex;
            if (updateListIterator.hasNext()) {
                Update nextUpdate = (Update) updateListIterator.next();
                toIndex = nextUpdate.iFirstIndex;
                updateListIterator.previous();
            } else {
                // maybe this is not always correct!
                toIndex = currPar.size() - currPar.ignoreAtEnd
                    - iAddedElements;
            }

            // applyChanges() returns true if the LM modifies its data,
            // so it must return new KnuthElements to replace the old ones
            if (((InlineLevelLayoutManager) currUpdate.inlineLM)
                .applyChanges(currPar.subList(fromIndex + iAddedElements,
                                              toIndex + iAddedElements))) {
                // insert the new KnuthElements
                LinkedList newElements = null;
                newElements
                    = currUpdate.inlineLM.getChangedKnuthElements
                    (currPar.subList(fromIndex + iAddedElements,
                                     toIndex + iAddedElements),
                     flaggedPenalty, effectiveAlignment);
                // remove the old elements
                currPar.subList(fromIndex + iAddedElements,
                                toIndex + iAddedElements).clear();
                // insert the new elements
                currPar.addAll(fromIndex + iAddedElements, newElements);
                iAddedElements += newElements.size() - (toIndex - fromIndex);
            }
        }
        updateListIterator = null;
        updateList.clear();
    }

    private void resetBP(BreakPoss resetBP) {
        if (resetBP == null) {
            reset((Position) null);
        } else {
            while (vecInlineBreaks.get(vecInlineBreaks.size() - 1) != resetBP) {
                vecInlineBreaks.remove(vecInlineBreaks.size() - 1);
            }
            reset(resetBP.getPosition());
        }
    }

    private void reset() {
        resetBP(prevBP);
    }

    protected boolean couldEndLine(BreakPoss bp) {
        if (bp.canBreakAfter()) {
            return true; // no keep, ends on break char
        } else if (bp.isSuppressible()) {
            // NOTE: except at end of content for this LM!!
            // Never break after only space chars or any other sequence
            // of areas which would be suppressed at the end of the line.
            return false;
        } else {
            // See if could break before next area
            // TODO: do we need to set anything on the layout context?
            LayoutContext lc = new LayoutContext(0);
            LayoutManager nextLM = getChildLM();
            return (nextLM == null || nextLM.canBreakBefore(lc));
        }
    }

    private BreakPoss getBestBP(List vecPossEnd) {
        if (vecPossEnd.size() == 1) {
            return ((BreakCost) vecPossEnd.get(0)).getBP();
        }
        // Choose the best break (use a sort on cost!)
        Iterator iter = vecPossEnd.iterator();
        int minCost = Integer.MAX_VALUE;
        BreakPoss bestBP = null;
        while (iter.hasNext()) {
            BreakCost bc = (BreakCost) iter.next();
            if (bc.getCost() < minCost) {
                minCost = bc.getCost();
                bestBP = bc.getBP();
            }
        }
        return bestBP;
    }

    /** Line area is always considered to act as a fence. */
    protected boolean hasLeadingFence(boolean bNotFirst) {
        return true;
    }

    /** Line area is always considered to act as a fence. */
    protected boolean hasTrailingFence(boolean bNotLast) {
        return true;
    }

    /** Return true if we are at the end of this LM,
        and BPs after prev have been added to vecInlineBreaks
        and all breakposs in vecInlineBreaks
        back to and excluding prev are suppressible */
    private boolean condAllAreSuppressible(BreakPoss prev) {
        if (!isFinished()) {
            return false;
        }
        if (vecInlineBreaks.get(vecInlineBreaks.size() - 1) == prev) {
            return false;
        }
        return allAreSuppressible(prev);
    }

    /** Test whether all breakposs in vecInlineBreaks
        back to and excluding prev are suppressible */
    private boolean allAreSuppressible(BreakPoss prev) {
        ListIterator bpIter =
            vecInlineBreaks.listIterator(vecInlineBreaks.size());
        boolean allAreSuppressible = true;
        BreakPoss bp;
        while (bpIter.hasPrevious()
               && (bp = (BreakPoss) bpIter.previous()) != prev
               && (allAreSuppressible = bp.isSuppressible())) {
        }
        return allAreSuppressible;
    }

    /** Remove all BPs from the end back to and excluding prev
        from vecInlineBreaks*/
    private void removeAllBP(BreakPoss prev) {
        int iPrev;
        if (prev == null) {
            vecInlineBreaks.clear();
        } else if ((iPrev = vecInlineBreaks.indexOf(prev)) != -1) {
            for (int i = vecInlineBreaks.size()-1; iPrev < i; --i) {
                vecInlineBreaks.remove(i);
            }
        }
    }

    private HyphContext getHyphenContext(StringBuffer sbChars) {
        // Find all hyphenation points in this word
        // (get in an array of offsets)
        // hyphProps are from the block level?.
        // Note that according to the spec,
        // they also "apply to" fo:character.
        // I don't know what that means, since
        // if we change language in the middle of a "word",
        // the effect would seem quite strange!
        // Or perhaps in that case, we say that it's several words.
        // We probably should bring the hyphenation props up from the actual
        // TextLM which generate the hyphenation buffer,
        // since these properties inherit and could be specified
        // on an inline or wrapper below the block level.
        Hyphenation hyph
            = Hyphenator.hyphenate(hyphProps.language,
                                   hyphProps.country, sbChars.toString(),
                                   hyphProps.hyphenationRemainCharacterCount,
                                   hyphProps.hyphenationPushCharacterCount);
        // They hyph structure contains the information we need
        // Now start from prev: reset to that position, ask that LM to get
        // a Position for the first hyphenation offset. If the offset isn't in
        // its characters, it returns null,
        // but must tell how many chars it had.
        // Keep looking at currentBP using next hyphenation point until the
        // returned size is greater than the available size
        // or no more hyphenation points remain. Choose the best break.
        if (hyph != null) {
            return new HyphContext(hyph.getHyphenationPoints());
        } else {
            return null;
        }
    }

    /**
     * Reset the positions to the given position.
     *
     * @param resetPos the position to reset to
     */
    public void resetPosition(Position resetPos) {
        if (resetPos == null) {
            setFinished(false);
            iReturnedLBP = 0;
        } else {
            if (isFinished()) {
                // if isFinished is true, iReturned LBP == breakpoints.size()
                // and breakpoints.get(iReturnedLBP) would generate
                // an IndexOutOfBoundException
                setFinished(false);
                iReturnedLBP--;
            }
            while ((LineBreakPosition) breakpoints.get(iReturnedLBP)
                   != (LineBreakPosition) resetPos) {
                iReturnedLBP--;
            }
            iReturnedLBP++;
        }
    }

    /**
     * Add the areas with the break points.
     *
     * @param parentIter the iterator of break positions
     * @param context the context for adding areas
     */
    public void addAreas(PositionIterator parentIter,
                         LayoutContext context) {
        addAreas(parentIter, 0.0);

        //vecInlineBreaks.clear();
        prevBP = null;
    }

    // Generate and add areas to parent area
    // Set size etc
    // dSpaceAdjust should reference extra space in the BPD
    /**
     * Add the areas with the associated space adjustment.
     *
     * @param parentIter the iterator of breaks positions
     * @param dSpaceAdjust the space adjustment
     */
    public void addAreas(PositionIterator parentIter, double dSpaceAdjust) {
        LayoutManager childLM;
        LayoutContext lc = new LayoutContext(0);
        int iCurrParIndex;
        while (parentIter.hasNext()) {
            ListIterator paragraphIterator = null;
            KnuthElement tempElement = null;
            // the TLM which created the last KnuthElement in this line
            LayoutManager lastLM = null;

            LineBreakPosition lbp = (LineBreakPosition) parentIter.next();
            LineArea lineArea = new LineArea();
            lineArea.setStartIndent(lbp.startIndent);
            lineArea.setBPD(lbp.lineHeight);
            lc.setBaseline(lbp.baseline);
            lc.setLineHeight(lbp.lineHeight);
            lc.setMiddleShift(middleShift);
            setCurrentArea(lineArea);

            iCurrParIndex = lbp.iParIndex;
            Paragraph currPar = (Paragraph) knuthParagraphs.get(iCurrParIndex);
            iEndElement = lbp.getLeafPos();

            // ignore the first elements added by the LineLayoutManager
            iStartElement += (iStartElement == 0) ? currPar.ignoreAtStart : 0;

            // ignore the last elements added by the LineLayoutManager
            iEndElement -= (iEndElement == (currPar.size() - 1))
                ? currPar.ignoreAtEnd : 0;

            // ignore the last element in the line if it is a KnuthGlue object
            paragraphIterator = currPar.listIterator(iEndElement);
            tempElement = (KnuthElement) paragraphIterator.next();
            if (tempElement.isGlue()) {
                iEndElement --;
                // this returns the same KnuthElement
                paragraphIterator.previous();
                tempElement = (KnuthElement) paragraphIterator.previous();
            }
            lastLM = tempElement.getLayoutManager();

            // ignore KnuthGlue and KnuthPenalty objects
            // at the beginning of the line
            paragraphIterator = currPar.listIterator(iStartElement);
            tempElement = (KnuthElement) paragraphIterator.next();
            while (!tempElement.isBox() && paragraphIterator.hasNext()) {
                tempElement = (KnuthElement) paragraphIterator.next();
                iStartElement ++;
            }

            // Add the inline areas to lineArea
            PositionIterator inlinePosIter
                = new KnuthPossPosIter(currPar, iStartElement,
                                       iEndElement + 1);

            iStartElement = lbp.getLeafPos() + 1;
            if (iStartElement == currPar.size()) {
                // advance to next paragraph
                iStartElement = 0;
            }

            lc.setSpaceAdjust(lbp.dAdjust);
            lc.setIPDAdjust(lbp.ipdAdjust);
            lc.setLeadingSpace(new SpaceSpecifier(true));
            lc.setTrailingSpace(new SpaceSpecifier(false));
            lc.setFlags(LayoutContext.RESOLVE_LEADING_SPACE, true);
            setChildContext(lc);
            while ((childLM = inlinePosIter.getNextChildLM()) != null) {
                lc.setFlags(LayoutContext.LAST_AREA, (childLM == lastLM));
                childLM.addAreas(inlinePosIter, lc);
                lc.setLeadingSpace(lc.getTrailingSpace());
                lc.setTrailingSpace(new SpaceSpecifier(false));
            }
            // when can this be null?
            if (lc.getTrailingSpace() != null) {
                addSpace(lineArea, lc.getTrailingSpace().resolve(true),
                         lc.getSpaceAdjust());
            }
            parentLM.addChildArea(lineArea);
        }
        setCurrentArea(null); // ?? necessary
    }

    /**
     * Add an unresolved area.
     * If a child layout manager needs to add an unresolved area
     * for page reference or linking then this intercepts it for
     * line area handling.
     * A line area may need to have the inline areas adjusted
     * to properly fill the line area. This adds a resolver that
     * resolves the inline area and can do the necessary
     * adjustments to the line and inline areas.
     *
     * @param id the id reference of the resolvable
     * @param res the resolvable object
     */
    public void addUnresolvedArea(String id, Resolvable res) {
        // create a resolvable class that handles ipd
        // adjustment for the current line

        parentLM.addUnresolvedArea(id, res);
    }

}

