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

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

import org.apache.fop.datatypes.LengthBase;
import org.apache.fop.datatypes.PercentBaseContext;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.flow.ChangeBar;

/**
 * The base class for nearly all LayoutManagers.
 * Provides the functionality for merging the {@link LayoutManager}
 * and the {@link org.apache.fop.datatypes.PercentBaseContext} interfaces
 * into a common base class for all higher LayoutManagers.
 */
public abstract class AbstractBaseLayoutManager
    implements LayoutManager, PercentBaseContext {

    /** Indicator if this LM generates reference areas. */
    protected boolean generatesReferenceArea;
    /** Indicator if this LM generates block areas. */
    protected boolean generatesBlockArea;
    /** The formatting object for this LM. */
    protected final FObj fobj;

    /**
     * logging instance
     */
    private static final Log LOG = LogFactory.getLog(AbstractBaseLayoutManager.class);

    private boolean fromFootnote;

    /**
     * Abstract base layout manager.
     */
    public AbstractBaseLayoutManager() {
        fobj = null;
    }

    /**
     * Abstract base layout manager.
     *
     * @param fo the formatting object for this layout manager
     */
    public AbstractBaseLayoutManager(FObj fo) {
        if (fo == null) {
            throw new IllegalStateException("Null formatting object found.");
        }
        this.fobj = fo;
        setGeneratesReferenceArea(fo.generatesReferenceAreas());
    }

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

    /** {@inheritDoc} */
    public int getBaseLength(int lengthBase, FObj fobjx) {
        if (fobjx == this.fobj) {
            switch (lengthBase) {
            case LengthBase.CONTAINING_BLOCK_WIDTH:
                return getAncestorBlockAreaIPD();
            case LengthBase.CONTAINING_BLOCK_HEIGHT:
                return getAncestorBlockAreaBPD();
            case LengthBase.PARENT_AREA_WIDTH:
                return getParentAreaIPD();
            case LengthBase.CONTAINING_REFAREA_WIDTH:
                return getReferenceAreaIPD();
            default:
                LOG.error("Unknown base type for LengthBase:" + lengthBase);
                return 0;
            }
        } else {
            LayoutManager lm = getParent();
            while (lm != null && fobjx != lm.getFObj()) {
                lm = lm.getParent();
            }
            if (lm != null) {
                return lm.getBaseLength(lengthBase, fobjx);
            }
        }
        LOG.error("Cannot find LM to handle given FO for LengthBase. ("
                + fobjx.getContextInfo() + ")");
        return 0;
    }

    /**
     * Find the first ancestor area that is a block area
     * and returns its IPD.
     * @return the ipd of the ancestor block area
     */
    protected int getAncestorBlockAreaIPD() {
        LayoutManager lm = getParent();
        while (lm != null) {
            if (lm.getGeneratesBlockArea() && !lm.getGeneratesLineArea()) {
                return lm.getContentAreaIPD();
            }
            lm = lm.getParent();
        }
        LOG.error("No parent LM found");
        return 0;
    }

    /**
     * Find the first ancestor area that is a block area
     * and returns its BPD.
     * @return the bpd of the ancestor block area
     */
    protected int getAncestorBlockAreaBPD() {
        LayoutManager lm = getParent();
        while (lm != null) {
            if (lm.getGeneratesBlockArea() && !lm.getGeneratesLineArea()) {
                return lm.getContentAreaBPD();
            }
            lm = lm.getParent();
        }
        LOG.error("No parent LM found");
        return 0;
    }

    /**
     * Find the parent area and returns its IPD.
     * @return the ipd of the parent area
     */
    protected int getParentAreaIPD() {
        LayoutManager lm = getParent();
        if (lm != null) {
            return lm.getContentAreaIPD();
        }
        LOG.error("No parent LM found");
        return 0;
    }

    /**
     * Find the parent area and returns its BPD.
     * @return the bpd of the parent area
     */
    protected int getParentAreaBPD() {
        LayoutManager lm = getParent();
        if (lm != null) {
            return lm.getContentAreaBPD();
        }
        LOG.error("No parent LM found");
        return 0;
    }

    /**
     * Find the first ancestor area that is a reference area
     * and returns its IPD.
     * @return the ipd of the ancestor reference area
     */
    public int getReferenceAreaIPD() {
        LayoutManager lm = getParent();
        while (lm != null) {
            if (lm.getGeneratesReferenceArea()) {
                return lm.getContentAreaIPD();
            }
            lm = lm.getParent();
        }
        LOG.error("No parent LM found");
        return 0;
    }

    /**
     * Find the first ancestor area that is a reference area
     * and returns its BPD.
     * @return the bpd of the ancestor reference area
     */
    protected int getReferenceAreaBPD() {
        LayoutManager lm = getParent();
        while (lm != null) {
            if (lm.getGeneratesReferenceArea()) {
                return lm.getContentAreaBPD();
            }
            lm = lm.getParent();
        }
        LOG.error("No parent LM found");
        return 0;
    }

    /**
     * {@inheritDoc}
     * <i>NOTE: Should be overridden by subclasses.
     * Default implementation throws an <code>UnsupportedOperationException</code>.</i>
     */
    public int getContentAreaIPD() {
        throw new UnsupportedOperationException(
                "getContentAreaIPD() called when it should have been overridden");
    }

    public int getContentAreaBPD() {
        return 0;
    }

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

    /**
     * Lets implementing LM set the flag indicating if they
     * generate reference areas.
     * @param generatesReferenceArea if true the areas generates by this LM are
     * reference areas.
     */
    protected void setGeneratesReferenceArea(boolean generatesReferenceArea) {
        this.generatesReferenceArea = generatesReferenceArea;
    }

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

    /**
     * Lets implementing LM set the flag indicating if they
     * generate block areas.
     * @param generatesBlockArea if true the areas generates by this LM are block areas.
     */
    protected void setGeneratesBlockArea(boolean generatesBlockArea) {
        this.generatesBlockArea = generatesBlockArea;
    }

    /** {@inheritDoc} */
    public boolean getGeneratesLineArea() {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public FObj getFObj() {
        return fobj;
    }

    /**
     * Returns the active change bar list.
     *
     * @return The active change bar list
     */
    public List<ChangeBar> getChangeBarList() {
       if (fobj == null) {
           return null;
       } else {
           return fobj.getChangeBarList();
       }
    }

    /** {@inheritDoc} */
    public void reset() {
        throw new UnsupportedOperationException("Not implemented");
    }

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

    /** {@inheritDoc} */
    public List<ListElement> getNextKnuthElements(LayoutContext context, int alignment, Stack lmStack,
            Position positionAtIPDChange, LayoutManager restartAtLM) {
        throw new UnsupportedOperationException("Not implemented");
    }

    public void preserveChildrenAtEndOfLayout() {

    }

    public void recreateChildrenLMs() {

    }

    public boolean isFromFootnote() {
        return fromFootnote || (getParent() != null && getParent().isFromFootnote());
    }

    public void setFromFootnote(boolean fromFootnote) {
        this.fromFootnote = fromFootnote;
    }
}
