/*
 * 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$ */

package org.apache.fop.layoutmgr;

import org.apache.fop.area.Area;
import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.traits.MinOptMax;

/**
 * Base LayoutManager for leaf-node FObj, ie: ones which have no children.
 * These are all inline objects. Most of them cannot be split (Text is
 * an exception to this rule.)
 * This class can be extended to handle the creation and adding of the
 * inline area.
 */
public class LeafNodeLayoutManager extends AbstractLayoutManager {
    /**
     * The inline area that this leafnode will add.
     */
    protected InlineArea curArea = null;
    private int alignment;
    private int lead;
    private MinOptMax ipd;

    /**
     * Create a Leaf node layout mananger.
     */
    public LeafNodeLayoutManager() {
    }

    /**
     * get the inline area.
     * @param context the context used to create the area
     * @return the current inline area for this layout manager
     */
    public InlineArea get(LayoutContext context) {
        return curArea;
    }

    /**
     * Check if this generates inline areas.
     * @return true always since this is an inline area manager
     */
    public boolean generatesInlineAreas() {
        return true;
    }

    /**
     * Check if this inline area is resolved due to changes in
     * page or ipd.
     * Currently not used.
     * @return true if the area is resolved when adding
     */
    public boolean resolved() {
        return false;
    }

    /**
     * Set the current inline area.
     * @param ia the inline area to set for this layout manager
     */
    public void setCurrentArea(InlineArea ia) {
        curArea = ia;
    }

    /**
     * Set the alignment of the inline area.
     * @param al the vertical alignment positioning
     */
    public void setAlignment(int al) {
        alignment = al;
    }

    /**
     * Set the lead for this inline area.
     * The lead is the distance from the top of the object
     * to the baseline.
     * Currently not used.
     * @param l the lead value
     */
    public void setLead(int l) {
        lead = l;
    }

    /**
     * This is a leaf-node, so this method is never called.
     * @param childArea the childArea to add
     */
    public void addChild(Area childArea) {
    }

    /**
     * This is a leaf-node, so this method is never called.
     * @param childArea the childArea to get the parent for
     * @return the parent area
     */
    public Area getParentArea(Area childArea) {
        return null;
    }

    /**
     * Get the next break position.
     * Since this holds an inline area it will return a single
     * break position.
     * @param context the layout context for this inline area
     * @return the break poisition for adding this inline area
     */
    public BreakPoss getNextBreakPoss(LayoutContext context) {
        curArea = get(context);
        if (curArea == null) {
            setFinished(true);
            return null;
        }
        BreakPoss bp = new BreakPoss(new LeafPosition(this, 0),
                                     BreakPoss.CAN_BREAK_AFTER
                                     | BreakPoss.CAN_BREAK_BEFORE | BreakPoss.ISFIRST
                                     | BreakPoss.ISLAST);
        ipd = getAllocationIPD(context.getRefIPD());
        bp.setStackingSize(ipd);
        bp.setNonStackingSize(new MinOptMax(curArea.getHeight()));
        bp.setTrailingSpace(new SpaceSpecifier(false));

        int bpd = curArea.getHeight();
        switch (alignment) {
            case VerticalAlign.MIDDLE:
                bp.setMiddle(bpd / 2 /* - fontLead/2 */);
                bp.setLead(bpd / 2 /* + fontLead/2 */);
            break;
            case VerticalAlign.TOP:
                bp.setTotal(bpd);
            break;
            case VerticalAlign.BOTTOM:
                bp.setTotal(bpd);
            break;
            case VerticalAlign.BASELINE:
            default:
                bp.setLead(bpd);
            break;
        }
        setFinished(true);
        return bp;
    }

    /**
     * Get the allocation ipd of the inline area.
     * This method may be overridden to handle percentage values.
     * @param refIPD the ipd of the parent reference area
     * @return the min/opt/max ipd of the inline area
     */
    protected MinOptMax getAllocationIPD(int refIPD) {
        return new MinOptMax(curArea.getIPD());
    }

    /**
     * Reset the position.
     * If the reset position is null then this inline area should be
     * restarted.
     * @param resetPos the position to reset.
     */
    public void resetPosition(Position resetPos) {
        // only reset if setting null, start again
        if (resetPos == null) {
            setFinished(false);
        }
    }

    /**
     * Add the area for this layout manager.
     * This adds the single inline area to the parent.
     * @param posIter the position iterator
     * @param context the layout context for adding the area
     */
    public void addAreas(PositionIterator posIter, LayoutContext context) {
        parentLM.addChild(curArea);

        addID();

        offsetArea(context);
        widthAdjustArea(context);

        while (posIter.hasNext()) {
            posIter.next();
        }
    }

    /**
     * Offset this area.
     * Offset the inline area in the bpd direction when adding the
     * inline area.
     * This is used for vertical alignment.
     * Subclasses should override this if necessary.
     * @param context the layout context used for adding the area
     */
    protected void offsetArea(LayoutContext context) {
        int bpd = curArea.getHeight();
        switch (alignment) {
            case VerticalAlign.MIDDLE:
                curArea.setOffset(context.getBaseline() - bpd / 2 /* - fontLead/2 */);
            break;
            case VerticalAlign.TOP:
                //curArea.setOffset(0);
            break;
            case VerticalAlign.BOTTOM:
                curArea.setOffset(context.getLineHeight() - bpd);
            break;
            case VerticalAlign.BASELINE:
            default:
                curArea.setOffset(context.getBaseline() - bpd);
            break;
        }
    }

    /**
     * Adjust the width of the area when adding.
     * This uses the min/opt/max values to adjust the with
     * of the inline area by a percentage.
     * @param context the layout context for adding this area
     */
    protected void widthAdjustArea(LayoutContext context) {
        double dAdjust = context.getIPDAdjust();
        int width = ipd.opt;
        if (dAdjust < 0) {
            width = (int)(width + dAdjust * (ipd.opt - ipd.min));
        } else if (dAdjust > 0) {
            width = (int)(width + dAdjust * (ipd.max - ipd.opt));
        }
        curArea.setWidth(width);
    }

    /**
     * Check if can break before this area.
     * @param context the layout context to check for the break
     * @return true if can break before this area in the context
     */
    public boolean canBreakBefore(LayoutContext context) {
        return true;
    }
}

