/*
 * 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.fo.flow.table;

import java.util.List;

import org.apache.fop.fo.Constants;
import org.apache.fop.layoutmgr.Keep;
import org.apache.fop.layoutmgr.table.TableRowIterator;
import org.apache.fop.traits.MinOptMax;
import org.apache.fop.util.BreakUtil;

/**
 * This class represents an effective row in a table and holds a list of grid units occupying
 * the row as well as some additional values.
 */
public class EffRow {

    /** Indicates that the row is the first in a table-body */
    public static final int FIRST_IN_PART = GridUnit.FIRST_IN_PART;
    /** Indicates that the row is the last in a table-body */
    public static final int LAST_IN_PART = GridUnit.LAST_IN_PART;

    private List gridUnits = new java.util.ArrayList();
    private int index;
    /** One of HEADER, FOOTER, BODY */
    private int bodyType;
    private MinOptMax height;
    private MinOptMax explicitHeight;

    /**
     * Creates a new effective row instance.
     * @param index index of the row
     * @param bodyType type of body (one of HEADER, FOOTER, BODY as found on TableRowIterator)
     * @param gridUnits the grid units this row is made of
     */
    public EffRow(int index, int bodyType, List gridUnits) {
        this.index = index;
        this.bodyType = bodyType;
        this.gridUnits = gridUnits;
        // TODO this is ugly, but we may eventually be able to do without that index
        for (Object gu : gridUnits) {
            if (gu instanceof PrimaryGridUnit) {
                ((PrimaryGridUnit) gu).setRowIndex(index);
            }
        }
    }

    /** @return the index of the EffRow in the sequence of rows */
    public int getIndex() {
        return this.index;
    }

    /**
     * @return an indicator what type of body this EffRow is in (one of HEADER, FOOTER, BODY
     * as found on TableRowIterator)
     */
    public int getBodyType() {
        return this.bodyType;
    }

    /** @return the table-row FO for this EffRow, or null if there is no table-row. */
    public TableRow getTableRow() {
        return getGridUnit(0).getRow();
    }

    /**
     * Returns the calculated height for this EffRow, including the cells'
     * bpds/paddings/borders, and the table's border-separation.
     *
     * @return the row's height
     */
    public MinOptMax getHeight() {
        return this.height;
    }

    /**
     * Sets the calculated height for this EffRow, including everything (cells' bpds,
     * paddings, borders, and border-separation).
     *
     * @param mom the calculated height
     */
    public void setHeight(MinOptMax mom) {
        this.height = mom;
    }

    /** @return the explicit height of the EffRow (as specified through properties) */
    public MinOptMax getExplicitHeight() {
        return this.explicitHeight;
    }

    /**
     * Sets the height for this row that resulted from the explicit height properties specified
     * by the user.
     * @param mom the height
     */
    public void setExplicitHeight(MinOptMax mom) {
        this.explicitHeight = mom;
    }

    /** @return the list of GridUnits for this EffRow */
    public List getGridUnits() {
        return gridUnits;
    }

    /**
     * Returns the grid unit at a given position.
     * @param column index of the grid unit in the row (zero based)
     * @return the requested grid unit.
     */
    public GridUnit getGridUnit(int column) {
        return (GridUnit)gridUnits.get(column);
    }

    /**
     * Returns the grid unit at a given position. In contrast to getGridUnit() this
     * method returns null if there's no grid unit at the given position. The number of
     * grid units for row x can be smaller than the number of grid units for row x-1.
     * @param column index of the grid unit in the row (zero based)
     * @return the requested grid unit or null if there's no grid unit at this position.
     */
    public GridUnit safelyGetGridUnit(int column) {
        if (column < gridUnits.size()) {
            return (GridUnit)gridUnits.get(column);
        } else {
            return null;
        }
    }

    /**
     * Returns a flag for this effective row. Only a subset of the flags on GridUnit is supported.
     * The flag is determined by inspecting flags on the EffRow's GridUnits.
     * @param which the requested flag (one of {@link EffRow#FIRST_IN_PART} or {@link
     * EffRow#LAST_IN_PART})
     * @return true if the flag is set
     */
    public boolean getFlag(int which) {
        if (which == FIRST_IN_PART) {
            return getGridUnit(0).getFlag(GridUnit.FIRST_IN_PART);
        } else if (which == LAST_IN_PART) {
            return getGridUnit(0).getFlag(GridUnit.LAST_IN_PART);
        } else {
            throw new IllegalArgumentException("Illegal flag queried: " +  which);
        }
    }

    /**
     * Returns the strength of the keep constraint if the enclosing (if any) fo:table-row element
     * of this row, or if any of the cells starting on this row, have keep-with-previous set.
     *
     * @return the strength of the keep-with-previous constraint
     */
    public Keep getKeepWithPrevious() {
        Keep keep = Keep.KEEP_AUTO;
        TableRow row = getTableRow();
        if (row != null) {
            keep = Keep.getKeep(row.getKeepWithPrevious());
        }
        for (Object gridUnit : gridUnits) {
            GridUnit gu = (GridUnit) gridUnit;
            if (gu.isPrimary()) {
                keep = keep.compare(gu.getPrimary().getKeepWithPrevious());
            }
        }
        return keep;
    }

    /**
     * Returns the strength of the keep constraint if the enclosing (if any) fo:table-row element
     * of this row, or if any of the cells ending on this row, have keep-with-next set.
     *
     * @return the strength of the keep-with-next constraint
     */
    public Keep getKeepWithNext() {
        Keep keep = Keep.KEEP_AUTO;
        TableRow row = getTableRow();
        if (row != null) {
            keep = Keep.getKeep(row.getKeepWithNext());
        }
        for (Object gridUnit : gridUnits) {
            GridUnit gu = (GridUnit) gridUnit;
            if (!gu.isEmpty() && gu.getColSpanIndex() == 0 && gu.isLastGridUnitRowSpan()) {
                keep = keep.compare(gu.getPrimary().getKeepWithNext());
            }
        }
        return keep;
    }

    /**
     * Returns the keep-together strength for this element. Note: The keep strength returned does
     * not take the parent table's keeps into account!
     * @return the keep-together strength
     */
    public Keep getKeepTogether() {
        TableRow row = getTableRow();
        Keep keep = Keep.KEEP_AUTO;
        if (row != null) {
            keep = Keep.getKeep(row.getKeepTogether());
        }
        return keep;
    }

    /**
     * Returns the break class for this row. This is a combination of break-before set on
     * the first children of any cells starting on this row.
     * <p><strong>Note:</strong> this method doesn't take into account break-before set on
     * the enclosing fo:table-row element, if any, as it must be ignored if the row
     * belongs to a group of spanned rows (see XSL-FO 1.1, 7.20.2).
     * <p><strong>Note:</strong> this works only after getNextKuthElements on the
     * corresponding TableCellLM have been called!</p>
     *
     * @return one of {@link Constants#EN_AUTO}, {@link Constants#EN_COLUMN}, {@link
     * Constants#EN_PAGE}, {@link Constants#EN_EVEN_PAGE}, {@link Constants#EN_ODD_PAGE}
     */
    public int getBreakBefore() {
        int breakBefore = Constants.EN_AUTO;
        for (Object gridUnit : gridUnits) {
            GridUnit gu = (GridUnit) gridUnit;
            if (gu.isPrimary()) {
                breakBefore = BreakUtil.compareBreakClasses(breakBefore,
                        gu.getPrimary().getBreakBefore());
            }
        }
        return breakBefore;
    }

    /**
     * Returns the break class for this row. This is a combination of break-after set on
     * the last children of any cells ending on this row.
     * <p><strong>Note:</strong> this method doesn't take into account break-after set on
     * the enclosing fo:table-row element, if any, as it must be ignored if the row
     * belongs to a group of spanned rows (see XSL-FO 1.1, 7.20.1).
     * <p><strong>Note:</strong> this works only after getNextKuthElements on the
     * corresponding TableCellLM have been called!</p>
     *
     * @return one of {@link Constants#EN_AUTO}, {@link Constants#EN_COLUMN}, {@link
     * Constants#EN_PAGE}, {@link Constants#EN_EVEN_PAGE}, {@link Constants#EN_ODD_PAGE}
     */
    public int getBreakAfter() {
        int breakAfter = Constants.EN_AUTO;
        for (Object gridUnit : gridUnits) {
            GridUnit gu = (GridUnit) gridUnit;
            if (!gu.isEmpty() && gu.getColSpanIndex() == 0 && gu.isLastGridUnitRowSpan()) {
                breakAfter = BreakUtil.compareBreakClasses(breakAfter,
                        gu.getPrimary().getBreakAfter());
            }
        }
        return breakAfter;
    }

    /** {@inheritDoc} */
    public String toString() {
        StringBuffer sb = new StringBuffer("EffRow {");
        sb.append(index);
        if (getBodyType() == TableRowIterator.BODY) {
            sb.append(" in body");
        } else if (getBodyType() == TableRowIterator.HEADER) {
            sb.append(" in header");
        } else {
            sb.append(" in footer");
        }
        sb.append(", ").append(height);
        sb.append(", ").append(explicitHeight);
        sb.append(", ").append(gridUnits.size()).append(" gu");
        sb.append("}");
        return sb.toString();
    }
}
