/*
 * 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: RtfTableCell.java 1610839 2014-07-15 20:25:58Z vhennebert $ */

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.Iterator;

/**
 * <p>A cell in an RTF table, container for paragraphs, lists, etc.</p>
 *
 * <p>This work was authored by Bertrand Delacretaz (bdelacretaz@codeconsult.ch).</p>
 */

public class RtfTableCell
        extends RtfContainer
        implements IRtfParagraphContainer, IRtfListContainer, IRtfTableContainer,
            IRtfExternalGraphicContainer, IRtfTextrunContainer {
    private RtfParagraph paragraph;
    private RtfList list;
    private RtfTable table;
    private RtfExternalGraphic externalGraphic;
    private final RtfTableRow parentRow;
    private boolean setCenter;
    private boolean setRight;
    private int id;
    private RtfParagraphBreak lastBreak;
    private int lastBreakDepth = Integer.MIN_VALUE;

    private static final String TABLE_CELL_PARAGRAPH = "cell";
    private static final String TABLE_CELL_NESTED_PARAGRAPH = "nestcell";

    /** default cell width (in twips ??) */
    public static final int DEFAULT_CELL_WIDTH = 2000;

    /** cell width in twips */
    private int cellWidth;
    private int widthOffset;

    /** cell merging has three states */
    private int vMerge = NO_MERGE;
    private int hMerge = NO_MERGE;

    /** cell merging: this cell is not merged */
    public static final int NO_MERGE = 0;

    /** cell merging: this cell is the start of a range of merged cells */
    public static final int MERGE_START = 1;

    /** cell merging: this cell is part of (but not the start of) a range of merged cells */
    public static final int MERGE_WITH_PREVIOUS = 2;

    /** Create an RTF element as a child of given container */
    RtfTableCell(RtfTableRow parent, Writer w, int cellWidth, int idNum) throws IOException {
        super(parent, w);
        id = idNum;
        parentRow = parent;
        this.cellWidth = cellWidth;
        setCenter = false;
        setRight = false;

    }

    /** Create an RTF element as a child of given container */
    RtfTableCell(RtfTableRow parent, Writer w, int cellWidth, RtfAttributes attrs,
            int idNum) throws IOException {
        super(parent, w, attrs);
        id = idNum;
        parentRow = parent;
        this.cellWidth = cellWidth;
    }

    /**
     * Start a new paragraph after closing current current paragraph, list and table
     * @param attrs attributes of new RtfParagraph
     * @return new RtfParagraph object
     * @throws IOException for I/O problems
     */
    public RtfParagraph newParagraph(RtfAttributes attrs) throws IOException {
        closeAll();

        // in tables, RtfParagraph must have the intbl attribute
        if (attrs == null) {
            attrs = new RtfAttributes();
        }

        attrs.set("intbl");

        paragraph = new RtfParagraph(this, writer, attrs);

        if (paragraph.attrib.isSet("qc")) {
            setCenter = true;
            attrs.set("qc");
        } else if (paragraph.attrib.isSet("qr")) {
            setRight = true;
            attrs.set("qr");
        } else {
            attrs.set("ql");
        }
        attrs.set("intbl");


        //lines modified by Chris Scott, Westinghouse
        return paragraph;
    }

    /**
     * Start a new external graphic after closing current paragraph, list and table
     * @throws IOException for I/O problems
     * @return new RtfExternalGraphic object
     */
    public RtfExternalGraphic newImage() throws IOException {
        closeAll();
        externalGraphic = new RtfExternalGraphic(this, writer);
        return externalGraphic;
    }

    /**
     * Start a new paragraph with default attributes after closing current
     * paragraph, list and table
     * @return new RtfParagraph object
     * @throws IOException for I/O problems
     */
    public RtfParagraph newParagraph() throws IOException {
        return newParagraph(null);
    }

    /**
     * Start a new list after closing current paragraph, list and table
     * @param attrib attributes for new RtfList
     * @return new RtfList object
     * @throws IOException for I/O problems
     */
    public RtfList newList(RtfAttributes attrib) throws IOException {
        closeAll();
        list = new RtfList(this, writer, attrib);
        return list;
    }

    /**
     * Start a new nested table after closing current paragraph, list and table
     * @param tc table column info for new RtfTable
     * @return new RtfTable object
     * @throws IOException for I/O problems
     */
    public RtfTable newTable(ITableColumnsInfo tc) throws IOException {
        closeAll();
        table = new RtfTable(this, writer, tc);
        return table;
    }

    /**
     * Start a new nested table after closing current paragraph, list and table
     * @param attrs attributes of new RtfTable
     * @param tc table column info for new RtfTable
     * @return new RtfTable object
     * @throws IOException for I/O problems
     */
    // Modified by Boris Poudérous on 07/22/2002
    public RtfTable newTable(RtfAttributes attrs, ITableColumnsInfo tc) throws IOException {
        closeAll();
        table = new RtfTable(this, writer, attrs, tc); // Added tc Boris Poudérous 07/22/2002
        return table;
    }

    /** used by RtfTableRow to write the <celldef> cell definition control words
     *  @param offset sum of the widths of preceeding cells in same row
     *  @return offset + width of this cell
     */
    int writeCellDef(int offset) throws IOException {
        /*
         * Don't write \clmgf or \clmrg. Instead add the widths
         * of all spanned columns and create a single wider cell,
         * because \clmgf and \clmrg won't work in last row of a
         * table (Word2000 seems to do the same).
         * Cause of this, dont't write horizontally merged cells.
         * They just exist as placeholders in TableContext class,
         * and are never written to RTF file.
         */
        // horizontal cell merge codes
        if (hMerge == MERGE_WITH_PREVIOUS) {
            return offset;
        }

        newLine();
        this.widthOffset = offset;

        // vertical cell merge codes
        if (vMerge == MERGE_START) {
            writeControlWord("clvmgf");
        } else if (vMerge == MERGE_WITH_PREVIOUS) {
            writeControlWord("clvmrg");
        }

        /**
         * Added by Boris POUDEROUS on 2002/06/26
         */
        // Cell background color processing :
        writeAttributes(attrib, ITableAttributes.CELL_COLOR);
        /** - end - */

        writeAttributes(attrib, ITableAttributes.ATTRIB_CELL_PADDING);
        writeAttributes(attrib, ITableAttributes.CELL_BORDER);
        writeAttributes(attrib, IBorderAttributes.BORDERS);

        // determine cell width
        int iCurrentWidth = this.cellWidth;
        if (attrib.getValue("number-columns-spanned") != null) {
            // Get the number of columns spanned
            int nbMergedCells = ((Integer)attrib.getValue("number-columns-spanned")).intValue();

            RtfTable tab = getRow().getTable();

            // Get the context of the current table in order to get the width of each column
            ITableColumnsInfo tableColumnsInfo
                = tab.getITableColumnsInfo();

            tableColumnsInfo.selectFirstColumn();

            // Reach the column index in table context corresponding to the current column cell
            // id is the index of the current cell (it begins at 1)
            // getColumnIndex() is the index of the current column in table context (it begins at 0)
            //  => so we must withdraw 1 when comparing these two variables.
            while ((this.id - 1) != tableColumnsInfo.getColumnIndex()) {
               tableColumnsInfo.selectNextColumn();
            }

            // We withdraw one cell because the first cell is already created
            // (it's the current cell) !
            int i = nbMergedCells - 1;
            while (i > 0) {
                tableColumnsInfo.selectNextColumn();
                iCurrentWidth += (int)tableColumnsInfo.getColumnWidth();

                i--;
            }
        }
        final int xPos = offset + iCurrentWidth;

        //these lines added by Chris Scott, Westinghouse
        //some attributes need to be written before opening block
        if (setCenter) {
            writeControlWord("trqc");
        } else if (setRight) {
            writeControlWord("trqr");
        } else {
            writeControlWord("trql");
        }
        writeAttributes(attrib, ITableAttributes.CELL_VERT_ALIGN);

        writeControlWord("cellx" + xPos);

        return xPos;

    }

    /**
     * Overriden to avoid writing any it's a merged cell.
     * @throws IOException for I/O problems
     */
    protected void writeRtfContent() throws IOException {
       // Never write horizontally merged cells.
       if (hMerge == MERGE_WITH_PREVIOUS) {
           return;
       }

       super.writeRtfContent();
    }

    /**
     * Called before writeRtfContent; overriden to avoid writing
     * any it's a merged cell.
     * @throws IOException for I/O problems
     */
    protected void writeRtfPrefix() throws IOException {
        // Never write horizontally merged cells.
        if (hMerge == MERGE_WITH_PREVIOUS) {
            return;
        }

        super.writeRtfPrefix();
    }

    /**
     * The "cell" control word marks the end of a cell
     * @throws IOException for I/O problems
     */
    protected void writeRtfSuffix() throws IOException {
        // Never write horizontally merged cells.
        if (hMerge == MERGE_WITH_PREVIOUS) {
            return;
        }

        if (getRow().getTable().isNestedTable()) {
            //nested table
            if (lastBreak == null) {
                writeControlWordNS("nestcell");
            }
            writeGroupMark(true);
            writeControlWord("nonesttables");
            writeControlWord("par");
            writeGroupMark(false);
        } else {
            // word97 hangs if cell does not contain at least one "par" control word
            // TODO this is what causes the extra spaces in nested table of test
            //      004-spacing-in-tables.fo,
            // but if is not here we generate invalid RTF for word97

            if (setCenter) {
                writeControlWord("qc");
            } else if (setRight) {
                writeControlWord("qr");
            } else {
                RtfElement lastChild = null;

                if (getChildren().size() > 0) {
                    lastChild = (RtfElement) getChildren().get(getChildren().size() - 1);
                }


                if (lastChild != null
                        && lastChild instanceof RtfTextrun) {
                    //Don't write \ql in order to allow for example a right aligned paragraph
                    //in a not right aligned table-cell to write its \qr.
                } else {
                    writeControlWord("ql");
                }
            }

            if (!containsText()) {
                writeControlWord("intbl");

                //R.Marra this create useless paragraph
                //Seem working into Word97 with the "intbl" only
                //writeControlWord("par");
            }

            if (lastBreak == null) {
                writeControlWord("cell");
            }
        }
    }


    //modified by Chris Scott, Westinghouse
    private void closeCurrentParagraph() throws IOException {
        if (paragraph != null) {
            paragraph.close();
        }
    }

    private void closeCurrentList() throws IOException {
        if (list != null) {
            list.close();
        }
    }

    private void closeCurrentTable() throws IOException {
        if (table != null) {
            table.close();
        }
    }

    private void closeCurrentExternalGraphic() throws IOException {
        if (externalGraphic != null) {
            externalGraphic.close();
        }
    }

    private void closeAll()
    throws IOException {
        closeCurrentTable();
        closeCurrentParagraph();
        closeCurrentList();
        closeCurrentExternalGraphic();
    }

    /**
     * @param mergeStatus vertical cell merging status to set
     */
    public void setVMerge(int mergeStatus) { this.vMerge = mergeStatus; }

    /**
     * @return vertical cell merging status
     */
    public int getVMerge() { return this.vMerge; }

    /**
     * Set horizontal cell merging status
     * @param mergeStatus mergeStatus to set
     */
    public void setHMerge(int mergeStatus) {
        this.hMerge = mergeStatus;
    }

    /**
     * @return horizontal cell merging status
     */
    public int getHMerge() {
        return this.hMerge;
    }

    /** get cell width */
    int getCellWidth() { return this.cellWidth; }

    /**
     * Overridden so that nested tables cause extra rows to be added after the row
     * that contains this cell
     * disabled for V0.3 - nested table support is not done yet
     * @throws IOException for I/O problems
     */
    /*
    protected void writeRtfContent()
    throws IOException {
        int extraRowIndex = 0;
        RtfTableCell extraCell = null;

        for (Iterator it = getChildren().iterator(); it.hasNext();) {
            final RtfElement e = (RtfElement)it.next();
            if (e instanceof RtfTable) {
                // nested table - render its cells in supplementary rows after current row,
                // and put the remaining content of this cell in a new cell after nested table
                // Line added by Boris Poudérous
        parentRow.getExtraRowSet().setParentITableColumnsInfo(
                ((RtfTable)this.getParentOfClass(e.getClass())).getITableColumnsInfo());
        extraRowIndex = parentRow.getExtraRowSet().addTable((RtfTable)e,
                extraRowIndex, widthOffset);
                // Boris Poudérous added the passing of the current cell
                // attributes to the new cells (in order not to have cell without
                // border for example)
        extraCell = parentRow.getExtraRowSet().createExtraCell(extraRowIndex,
                widthOffset, this.getCellWidth(), attrib);
                extraRowIndex++;

            } else if (extraCell != null) {
                // we are after a nested table, add elements to the extra cell created for them
                extraCell.addChild(e);

            } else {
                // before a nested table, normal rendering
                e.writeRtf();
            }
        }
    }*/

    /**
     * A table cell always contains "useful" content, as it is here to take some
     * space in a row.
     * Use containsText() to find out if there is really some useful content in the cell.
     * TODO: containsText could use the original isEmpty implementation?
     * @return false (always)
     */
    public boolean isEmpty() {
        return false;
    }

    /** true if the "par" control word must be written for given RtfParagraph
     *  (which is not the case for the last non-empty paragraph of the cell)
     */
    boolean paragraphNeedsPar(RtfParagraph p) {
        // true if there is at least one non-empty paragraph after p in our children
        boolean pFound = false;
        boolean result = false;
        for (Iterator it = getChildren().iterator(); it.hasNext();) {
            final Object o = it.next();
            if (!pFound) {
                // set pFound when p is found in the list
                pFound =  (o == p);
            } else {
                if (o instanceof RtfParagraph) {
                    final RtfParagraph p2 = (RtfParagraph)o;
                    if (!p2.isEmpty()) {
                        // found a non-empty paragraph after p
                        result = true;
                        break;
                    }
                } else if (o instanceof RtfTable) {
                    break;
                }
            }
        }
        return result;
    }

    /**
     * Returns the current RtfTextrun object.
     * Opens a new one if necessary.
     * @return The RtfTextrun object
     * @throws IOException Thrown when an IO-problem occurs
     */
    public RtfTextrun getTextrun() throws IOException {
        RtfAttributes attrs = new RtfAttributes();

        if (!getRow().getTable().isNestedTable()) {
            attrs.set("intbl");
        }

        RtfTextrun textrun = RtfTextrun.getTextrun(this, writer, attrs);

        //Suppress the very last \par, because the closing \cell applies the
        //paragraph attributes.
        textrun.setSuppressLastPar(true);

        return textrun;
    }

    /**
     * Get the parent row.
     * @return The parent row.
     */
    public RtfTableRow getRow() {
        RtfElement e = this;
        while (e.parent != null) {
            if (e.parent instanceof RtfTableRow) {
                return (RtfTableRow) e.parent;
            }

            e = e.parent;
        }

        return null;
    }

    /**
     * The table cell decides whether or not a newly added paragraph break
     * will be used to write the cell-end control word.
     * For nested tables it is not necessary.
     *
     * @param parBreak the paragraph break element
     * @param breakDepth The depth is necessary for picking the correct break element.
     * If it is deeper inside the whole cell it will be used, and if there is something on
     * the same level (depth) it is also set because the method is called for all breaks
     * in the correct order.
     */
    public void setLastParagraph(RtfParagraphBreak parBreak, int breakDepth) {
        if (parBreak != null && breakDepth >= lastBreakDepth) {
            lastBreak = parBreak;
            lastBreakDepth = breakDepth;
        }
    }

    /**
     * The last paragraph break was just stored before,
     * now the control word is really switched
     */
    public void finish() {
      //If it is nested and contains another table do not set it
      if (getRow().getTable().isNestedTable() && table != null) {
          lastBreak = null;
      } else if (lastBreak != null) {
              lastBreak.switchControlWord(
                                          getRow().getTable().isNestedTable()
                                          ? TABLE_CELL_NESTED_PARAGRAPH
                                          : TABLE_CELL_PARAGRAPH);
      }
    }
}
