/*
 * {{{ header & license
 * Copyright (c) 2007 Wisconsin Court System
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * }}}
 */
package org.xhtmlrenderer.newtable;

import java.awt.Rectangle;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.List;

import org.xhtmlrenderer.css.constants.CSSName;
import org.xhtmlrenderer.css.constants.IdentValue;
import org.xhtmlrenderer.css.style.CssContext;
import org.xhtmlrenderer.css.style.derived.BorderPropertySet;
import org.xhtmlrenderer.css.style.derived.RectPropertySet;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.render.Box;
import org.xhtmlrenderer.render.ContentLimitContainer;
import org.xhtmlrenderer.render.PageBox;
import org.xhtmlrenderer.render.RenderingContext;

public class TableRowBox extends BlockBox {
    private int _baseline;
    private boolean _haveBaseline = false;
    private int _heightOverride;
    private ContentLimitContainer _contentLimitContainer;
    
    private int _extraSpaceTop;
    private int _extraSpaceBottom;
    
    public TableRowBox() {
    }
    
    public BlockBox copyOf() {
        TableRowBox result = new TableRowBox();
        result.setStyle(getStyle());
        result.setElement(getElement());
        
        return result;
    }
    
    public boolean isAutoHeight() {
        return getStyle().isAutoHeight() || ! getStyle().hasAbsoluteUnit(CSSName.HEIGHT);
    }
    
    private TableBox getTable() {
        // row -> section -> table
        return (TableBox)getParent().getParent();
    }
    
    private TableSectionBox getSection() {
        return (TableSectionBox)getParent();
    }
    
    public void layout(LayoutContext c, int contentStart) {
        boolean running = c.isPrint() && getTable().getStyle().isPaginateTable();
        int prevExtraTop = 0;
        int prevExtraBottom = 0;
        
        if (running) {
            prevExtraTop = c.getExtraSpaceTop();
            prevExtraBottom = c.getExtraSpaceBottom();
            
            calcExtraSpaceTop(c);
            calcExtraSpaceBottom(c);
            
            c.setExtraSpaceTop(c.getExtraSpaceTop() + getExtraSpaceTop());
            c.setExtraSpaceBottom(c.getExtraSpaceBottom() + getExtraSpaceBottom());
        }
        
        super.layout(c, contentStart);
        
        if (running) {
            if (isShouldMoveToNextPage(c)) {
                if (getTable().getFirstBodyRow() == this) {
                    // XXX Performance problem here.  This forces the table
                    // to move to the next page (which we want), but the initial
                    // table layout run still completes (which we don't)
                    getTable().setNeedPageClear(true);
                } else {
                    setNeedPageClear(true);
                }
            }
            c.setExtraSpaceTop(prevExtraTop);
            c.setExtraSpaceBottom(prevExtraBottom);
        }
    }
    
    private boolean isShouldMoveToNextPage(LayoutContext c) {
        PageBox page = c.getRootLayer().getFirstPage(c, this);
        
        if (getAbsY() + getHeight() < page.getBottom()) {
            return false;
        }
        
        for (Iterator i = getChildIterator(); i.hasNext(); ) {
            TableCellBox cell = (TableCellBox)i.next();
            int baseline = cell.calcBlockBaseline(c);
            if (baseline != BlockBox.NO_BASELINE && baseline < page.getBottom()) {
                return false;
            }
        }
        
        return true;
    }
    
    public void analyzePageBreaks(LayoutContext c, ContentLimitContainer container) {
        if (getTable().getStyle().isPaginateTable()) {
            _contentLimitContainer = new ContentLimitContainer(c, getAbsY());
            _contentLimitContainer.setParent(container);
            
            if (container != null) {
                container.updateTop(c, getAbsY());
                container.updateBottom(c, getAbsY() + getHeight());
            }
            
            for (Iterator i = getChildIterator(); i.hasNext(); ) {
                Box b = (Box)i.next();
                b.analyzePageBreaks(c, _contentLimitContainer);
            }
            
            if (container != null && _contentLimitContainer.isContainsMultiplePages()) {
                propagateExtraSpace(c, container, _contentLimitContainer, getExtraSpaceTop(), getExtraSpaceBottom());
            }
        } else {
            super.analyzePageBreaks(c, container);
        }
    }   

    private void calcExtraSpaceTop(LayoutContext c) {
        int maxBorderAndPadding = 0;
        
        for (Iterator i = getChildIterator(); i.hasNext(); ) {
            TableCellBox cell = (TableCellBox)i.next();
            
            int borderAndPadding = (int)cell.getPadding(c).top() + (int)cell.getBorder(c).top();
            if (borderAndPadding > maxBorderAndPadding) {
                maxBorderAndPadding = borderAndPadding;
            }
        }

        _extraSpaceTop = maxBorderAndPadding;
    }
    
    private void calcExtraSpaceBottom(LayoutContext c) {
        int maxBorderAndPadding = 0;
        
        int cRow = getIndex();
        int totalRows = getSection().getChildCount();
        List grid = getSection().getGrid();
        if (grid.size() > 0) {
            List row = ((RowData)grid.get(cRow)).getRow();
            for (int cCol = 0; cCol < row.size(); cCol++) {
                TableCellBox cell = (TableCellBox)row.get(cCol);
                
                if (cell == null || cell == TableCellBox.SPANNING_CELL) {
                    continue;
                }
                if (cRow < totalRows - 1 && getSection().cellAt(cRow+1, cCol) == cell) {
                    continue;
                }
                
                int borderAndPadding = (int)cell.getPadding(c).bottom() + (int)cell.getBorder(c).bottom();
                if (borderAndPadding > maxBorderAndPadding) {
                    maxBorderAndPadding = borderAndPadding;
                }
            }
        }
        
        _extraSpaceBottom = maxBorderAndPadding;
    }

    protected void layoutChildren(LayoutContext c, int contentStart) {
        setState(Box.CHILDREN_FLUX);
        ensureChildren(c);
        
        TableSectionBox section = getSection();
        if (section.isNeedCellWidthCalc()) {
            section.setCellWidths(c);
            section.setNeedCellWidthCalc(false);
        }
        
        if (getChildrenContentType() != CONTENT_EMPTY) {
            int cCol = 0;
            for (Iterator i = getChildIterator(); i.hasNext(); ) {
                TableCellBox cell = (TableCellBox)i.next();
                
                layoutCell(c, cell, 0);
                
                cCol++;
            }
        }
        
        setState(Box.DONE);
    }
    
    private void alignBaselineAlignedCells(LayoutContext c) {
        int[] baselines = new int[getChildCount()];
        int lowest = Integer.MIN_VALUE;
        boolean found = false;
        for (int i = 0; i < getChildCount(); i++) {
            TableCellBox cell = (TableCellBox)getChild(i);
            
            if (cell.getVerticalAlign() == IdentValue.BASELINE) {
                int baseline = cell.calcBaseline(c);
                baselines[i] = baseline;
                if (baseline > lowest) {
                    lowest = baseline;
                }
                found = true;
            }
        }
        for (int i = 0; i < getChildCount(); i++) {
            TableCellBox cell = (TableCellBox)getChild(i);
            
            if (cell.getVerticalAlign() == IdentValue.BASELINE) {
                int deltaY = lowest - baselines[i];
                if (deltaY != 0) {
                    if (c.isPrint() && cell.isPageBreaksChange(c, deltaY)) {
                        relayoutCell(c, cell, deltaY);
                    } else {
                        cell.moveContent(c, deltaY);
                        cell.setHeight(cell.getHeight() + deltaY);
                    }
                }
            }
        }
        
        if (found) {
            setBaseline(lowest - getAbsY());
            setHaveBaseline(true);
        }
    }
    
    private boolean alignMiddleAndBottomAlignedCells(LayoutContext c) {
        boolean needRowHeightRecalc = false;
        
        int cRow = getIndex();
        int totalRows = getSection().getChildCount();
        List grid = getSection().getGrid();
        if (grid.size() > 0) {
            List row = ((RowData)grid.get(cRow)).getRow();
            for (int cCol = 0; cCol < row.size(); cCol++) {
                TableCellBox cell = (TableCellBox)row.get(cCol);
                
                if (cell == null || cell == TableCellBox.SPANNING_CELL) {
                    continue;
                }
                if (cRow < totalRows - 1 && getSection().cellAt(cRow+1, cCol) == cell) {
                    continue;
                }
                
                IdentValue val = cell.getVerticalAlign();
                if (val == IdentValue.MIDDLE || val == IdentValue.BOTTOM) {
                    int deltaY = calcMiddleBottomDeltaY(cell, val);
                    if (deltaY > 0) {
                        if (c.isPrint() && cell.isPageBreaksChange(c, deltaY)) {
                            int oldCellHeight = cell.getHeight();
                            relayoutCell(c, cell, deltaY);
                            if (oldCellHeight + deltaY != cell.getHeight()) {
                                needRowHeightRecalc = true;
                            }
                        } else {
                            cell.moveContent(c, deltaY);
                            // Set a provisional height in case we need to calculate
                            // a default baseline
                            cell.setHeight(cell.getHeight() + deltaY);
                        }
                    }
                }
            }
        }
        
        return needRowHeightRecalc;
    }
    
    private int calcMiddleBottomDeltaY(TableCellBox cell, IdentValue verticalAlign) {
        int result;
        if (cell.getStyle().getRowSpan() == 1) {
            result = getHeight() - cell.getChildrenHeight();
        } else {
            result = getAbsY() + getHeight() - (cell.getAbsY() + cell.getChildrenHeight());
        }
        
        if (verticalAlign == IdentValue.MIDDLE) {
            return result / 2;
        } else {  /* verticalAlign == IdentValue.BOTTOM */
            return result;
        }
    }
    
    protected void calcLayoutHeight(
            LayoutContext c, BorderPropertySet border, 
            RectPropertySet margin, RectPropertySet padding) {
        if (getHeightOverride() > 0) {
            setHeight(getHeightOverride());
        }
        
        alignBaselineAlignedCells(c);
        
        calcRowHeight(c);
        
        boolean recalcRowHeight = alignMiddleAndBottomAlignedCells(c);
        
        if (recalcRowHeight) {
            calcRowHeight(c);
        }
        
        if (! isHaveBaseline()) {
            calcDefaultBaseline(c);
        }
        
        setCellHeights(c);
    }

    private void calcRowHeight(CssContext c) {
        int y1 = getAbsY();
        int y2;
        
        if (getHeight() != 0) {
            y2 = y1 + getHeight();
        } else {
            y2 = y1;
        }
        
        if (isLastRow()) {
            int bottom = getTable().calcFixedHeightRowBottom(c);
            if (bottom > 0 && bottom > y2) {
                y2 = bottom;
            }
        }
        
        int cRow = getIndex();
        int totalRows = getSection().getChildCount();
        List grid = getSection().getGrid();
        if (grid.size() > 0) {
            List row = ((RowData)grid.get(cRow)).getRow();
            for (int cCol = 0; cCol < row.size(); cCol++) {
                TableCellBox cell = (TableCellBox)row.get(cCol);
                
                if (cell == null || cell == TableCellBox.SPANNING_CELL) {
                    continue;
                }
                if (cRow < totalRows - 1 && getSection().cellAt(cRow+1, cCol) == cell) {
                    continue;
                }
                
                int bottomCellEdge = cell.getAbsY() + cell.getHeight();
                if (bottomCellEdge > y2) {
                    y2 = bottomCellEdge;
                }
            }
        }
        
        setHeight(y2 - y1);
    }
    
    private boolean isLastRow() {
        TableBox table = getTable();
        TableSectionBox section = getSection();
        if (table.sectionBelow(section, true) == null) {
            return section.getChild(section.getChildCount()-1) == this;
        } else {
            return false;
        }
    }
    
    private void calcDefaultBaseline(LayoutContext c) {
        int lowestCellEdge = 0;
        int cRow = getIndex();
        int totalRows = getSection().getChildCount();
        List grid = getSection().getGrid();
        if (grid.size() > 0) {
            List row = ((RowData)grid.get(cRow)).getRow();
            for (int cCol = 0; cCol < row.size(); cCol++) {
                TableCellBox cell = (TableCellBox)row.get(cCol);
                
                if (cell == null || cell == TableCellBox.SPANNING_CELL) {
                    continue;
                }
                if (cRow < totalRows - 1 && getSection().cellAt(cRow+1, cCol) == cell) {
                    continue;
                }
                
                Rectangle contentArea = cell.getContentAreaEdge(cell.getAbsX(), cell.getAbsY(), c);
                int bottomCellEdge = contentArea.y + contentArea.height;
                if (bottomCellEdge > lowestCellEdge) {
                    lowestCellEdge = bottomCellEdge;
                }
            }
        }
        if (lowestCellEdge > 0) {
            setBaseline(lowestCellEdge - getAbsY());
        }
        setHaveBaseline(true);
    }
    
    private void setCellHeights(LayoutContext c) {
        int cRow = getIndex();
        int totalRows = getSection().getChildCount();
        List grid = getSection().getGrid();
        if (grid.size() > 0) {
            List row = ((RowData)grid.get(cRow)).getRow();
            for (int cCol = 0; cCol < row.size(); cCol++) {
                TableCellBox cell = (TableCellBox)row.get(cCol);
                
                if (cell == null || cell == TableCellBox.SPANNING_CELL) {
                    continue;
                }
                if (cRow < totalRows - 1 && getSection().cellAt(cRow+1, cCol) == cell) {
                    continue;
                }
                
                if (cell.getStyle().getRowSpan() == 1) {
                    cell.setHeight(getHeight());
                } else {
                    cell.setHeight(getAbsY() + getHeight() - cell.getAbsY());
                }
            }
        }
    }
    
    private void relayoutCell(LayoutContext c, TableCellBox cell, int contentStart) {
        int width = cell.getWidth();
        cell.reset(c);
        cell.setLayoutWidth(c, width);
        layoutCell(c, cell, contentStart);
    }
    
    private void layoutCell(LayoutContext c, TableCellBox cell, int contentStart) {
        cell.initContainingLayer(c);
        cell.calcCanvasLocation();
        
        cell.layout(c, contentStart);
    } 
    
    public void initStaticPos(LayoutContext c, BlockBox parent, int childOffset) {
        setX(0);
        
        TableBox table = getTable();
        setY(parent.getHeight() + table.getStyle().getBorderVSpacing(c));
        c.translate(0, getY()-childOffset);
    }

    public int getBaseline() {
        return _baseline;
    }

    public void setBaseline(int baseline) {
        _baseline = baseline;
    }
    
    protected boolean isSkipWhenCollapsingMargins() {
        return true;
    }
    
    public void paintBorder(RenderingContext c) {
        // rows never have borders
    }
    
    public void paintBackground(RenderingContext c) {
        // painted at the cell level
    }   
    
    public void reset(LayoutContext c) {
        super.reset(c);
        setHaveBaseline(false);
        getSection().setNeedCellWidthCalc(true);
        setContentLimitContainer(null);
    }

    public boolean isHaveBaseline() {
        return _haveBaseline;
    }

    public void setHaveBaseline(boolean haveBaseline) {
        _haveBaseline = haveBaseline;
    }
    
    protected String getExtraBoxDescription() {
        if (isHaveBaseline()) {
            return "(baseline=" + getBaseline() + ") ";
        } else {
            return "";
        }
    }

    public int getHeightOverride() {
        return _heightOverride;
    }

    public void setHeightOverride(int heightOverride) {
        _heightOverride = heightOverride;
    }
    
    public void exportText(RenderingContext c, Writer writer) throws IOException {
        if (getTable().isMarginAreaRoot()) {
            super.exportText(c, writer);
        } else {
            int yPos = getAbsY();
            if (yPos >= c.getPage().getBottom() && isInDocumentFlow()) {
                exportPageBoxText(c, writer, yPos);
            }
            
            for (Iterator i = getChildIterator(); i.hasNext(); ) {
                TableCellBox cell = (TableCellBox)i.next();
                StringBuffer buffer =  new StringBuffer();
                cell.collectText(c, buffer);
                writer.write(buffer.toString().trim());
                int cSpan = cell.getStyle().getColSpan();
                for (int j = 0; j < cSpan; j++) {
                    writer.write('\t');    
                }
            }
            
            writer.write(LINE_SEPARATOR);
        }
    }

    public ContentLimitContainer getContentLimitContainer() {
        return _contentLimitContainer;
    }

    public void setContentLimitContainer(ContentLimitContainer contentLimitContainer) {
        _contentLimitContainer = contentLimitContainer;
    }

    public int getExtraSpaceTop() {
        return _extraSpaceTop;
    }

    public void setExtraSpaceTop(int extraSpaceTop) {
        _extraSpaceTop = extraSpaceTop;
    }

    public int getExtraSpaceBottom() {
        return _extraSpaceBottom;
    }

    public void setExtraSpaceBottom(int extraSpaceBottom) {
        _extraSpaceBottom = extraSpaceBottom;
    }
}
