/*
 * 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.ArrayList;
import java.util.List;

import org.apache.fop.fo.Constants;
import org.apache.fop.fo.ValidationException;


/**
 * A row group builder optimised for a fixed number of columns, known before the parsing
 * of cells starts (that is, if the fo:table has explicit fo:table-column children).
 */
class FixedColRowGroupBuilder extends RowGroupBuilder {

    /** Number of columns in the corresponding table. */
    private int numberOfColumns;

    private TableRow currentTableRow;

    /** 0-based, index in the row group. */
    private int currentRowIndex;

    /** The rows belonging to this row group. List of List of {@link GridUnit}s. */
    private List<List<GridUnit>> rows;

    private boolean firstInPart = true;

    /** The last encountered row. This is the last row of the table if it has no footer. */
    private List lastRow;

    private BorderResolver borderResolver;

    FixedColRowGroupBuilder(Table t) {
        super(t);
        numberOfColumns = t.getNumberOfColumns();
        if (t.isSeparateBorderModel()) {
            borderResolver = new SeparateBorderResolver();
        } else {
            borderResolver = new CollapsingBorderResolver(t);
        }
        initialize();
    }

    /**
     * Prepares this builder for creating a new row group.
     */
    private void initialize() {
        rows = new ArrayList();
        currentRowIndex = 0;
    }

    /** {@inheritDoc} */
    void addTableCell(TableCell cell) {
        for (int i = rows.size(); i < currentRowIndex + cell.getNumberRowsSpanned(); i++) {
            List effRow = new ArrayList(numberOfColumns);
            for (int j = 0; j < numberOfColumns; j++) {
                effRow.add(null);
            }
            rows.add(effRow);
        }
        int columnIndex = cell.getColumnNumber() - 1;
        PrimaryGridUnit pgu = new PrimaryGridUnit(cell, columnIndex);
        List row = (List) rows.get(currentRowIndex);
        row.set(columnIndex, pgu);
        // TODO
        GridUnit[] cellRow = new GridUnit[cell.getNumberColumnsSpanned()];
        cellRow[0] = pgu;
        for (int j = 1; j < cell.getNumberColumnsSpanned(); j++) {
            GridUnit gu = new GridUnit(pgu, j, 0);
            row.set(columnIndex + j, gu);
            cellRow[j] = gu;
        }
        pgu.addRow(cellRow);
        for (int i = 1; i < cell.getNumberRowsSpanned(); i++) {
            row = (List) rows.get(currentRowIndex + i);
            cellRow = new GridUnit[cell.getNumberColumnsSpanned()];
            for (int j = 0; j < cell.getNumberColumnsSpanned(); j++) {
                GridUnit gu = new GridUnit(pgu, j, i);
                row.set(columnIndex + j, gu);
                cellRow[j] = gu;
            }
            pgu.addRow(cellRow);
        }
    }

    private static void setFlagForCols(int flag, List row) {
        for (Object aRow : row) {
            ((GridUnit) aRow).setFlag(flag);
        }
    }

    /** {@inheritDoc} */
    void startTableRow(TableRow tableRow) {
        currentTableRow = tableRow;
    }

    /** {@inheritDoc} */
    void endTableRow() {
        assert currentTableRow != null;
        if (currentRowIndex > 0 && currentTableRow.getBreakBefore() != Constants.EN_AUTO) {
            TableEventProducer eventProducer = TableEventProducer.Provider.get(
                    currentTableRow.getUserAgent().getEventBroadcaster());
            eventProducer.breakIgnoredDueToRowSpanning(this, currentTableRow.getName(), true,
                    currentTableRow.getLocator());
        }
        if (currentRowIndex < rows.size() - 1
                && currentTableRow.getBreakAfter() != Constants.EN_AUTO) {
            TableEventProducer eventProducer = TableEventProducer.Provider.get(
                    currentTableRow.getUserAgent().getEventBroadcaster());
            eventProducer.breakIgnoredDueToRowSpanning(this, currentTableRow.getName(), false,
                    currentTableRow.getLocator());
        }
        for (Object o : ((List) rows.get(currentRowIndex))) {
            GridUnit gu = (GridUnit) o;
            // The row hasn't been filled with empty grid units yet
            if (gu != null) {
                gu.setRow(currentTableRow);
            }
        }
        handleRowEnd(currentTableRow);
    }

    /** {@inheritDoc} */
    void endRow(TablePart part) {
        handleRowEnd(part);
    }

    private void handleRowEnd(TableCellContainer container) {
        List currentRow = (List) rows.get(currentRowIndex);
        lastRow = currentRow;
        // Fill gaps with empty grid units
        for (int i = 0; i < numberOfColumns; i++) {
            if (currentRow.get(i) == null) {
                currentRow.set(i, new EmptyGridUnit(table, currentTableRow, i));
            }
        }
        borderResolver.endRow(currentRow, container);
        if (firstInPart) {
            setFlagForCols(GridUnit.FIRST_IN_PART, currentRow);
            firstInPart = false;
        }
        if (currentRowIndex == rows.size() - 1) {
            // Means that the current row has no cell spanning over following rows
            container.getTablePart().addRowGroup(rows);
            initialize();
        } else {
            currentRowIndex++;
        }
        currentTableRow = null;
    }

    /** {@inheritDoc} */
    void startTablePart(TablePart part) {
        firstInPart = true;
        borderResolver.startPart(part);
    }

    /** {@inheritDoc} */
    void endTablePart() throws ValidationException {
        if (rows.size() > 0) {
            throw new ValidationException(
                    "A table-cell is spanning more rows than available in its parent element.");
        }
        setFlagForCols(GridUnit.LAST_IN_PART, lastRow);
        borderResolver.endPart();
    }

    /** {@inheritDoc} */
    void endTable() {
        borderResolver.endTable();
    }
}
