/*
 * 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: RtfExtraRowSet.java 1761020 2016-09-16 11:17:35Z ssteiner $ */

package org.apache.fop.render.rtf.rtflib.rtfdoc;

/*
 * This file is part of the RTF library of the FOP project, which was originally
 * created by Bertrand Delacretaz <bdelacretaz@codeconsult.ch> and by other
 * contributors to the jfor project (www.jfor.org), who agreed to donate jfor to
 * the FOP project.
 */

import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;


/**
 * <p>Used to add extra table rows after a row that contains a nested table:</p>
 * <ul>
 * <li> created by RtfTableRow before generating RTF code
 * <li> an RtfTableCell that contains a nested table can ask this to put
 *         some of its children in extra rows that after the current row
 * <li> once RtfTableRow is done rendering its children, it renders this,
 *         causing extra rows to be generated, with content that can come
 *         from several RtfTableCells
 * </ul>
 *
 * <p>See org.apache.fop.rtf.rtflib.testdocs.NestedTable for an example of
 * usage.</p>
 *
 * <p>This work was authored by Bertrand Delacretaz (bdelacretaz@codeconsult.ch).</p>
 */

public class RtfExtraRowSet extends RtfContainer {
    // TODO what is idnum?
    static final int DEFAULT_IDNUM = 0;

    /** Parent table context
     * (added by Boris Poudérous on july 2002 in order to process nested tables)
     */
    private ITableColumnsInfo parentITableColumnsInfo;

    /** While a top-level RtfTableRow is being rendered, we build a list of
     *  RtfTableCells that must be rendered in extra rows.
     *  This holds a cell with positioning information
     */
    private final List cells = new LinkedList();
    private static class PositionedCell
    implements Comparable {
        private final RtfTableCell cell;
        private final int xOffset;
        private final int rowIndex;

        PositionedCell(RtfTableCell c, int index, int offset) {
            cell = c;
            xOffset = offset;
            rowIndex = index;
        }

        /** debugging dump */
        public String toString() {
            return "PositionedCell: row " + rowIndex + ", offset " + xOffset;
        }

        /** cells need to be sorted by row index and then by x offset */
        public int compareTo(Object o) {
            int result = 0;
            if (o == null) {
                result = 1;
            } else if (!(o instanceof PositionedCell)) {
                result = 1;
            } else {
                final PositionedCell pc = (PositionedCell)o;
                if (this.rowIndex < pc.rowIndex) {
                    result = -1;
                } else if (this.rowIndex > pc.rowIndex) {
                    result = 1;
                } else if (this.xOffset < pc.xOffset) {
                    result = -1;
                } else if (this.xOffset > pc.xOffset) {
                    result = 1;
                }
            }

            return result;
        }

        public int hashCode() {
            int hc = super.hashCode();
            hc ^= (hc * 11) + xOffset;
            hc ^= (hc * 19) + rowIndex;
            return hc;
        }

        public boolean equals(Object o) {
            if (o instanceof PositionedCell) {
                PositionedCell pc = (PositionedCell) o;
                return (pc.rowIndex == rowIndex) && (pc.xOffset == xOffset);
            } else {
                return false;
            }
        }
    }

    /** our maximum row index */
    private int maxRowIndex;

    /** an RtfExtraRowSet has no parent, it is only used temporary during
     *  generation of RTF for an RtfTableRow
     */
    RtfExtraRowSet(Writer w)
    throws IOException {
        super(null, w);
    }

    /** Add all cells of given Table to this set for later rendering in extra rows
     *  @return index of extra row to use for elements that follow this table in the same cell
     *  @param rowIndex index of first extra row to create to hold cells of tbl
     *  @param xOffset horizontal position of left edge of first column of tbl
     */
    int addTable(RtfTable tbl, int rowIndex, int xOffset) {
        // process all rows of the table
        for (Object o : tbl.getChildren()) {
            final RtfElement e = (RtfElement) o;
            if (e instanceof RtfTableRow) {
                addRow((RtfTableRow) e, rowIndex, xOffset);
                rowIndex++;
                maxRowIndex = Math.max(rowIndex, maxRowIndex);
            }
        }
        return rowIndex;
    }

    /** add all cells of given row to this set */
    private void addRow(RtfTableRow row, int rowIndex, int xOffset) {
        for (Object o : row.getChildren()) {
            final RtfElement e = (RtfElement) o;
            if (e instanceof RtfTableCell) {
                final RtfTableCell c = (RtfTableCell) e;
                cells.add(new PositionedCell(c, rowIndex, xOffset));
                xOffset += c.getCellWidth();
            }
        }
    }

    /** create an extra cell to hold content that comes after a nested table in a cell
     *  Modified by Boris Poudérous in order to permit the extra cell to have
     *  the attributes of its parent cell
     */
    RtfTableCell createExtraCell(int rowIndex, int xOffset, int cellWidth,
                                 RtfAttributes parentCellAttributes)
    throws IOException {
        final RtfTableCell c = new RtfTableCell(null, writer, cellWidth,
                parentCellAttributes, DEFAULT_IDNUM);
        cells.add(new PositionedCell(c, rowIndex, xOffset));
        return c;
    }

    /**
     * render extra RtfTableRows containing all the extra RtfTableCells that we
     * contain
     * @throws IOException for I/O problems
     */
    protected void writeRtfContent() throws IOException {
        // sort cells by rowIndex and xOffset
        Collections.sort(cells);

        // process all extra cells by rendering them into extra rows
        List rowCells = null;
        int rowIndex = -1;
        for (Object cell : cells) {
            final PositionedCell pc = (PositionedCell) cell;
            if (pc.rowIndex != rowIndex) {
                // starting a new row, render previous one
                if (rowCells != null) {
                    writeRow(rowCells);
                }
                rowIndex = pc.rowIndex;
                rowCells = new LinkedList();
            }
            rowCells.add(pc);
        }

        // render last row
        if (rowCells != null) {
            writeRow(rowCells);
        }
    }

    /** write one RtfTableRow containing given PositionedCells */
    private void writeRow(List cells)
    throws IOException {
        if (allCellsEmpty(cells)) {
            return;
        }

        final RtfTableRow row = new RtfTableRow(null, writer, DEFAULT_IDNUM);
        int cellIndex = 0;

        // Get the context of the table that holds the nested table
        ITableColumnsInfo parentITableColumnsInfo = getParentITableColumnsInfo();
        parentITableColumnsInfo.selectFirstColumn();

        // X offset of the current empty cell to add
        float xOffset = 0;
        float xOffsetOfLastPositionedCell = 0;

        for (Object cell : cells) {
            final PositionedCell pc = (PositionedCell) cell;

            // if first cell is not at offset 0, add placeholder cell
            // TODO should be merged with the cell that is above it
            if (cellIndex == 0 && pc.xOffset > 0) {
                /**
                 * Added by Boris Poudérous
                 */
                // Add empty cells merged vertically with the cells above and with the same widths
                // (BEFORE the cell that contains the nested table)
                for (int i = 0; (xOffset < pc.xOffset)
                        && (i < parentITableColumnsInfo.getNumberOfColumns()); i++) {
                    // Get the width of the cell above
                    xOffset += parentITableColumnsInfo.getColumnWidth();
                    // Create the empty cell merged vertically
                    row.newTableCellMergedVertically((int) parentITableColumnsInfo.getColumnWidth(),
                            pc.cell.attrib);
                    // Select next column in order to have its width
                    parentITableColumnsInfo.selectNextColumn();
                }
            }

            row.addChild(pc.cell);
            // Line added by Boris Poudérous
            xOffsetOfLastPositionedCell = pc.xOffset + pc.cell.getCellWidth();
            cellIndex++;
        }

        /**
         * Added by Boris Poudérous
         */
        // Add empty cells merged vertically with the cells above AFTER the cell
        // that contains the nested table
        // The cells added have the same widths than the cells above.
        if (parentITableColumnsInfo.getColumnIndex()
                < (parentITableColumnsInfo.getNumberOfColumns() - 1)) {
            parentITableColumnsInfo.selectNextColumn();

            while (parentITableColumnsInfo.getColumnIndex()
                   < parentITableColumnsInfo.getNumberOfColumns()) {
                  // Create the empty cell merged vertically
                  // TODO : the new cells after the extra cell don't have its
                  // attributes as we did for the previous cells.
                  //        => in fact the m_attrib below (last argument) is
                  // empty => should be the attributes of the above cells.
                  row.newTableCellMergedVertically((int)parentITableColumnsInfo.getColumnWidth(),
                          attrib);
                  // Select next column in order to have its width
                  parentITableColumnsInfo.selectNextColumn();
                }
           }

        row.writeRtf();
    }

    /** true if all cells of given list are empty
     *  @param cells List of PositionedCell objects
     */
    private static boolean allCellsEmpty(List cells) {
        boolean empty = true;
        for (Object cell : cells) {
            final PositionedCell pc = (PositionedCell) cell;
            if (pc.cell.containsText()) {
                empty = false;
                break;
            }
        }
        return empty;
    }

    /**
     * As this contains cells from several rows, we say that it's empty
     * only if we have no cells.
     * writeRow makes the decision about rendering specific rows
     * @return false (always)
     */
    public boolean isEmpty() {
        return false;
    }

    /**
     * @return The table context of the parent table
     * Added by Boris Poudérous on july 2002 in order to process nested tables
     */
     public ITableColumnsInfo getParentITableColumnsInfo() {
       return this.parentITableColumnsInfo;
     }

     /**
      *
      * @param parentITableColumnsInfo table context to set
      */
     public void setParentITableColumnsInfo(ITableColumnsInfo parentITableColumnsInfo) {
       this.parentITableColumnsInfo = parentITableColumnsInfo;
     }
     /** - end - */
}
