/*
 * Copyright 1999-2004 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: InlineLayoutManager.java 198205 2004-12-22 08:54:10Z spepping $ */

package org.apache.fop.layoutmgr;

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

import org.apache.fop.fo.FObj;
import org.apache.fop.fo.flow.InlineLevel;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.CommonMarginInline;
import org.apache.fop.fo.properties.SpaceProperty;
import org.apache.fop.traits.MinOptMax;
import org.apache.fop.traits.SpaceVal;

/**
 * LayoutManager for objects which stack children in the inline direction,
 * such as Inline or Line
 */
public class InlineLayoutManager extends InlineStackingLayoutManager 
                                         implements InlineLevelLayoutManager {
    private InlineLevel fobj;

    private CommonMarginInline inlineProps = null;
    private CommonBorderPaddingBackground borderProps = null;

    /**
     * Create an inline layout manager.
     * This is used for fo's that create areas that
     * contain inline areas.
     *
     * @param node the formatting object that creates the area
     */
    // The node should be FObjMixed
    public InlineLayoutManager(InlineLevel node) {
        super(node);
        fobj = node;
    }
    
    /**
     * @see org.apache.fop.layoutmgr.AbstractLayoutManager#initProperties()
     */
    protected void initProperties() {
        inlineProps = fobj.getCommonMarginInline();
        borderProps = fobj.getCommonBorderPaddingBackground();

        int iPad = borderProps.getPadding(CommonBorderPaddingBackground.BEFORE, false);
        iPad += borderProps.getBorderWidth(CommonBorderPaddingBackground.BEFORE,
                                             false);
        iPad += borderProps.getPadding(CommonBorderPaddingBackground.AFTER, false);
        iPad += borderProps.getBorderWidth(CommonBorderPaddingBackground.AFTER, false);
        extraBPD = new MinOptMax(iPad);
    }

    protected MinOptMax getExtraIPD(boolean bNotFirst, boolean bNotLast) {
        int iBP = borderProps.getPadding(CommonBorderPaddingBackground.START,
                                           bNotFirst);
        iBP += borderProps.getBorderWidth(CommonBorderPaddingBackground.START,
                                            bNotFirst);
        iBP += borderProps.getPadding(CommonBorderPaddingBackground.END, bNotLast);
        iBP += borderProps.getBorderWidth(CommonBorderPaddingBackground.END, bNotLast);
        return new MinOptMax(iBP);
    }


    protected boolean hasLeadingFence(boolean bNotFirst) {
        int iBP = borderProps.getPadding(CommonBorderPaddingBackground.START,
                                           bNotFirst);
        iBP += borderProps.getBorderWidth(CommonBorderPaddingBackground.START,
                                            bNotFirst);
        return (iBP > 0);
    }

    protected boolean hasTrailingFence(boolean bNotLast) {
        int iBP = borderProps.getPadding(CommonBorderPaddingBackground.END, bNotLast);
        iBP += borderProps.getBorderWidth(CommonBorderPaddingBackground.END, bNotLast);
        return (iBP > 0);
    }

    protected SpaceProperty getSpaceStart() {
        return inlineProps.spaceStart;
    }
    protected SpaceProperty getSpaceEnd() {
        return inlineProps.spaceEnd;
    }
    
    /**
     * Return value indicating whether the next area to be generated could
     * start a new line. This should only be called in the "START" condition
     * if a previous inline BP couldn't end the line.
     * Return true if any space-start, border-start or padding-start, else
     * propagate to first child LM
     */
    public boolean canBreakBefore(LayoutContext context) {
        if (new SpaceVal(inlineProps.spaceStart).getSpace().min > 0 || hasLeadingFence(false)) {
            return true;
        }
        return super.canBreakBefore(context);
    }
    
    protected void setTraits(boolean bNotFirst, boolean bNotLast) {
        
        // Add border and padding to current area and set flags (FIRST, LAST ...)
        TraitSetter.setBorderPaddingTraits(getCurrentArea(),
                                           borderProps, bNotFirst, bNotLast);

        if (borderProps != null) {
            TraitSetter.addBorders(getCurrentArea(), borderProps);
            TraitSetter.addBackground(getCurrentArea(), borderProps);
        }
    }

    public LinkedList getNextKnuthElements(LayoutContext lc, int alignment) {
        InlineLevelLayoutManager curLM;

        // the list returned by child LM
        LinkedList returnedList;
        KnuthElement returnedElement;

        // the list which will be returned to the parent LM
        LinkedList returnList = new LinkedList();

        SpaceSpecifier leadingSpace = lc.getLeadingSpace();

        if (lc.startsNewArea()) {
            // First call to this LM in new parent "area", but this may
            // not be the first area created by this inline
            childLC = new LayoutContext(lc);
            if (getSpaceStart() != null) {
                lc.getLeadingSpace().addSpace(new SpaceVal(getSpaceStart()));
            }

            // Check for "fence"
            if (hasLeadingFence(!lc.isFirstArea())) {
                // Reset leading space sequence for child areas
                leadingSpace = new SpaceSpecifier(false);
            }
            // Reset state variables
            clearPrevIPD(); // Clear stored prev content dimensions
        }

        while ((curLM = (InlineLevelLayoutManager) getChildLM()) != null) {
            // get KnuthElements from curLM
            returnedList = curLM.getNextKnuthElements(lc, alignment);
            if (returnedList != null) {
                // "wrap" the Position stored in each element of returnedList
                ListIterator listIter = returnedList.listIterator();
                while (listIter.hasNext()) {
                    returnedElement = (KnuthElement) listIter.next();
                    returnedElement.setPosition
                        (new NonLeafPosition(this,
                                             returnedElement.getPosition()));
                    returnList.add(returnedElement);
                }
                return returnList;
            } else {
                // curLM returned null because it finished;
                // just iterate once more to see if there is another child
            }
        }
        setFinished(true);
        return null;
    }

    public KnuthElement addALetterSpaceTo(KnuthElement element) {
        NonLeafPosition savedPos = (NonLeafPosition) element.getPosition();
        element.setPosition(savedPos.getPosition());

        KnuthElement newElement
            = ((InlineLevelLayoutManager)
               element.getLayoutManager()).addALetterSpaceTo(element);
        newElement.setPosition
            (new NonLeafPosition(this, newElement.getPosition()));
        element.setPosition(savedPos);
        return newElement;
    }

    public void getWordChars(StringBuffer sbChars, Position pos) {
        Position newPos = ((NonLeafPosition) pos).getPosition();
        ((InlineLevelLayoutManager)
         newPos.getLM()).getWordChars(sbChars, newPos);
    }

    public void hyphenate(Position pos, HyphContext hc) {
        Position newPos = ((NonLeafPosition) pos).getPosition();
        ((InlineLevelLayoutManager)
         newPos.getLM()).hyphenate(newPos, hc);
    }

    public boolean applyChanges(List oldList) {
        // "unwrap" the Positions stored in the elements
        ListIterator oldListIterator = oldList.listIterator();
        KnuthElement oldElement;
        while (oldListIterator.hasNext()) {
            oldElement = (KnuthElement) oldListIterator.next();
            oldElement.setPosition
                (((NonLeafPosition) oldElement.getPosition()).getPosition());
        }
        // reset the iterator
        oldListIterator = oldList.listIterator();

        InlineLevelLayoutManager prevLM = null;
        InlineLevelLayoutManager currLM;
        int fromIndex = 0;

        boolean bSomethingChanged = false;
        while(oldListIterator.hasNext()) {
            oldElement = (KnuthElement) oldListIterator.next();
            currLM = (InlineLevelLayoutManager) oldElement.getLayoutManager();
            // initialize prevLM
            if (prevLM == null) {
                prevLM = currLM;
            }

            if (currLM != prevLM || !oldListIterator.hasNext()) {
                if (oldListIterator.hasNext()) {
                    bSomethingChanged
                        = prevLM.applyChanges(oldList.subList(fromIndex, oldListIterator.previousIndex()))
                        || bSomethingChanged;
                    prevLM = currLM;
                    fromIndex = oldListIterator.previousIndex();
                } else if (currLM == prevLM) {
                    bSomethingChanged
                        = prevLM.applyChanges(oldList.subList(fromIndex, oldList.size()))
                        || bSomethingChanged;
                } else {
                    bSomethingChanged
                        = prevLM.applyChanges(oldList.subList(fromIndex, oldListIterator.previousIndex()))
                        || bSomethingChanged;
                    bSomethingChanged
                        = currLM.applyChanges(oldList.subList(oldListIterator.previousIndex(), oldList.size()))
                        || bSomethingChanged;
                }
            }
        }

        // "wrap" again the Positions stored in the elements
        oldListIterator = oldList.listIterator();
        while (oldListIterator.hasNext()) {
            oldElement = (KnuthElement) oldListIterator.next();
            oldElement.setPosition
                (new NonLeafPosition(this, oldElement.getPosition()));
        }
        return bSomethingChanged;
    }

    public LinkedList getChangedKnuthElements(List oldList, int flaggedPenalty, int alignment) {
        // "unwrap" the Positions stored in the elements
        ListIterator oldListIterator = oldList.listIterator();
        KnuthElement oldElement;
        while (oldListIterator.hasNext()) {
            oldElement = (KnuthElement) oldListIterator.next();
            oldElement.setPosition
                (((NonLeafPosition) oldElement.getPosition()).getPosition());
        }
        // reset the iterator
        oldListIterator = oldList.listIterator();

        KnuthElement returnedElement;
        LinkedList returnedList = new LinkedList();
        LinkedList returnList = new LinkedList();
        InlineLevelLayoutManager prevLM = null;
        InlineLevelLayoutManager currLM;
        int fromIndex = 0;

        while(oldListIterator.hasNext()) {
            oldElement = (KnuthElement) oldListIterator.next();
            currLM = (InlineLevelLayoutManager) oldElement.getLayoutManager();
            if (prevLM == null) {
                prevLM = currLM;
            }

            if (currLM != prevLM || !oldListIterator.hasNext()) {
                if (oldListIterator.hasNext()) {
                    returnedList.addAll
                        (prevLM.getChangedKnuthElements
                         (oldList.subList(fromIndex,
                                          oldListIterator.previousIndex()),
                          flaggedPenalty, alignment));
                    prevLM = currLM;
                    fromIndex = oldListIterator.previousIndex();
                } else if (currLM == prevLM) {
                    returnedList.addAll
                        (prevLM.getChangedKnuthElements
                         (oldList.subList(fromIndex, oldList.size()),
                          flaggedPenalty, alignment));
                } else {
                    returnedList.addAll
                        (prevLM.getChangedKnuthElements
                         (oldList.subList(fromIndex,
                                          oldListIterator.previousIndex()),
                          flaggedPenalty, alignment));
                    returnedList.addAll
                        (currLM.getChangedKnuthElements
                         (oldList.subList(oldListIterator.previousIndex(),
                                          oldList.size()),
                          flaggedPenalty, alignment));
                }
            }
        }

        // "wrap" the Position stored in each element of returnedList
        ListIterator listIter = returnedList.listIterator();
        while (listIter.hasNext()) {
            returnedElement = (KnuthElement) listIter.next();
            returnedElement.setPosition
                (new NonLeafPosition(this, returnedElement.getPosition()));
            returnList.add(returnedElement);
        }
        return returnList;
    }
}

