/*
 * 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.
 */

package org.apache.myfaces.renderkit.html.ext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.component.NewspaperTable;
import org.apache.myfaces.component.html.ext.HtmlDataTable;
import org.apache.myfaces.custom.column.HtmlColumn;
import org.apache.myfaces.custom.column.HtmlSimpleColumn;
import org.apache.myfaces.custom.crosstable.UIColumns;
import org.apache.myfaces.renderkit.html.util.ColumnInfo;
import org.apache.myfaces.renderkit.html.util.RowInfo;
import org.apache.myfaces.renderkit.html.util.TableContext;
import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase;
import org.apache.myfaces.shared_tomahawk.util.ArrayUtils;

import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.component.ValueHolder;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Renderer for the Tomahawk extended HtmlDataTable component.
 *
 * @JSFRenderer
 *   renderKitId = "HTML_BASIC"
 *   family = "javax.faces.Data"
 *   type = "org.apache.myfaces.Table" 
 *   
 * @author Manfred Geiler (latest modification by $Author: lu4242 $)
 * @version $Revision: 1353439 $ $Date: 2012-06-25 04:12:06 -0500 (Mon, 25 Jun 2012) $
 */
public class HtmlTableRenderer extends HtmlTableRendererBase {
    private static final Log log = LogFactory.getLog(HtmlTableRenderer.class);

    /**
     * DetailStamp facet name.
     */
    public static final String DETAIL_STAMP_FACET_NAME = "detailStamp";
    private static final String BODY_STYLE_CLASS = "bodyStyleClass";
    private static final String BODY_STYLE = "bodyStyle";

    /**
     * @param component dataTable
     * @return number of layout columns
     */
    protected int getNewspaperColumns(UIComponent component) {
        if (component instanceof NewspaperTable) {
            // the number of slices to break the table up into */
            NewspaperTable newspaperTable = (NewspaperTable) component;
            return newspaperTable.getNewspaperColumns();
        }
        return super.getNewspaperColumns(component);
    }

    /**
     * @param component dataTable
     * @return component to display between layout columns
     */
    protected UIComponent getNewspaperTableSpacer(UIComponent component) {
        if (component instanceof NewspaperTable) {
            // the number of slices to break the table up into */
            NewspaperTable newspaperTable = (NewspaperTable) component;
            return newspaperTable.getSpacer();
        }
        return super.getNewspaperTableSpacer(component);
    }

    /**
     * @param component dataTable
     * @return whether dataTable has component to display between layout columns
     */
    protected boolean hasNewspaperTableSpacer(UIComponent component) {
        if (null != getNewspaperTableSpacer(component)) {
            return true;
        }
        return super.hasNewspaperTableSpacer(component);
    }

    /**
     * @param component dataTable
     * @return if the orientation of the has newspaper columns is horizontal
     */
    protected boolean isNewspaperHorizontalOrientation(UIComponent component) {
        if (component instanceof NewspaperTable) {
            // get the value of the newspaperOrientation attribute, any value besides horizontal
            // means vertical, the default
            NewspaperTable newspaperTable = (NewspaperTable) component;
            return NewspaperTable.NEWSPAPER_HORIZONTAL_ORIENTATION.equals(newspaperTable.getNewspaperOrientation());
        }
        return super.isNewspaperHorizontalOrientation(component);
    }

    protected void startTable(FacesContext facesContext, UIComponent uiComponent) throws IOException {
        boolean embedded = isEmbeddedTable(uiComponent);

        if (!embedded) {
            super.startTable(facesContext, uiComponent);
        }
    }

    protected String determineHeaderFooterTag(FacesContext facesContext, UIComponent component, boolean header) {
        if (isEmbeddedTable(component)) {
            // we are embedded, so do not render the tfoot/thead stuff
            return null;
        }

        return super.determineHeaderFooterTag(facesContext, component, header);
    }

    protected String determineHeaderCellTag(FacesContext facesContext, UIComponent component) {
        if (isEmbeddedTable(component)) {
            return HTML.TD_ELEM;
        }

        return HTML.TH_ELEM;
    }

    protected void renderTableHeaderOrFooterRow(FacesContext facesContext, ResponseWriter writer, UIComponent component, UIComponent facet, String styleClass, String colElementName, int colspan) throws IOException {
        if (isEmbeddedTable(component)) {
            // embedded tables render the header/footer stuff using TD only
            colElementName = HTML.TD_ELEM;
        }

        super.renderTableHeaderOrFooterRow(facesContext, writer, component, facet, styleClass, colElementName, colspan);
    }

    protected boolean isEmbeddedTable(UIComponent uiComponent) {
        boolean embedded = false;
        if (uiComponent instanceof HtmlDataTable) {
            HtmlDataTable table = (HtmlDataTable) uiComponent;
            embedded = table.isEmbedded();
        }
        return embedded;
    }

    protected boolean isDetailStampAfterRow(UIComponent uiComponent) {
        if (uiComponent instanceof HtmlDataTable) {
            return "after".equals(((HtmlDataTable) uiComponent).getDetailStampLocation());
        }

        return true;
    }

    protected void endTable(FacesContext facesContext, UIComponent uiComponent) throws IOException {
        boolean embedded = isEmbeddedTable(uiComponent);

        if (!embedded) {
            super.endTable(facesContext, uiComponent);
        }
    }

    protected void beforeRow(FacesContext facesContext, UIData uiData) throws IOException {
        super.beforeRow(facesContext, uiData);

        if (!isDetailStampAfterRow(uiData)) {
            renderDetailRow(facesContext, uiData);
        }
    }

    protected void afterRow(FacesContext facesContext, UIData uiData) throws IOException {
        super.afterRow(facesContext, uiData);

        if (isDetailStampAfterRow(uiData)) {
            renderDetailRow(facesContext, uiData);
        }
    }

    /**
     * @param facesContext
     * @param uiData
     * @throws IOException
     */
    private void renderDetailRow(FacesContext facesContext, UIData uiData) throws IOException {
        UIComponent detailStampFacet = uiData.getFacet(DETAIL_STAMP_FACET_NAME);

        if (uiData instanceof HtmlDataTable) {
            HtmlDataTable htmlDataTable = (HtmlDataTable) uiData;

            if (htmlDataTable.isCurrentDetailExpanded()) {

                boolean embedded = false;
                if (detailStampFacet != null) {
                    embedded = isEmbeddedTable(detailStampFacet);
                }

                ResponseWriter writer = facesContext.getResponseWriter();

                if (!embedded) {
                    writer.startElement(HTML.TR_ELEM, uiData);
                    writer.startElement(HTML.TD_ELEM, uiData);
                    //TOMAHAWK-1087 datatable dont renders a detail correct 
                    //if a UIColumns is used we have to count UIColumns 
                    //elements as component.getRowCount()
                    //instead of just get the number of children available,
                    //so the colspan could be assigned correctly.
                    int childCount = 0;
                    for (Iterator childIter = uiData.getChildren().iterator();
                        childIter.hasNext();)
                    {
                        UIComponent childComp = (UIComponent) childIter.next();
                        if (childComp instanceof UIColumns)
                        {
                            UIColumns v = (UIColumns) childComp;
                            childCount += v.getRowCount();
                        }
                        else
                        {
                            childCount++;
                        }
                    }
                    writer.writeAttribute(HTML.COLSPAN_ATTR, new Integer(
                            childCount), null);
                }

                if (detailStampFacet != null) {
                    RendererUtils.renderChild(facesContext, detailStampFacet);
                }

                if (!embedded) {
                    writer.endElement(HTML.TD_ELEM);
                    writer.endElement(HTML.TR_ELEM);
                }
            }
        }
    }

    /**
     * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#encodeBegin(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
     */
    public void encodeBegin(FacesContext facesContext, UIComponent uiComponent) throws IOException {
        if (uiComponent instanceof HtmlDataTable) {
            HtmlDataTable htmlDataTable = (HtmlDataTable) uiComponent;
            if (htmlDataTable.isRenderedIfEmpty() || htmlDataTable.getRowCount() > 0) {
                super.encodeBegin(facesContext, uiComponent);
            }
        }
        else {
            super.encodeBegin(facesContext, uiComponent);
        }
    }

    /**
     * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#encodeChildren(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
     */
    public void encodeChildren(FacesContext facesContext, UIComponent component) throws IOException {
        if (component instanceof HtmlDataTable) {
            HtmlDataTable htmlDataTable = (HtmlDataTable) component;
            if (htmlDataTable.isRenderedIfEmpty() || htmlDataTable.getRowCount() > 0) {
                super.encodeChildren(facesContext, component);
            }
        }
        else {
            super.encodeChildren(facesContext, component);
        }
    }

    private boolean isGroupedTable(UIData uiData) {
        if (uiData instanceof HtmlDataTable) {
            List children = getChildren(uiData);
            for (int j = 0, size = getChildCount(uiData); j < size; j++) {
                UIComponent child = (UIComponent) children.get(j);
                if (child instanceof HtmlSimpleColumn) {
                    HtmlSimpleColumn column = (HtmlSimpleColumn) child;
                    if (column.isGroupBy()) {
                        return true;
                    }
                }
            }
        }

        return false;  //To change body of created methods use File | Settings | File Templates.
    }

    protected void beforeBody(FacesContext facesContext, UIData uiData) throws IOException {
        if (isGroupedTable(uiData)) {
            createColumnInfos((HtmlDataTable) uiData, facesContext);
        }
        super.beforeBody(facesContext, uiData);
    }

    private void createColumnInfos(HtmlDataTable htmlDataTable, FacesContext facesContext)
        throws IOException {
        int first = htmlDataTable.getFirst();
        int rows = htmlDataTable.getRows();
        int last;
        int currentRowSpan = -1;
        int currentRowInfoIndex = -1;

        TableContext tableContext = htmlDataTable.getTableContext();
        RowInfo rowInfo = null;
        ColumnInfo columnInfo = null;
        HtmlSimpleColumn currentColumn = null;
        Map groupHashTable = new HashMap();

        if (rows <= 0) {
            last = htmlDataTable.getRowCount();
        }
        else {
            last = first + rows;
        }

        //Loop over the Children Columns to find the Columns with groupBy Attribute true
        List children = getChildren(htmlDataTable);
        int nChildren = getChildCount(htmlDataTable);

        for (int j = 0, size = nChildren; j < size; j++) {
            UIComponent child = (UIComponent) children.get(j);
            if (child instanceof HtmlSimpleColumn) {
                currentColumn = (HtmlSimpleColumn) child;
                if (currentColumn.isGroupBy()) {
                    groupHashTable.put(new Integer(j), null);
                }
            }
        }

        boolean groupEndReached = false;

        for (int rowIndex = first; last == -1 || rowIndex < last; rowIndex++) {
            htmlDataTable.setRowIndex(rowIndex);
            rowInfo = new RowInfo();
            //scrolled past the last row
            if (!htmlDataTable.isRowAvailable()) {
                break;
            }

            Set groupIndexList = groupHashTable.keySet();
            List currentColumnContent = null;
            for (Iterator it = groupIndexList.iterator(); it.hasNext();) {
                currentColumnContent = new ArrayList();
                Integer currentIndex = (Integer) it.next();
                currentColumn = (HtmlSimpleColumn) children.get(currentIndex.intValue());

                if (currentColumn.isGroupByValueSet()) {
                    currentColumnContent.add(currentColumn.getGroupByValue());
                }
                else {
                    // iterate the children - this avoids to add the column facet too
                    List currentColumnChildren = currentColumn.getChildren();
                    if (currentColumnChildren != null) {
                        collectChildrenValues(currentColumnContent, currentColumnChildren.iterator());
                    }
                }

                if (!isListEqual(currentColumnContent, (List) groupHashTable.get(currentIndex)) &&
                    currentRowInfoIndex > -1) {
                    groupEndReached = true;
                    groupHashTable.put(currentIndex, currentColumnContent);
                }
                else if (currentRowInfoIndex == -1) {
                    groupHashTable.put(currentIndex, currentColumnContent);
                }
            }
            currentRowSpan++;


            for (int j = 0, size = nChildren; j < size; j++) {
                columnInfo = new ColumnInfo();
                if (groupHashTable.containsKey(new Integer(j)))  // Column is groupBy
                {
                    if (currentRowSpan > 0) {
                        if (groupEndReached) {
                            ((ColumnInfo)
                                ((RowInfo)
                                    tableContext.getRowInfos().get(currentRowInfoIndex - currentRowSpan + 1)).
                                    getColumnInfos().get(j)).
                                setRowSpan(currentRowSpan);
                            columnInfo.setStyle(htmlDataTable.getRowGroupStyle());
                            columnInfo.setStyleClass(htmlDataTable.getRowGroupStyleClass());
                        }
                        else {
                            columnInfo.setRendered(false);
                        }
                    }
                    else {
                        columnInfo.setStyle(htmlDataTable.getRowGroupStyle());
                        columnInfo.setStyleClass(htmlDataTable.getRowGroupStyleClass());
                    }

                }
                else    // Column  is not group by
                {
                    if (groupEndReached) {
                        ((ColumnInfo)
                            ((RowInfo)
                                tableContext.getRowInfos().get(currentRowInfoIndex)).
                                getColumnInfos().get(j)).
                            setStyle(htmlDataTable.getRowGroupStyle());
                        ((ColumnInfo)
                            ((RowInfo)
                                tableContext.getRowInfos().get(currentRowInfoIndex)).
                                getColumnInfos().get(j)).
                            setStyleClass(htmlDataTable.getRowGroupStyleClass());
                    }
                }
                rowInfo.getColumnInfos().add(columnInfo);
            }
            if (groupEndReached) {
                currentRowSpan = 0;
                groupEndReached = false;
            }
            tableContext.getRowInfos().add(rowInfo);
            currentRowInfoIndex++;
        }

        // do further processing if we've found at least one row
        if (currentRowInfoIndex > -1) {
            for (int j = 0, size = nChildren; j < size; j++) {
                if (groupHashTable.containsKey(new Integer(j)))  // Column is groupBy
                {
                    ((ColumnInfo)
                        ((RowInfo)
                            tableContext.getRowInfos().get(currentRowInfoIndex - currentRowSpan)).
                            getColumnInfos().get(j)).
                        setRowSpan(currentRowSpan + 1);
                }
                else    // Column  is not group by
                {
                    ((ColumnInfo)
                        ((RowInfo)
                            tableContext.getRowInfos().get(currentRowInfoIndex)).
                            getColumnInfos().get(j)).
                        setStyle(htmlDataTable.getRowGroupStyle());
                    ((ColumnInfo)
                        ((RowInfo)
                            tableContext.getRowInfos().get(currentRowInfoIndex)).
                            getColumnInfos().get(j)).
                        setStyleClass(htmlDataTable.getRowGroupStyleClass());
                }
            }
        }

        htmlDataTable.setRowIndex(-1);
    }

    protected void collectChildrenValues(List container, Iterator iterChildren) {
        while (iterChildren.hasNext()) {
            UIComponent child = (UIComponent) iterChildren.next();
            if (child.isRendered()) {
                if (child instanceof ValueHolder) {
                    Object value = ((ValueHolder) child).getValue();
                    container.add(value);
                }

                // we lave the column ... now iterate the facets too
                collectChildrenValues(container, child.getFacetsAndChildren());
            }
        }
    }

    /**
     * checks if the contenta of both lists are the same.<br />
     * <b>Notice:</b> In case both lists are null or empty they are
     * considered NOT being equal
     */
    protected boolean isListEqual(List list1, List list2) {
        if (list1 == list2) {
            return list1 != null && list1.size() > 0;
        }
        if (list1 == null || list2 == null || list1.size() != list2.size()) {
            return false;
        }

        for (int i = 0; i < list1.size(); i++) {
            Object o1 = list1.get(i);
            Object o2 = list2.get(i);

            if (o1 != null && !o1.equals(o2)) {
                return false;
            }
            if (o2 != null && !o2.equals(o1)) {
                return false;
            }
        }

        return true;
    }


    /**
     * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#encodeEnd(javax.faces.context.FacesContext, javax.faces.component.UIComponent)
     */
    public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException {
        if (uiComponent instanceof HtmlDataTable) {
            HtmlDataTable htmlDataTable = (HtmlDataTable) uiComponent;
            if (htmlDataTable.isRenderedIfEmpty() || htmlDataTable.getRowCount() > 0) {
                super.encodeEnd(facesContext, uiComponent);
            }
        }
        else {
            super.encodeEnd(facesContext, uiComponent);
        }
    }

    protected void renderRowStart(FacesContext facesContext,
                                  ResponseWriter writer, UIData uiData, Styles styles, int rowStyleIndex)
        throws IOException {
        super.renderRowStart(facesContext, writer, uiData, styles, rowStyleIndex);

        // get event handlers from component
        HtmlDataTable table = (HtmlDataTable) uiData;

        renderRowAttribute(writer, HTML.ONCLICK_ATTR, table.getRowOnClick());
        renderRowAttribute(writer, HTML.ONDBLCLICK_ATTR, table.getRowOnDblClick());
        renderRowAttribute(writer, HTML.ONKEYDOWN_ATTR, table.getRowOnKeyDown());
        renderRowAttribute(writer, HTML.ONKEYPRESS_ATTR, table.getRowOnKeyPress());
        renderRowAttribute(writer, HTML.ONKEYUP_ATTR, table.getRowOnKeyUp());
        renderRowAttribute(writer, HTML.ONMOUSEDOWN_ATTR, table.getRowOnMouseDown());
        renderRowAttribute(writer, HTML.ONMOUSEMOVE_ATTR, table.getRowOnMouseMove());
        renderRowAttribute(writer, HTML.ONMOUSEOUT_ATTR, table.getRowOnMouseOut());
        renderRowAttribute(writer, HTML.ONMOUSEOVER_ATTR, table.getRowOnMouseOver());
        renderRowAttribute(writer, HTML.ONMOUSEUP_ATTR, table.getRowOnMouseUp());
    }

    /**
     * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#renderRowStyle(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIData, org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase.Styles, int)
     */
    protected void renderRowStyle(FacesContext facesContext, ResponseWriter writer, UIData uiData, Styles styles, int rowStyleIndex) throws IOException {
        String rowStyleClass;
        String rowStyle;
        if (uiData instanceof HtmlDataTable) {
            HtmlDataTable datatable = (HtmlDataTable) uiData;
            rowStyleClass = datatable.getRowStyleClass();
            rowStyle = datatable.getRowStyle();
        }
        else {
            rowStyleClass = (String) uiData.getAttributes().get(JSFAttr.ROW_STYLECLASS_ATTR);
            rowStyle = (String) uiData.getAttributes().get(JSFAttr.ROW_STYLE_ATTR);
        }
        if (rowStyleClass == null) {
            super.renderRowStyle(facesContext, writer, uiData, styles, rowStyleIndex);
        }
        else if (rowStyleClass != null && rowStyleClass.length() <= 0)
        {
            // TOMAHAWK-1628 rowStyleClass can hold an EL that return null and is coerced to empty string
            super.renderRowStyle(facesContext, writer, uiData, styles, rowStyleIndex);
        }
        else {
            writer.writeAttribute(HTML.CLASS_ATTR, rowStyleClass, null);
        }
        if (rowStyle != null) {
            writer.writeAttribute(HTML.STYLE_ATTR, rowStyle, null);
        }
    }

    protected void renderRowAttribute(ResponseWriter writer,
                                      String htmlAttribute, Object value) throws IOException {
        if (value != null) {
            writer.writeAttribute(htmlAttribute, value, null);
        }
    }

    /**
     * Render the specified column object using the current row data.
     * <p/>
     * When the component is a UIColumn object, the inherited method is
     * invoked to render a single table cell.
     * <p/>
     * In addition to the inherited functionality, support is implemented
     * here for UIColumns children. When a UIColumns child is encountered:
     * <pre>
     * For each dynamic column in that UIColumns child:
     *   * Select the column (which sets variable named by the var attribute
     *     to refer to the current column object)
     *   * Call this.renderColumnBody passing the UIColumns object.
     * </pre>
     * The renderColumnBody method eventually:
     * <ul>
     * <li>emits TD
     * <li>calls encodeBegin on the UIColumns (which does nothing)
     * <li>calls rendering methods on all children of the UIColumns
     * <li>calls encodeEnd on the UIColumns (which does nothing)
     * <li> emits /TD
     * </ul>
     * If the children of the UIColumns access the variable named by the var
     * attribute on the UIColumns object, then they end up rendering content
     * that is extracted from the current column object.
     *
     * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#encodeColumnChild(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIData, javax.faces.component.UIComponent, org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase.Styles, int)
     */
    protected void encodeColumnChild(FacesContext facesContext,
                                     ResponseWriter writer, UIData uiData,
                                     UIComponent component, Styles styles, int columnStyleIndex)
        throws IOException {        
        //columnStyleIndex param does not takes into
        //consideration UIColumns, since it is called from HtmlTableRendererBase.
        //So we need to recalculate its index taking into account that
        //UIColumns component count as ((UIColumns) child).getRowCount() instead 1 
        columnStyleIndex = getColumnStyleIndex(uiData, columnStyleIndex);
        super.encodeColumnChild(facesContext, writer, uiData, component,
            styles, columnStyleIndex);
        if (component instanceof UIColumns)
        {
            UIColumns columns = (UIColumns) component;
            for (int k = 0, colSize = columns.getRowCount(); k < colSize; k++)
            {
                columns.setRowIndex(k);
                renderColumnBody(facesContext, writer, uiData, component,
                    styles, columnStyleIndex + k);
            }
            columns.setRowIndex(-1);
        }
    }
    
    /**
      * This method calculates the correct columnStyleIndex taking the dynamic columns
      * of <b>UIColumns</b> into consideration
      * 
      * @param uiData
      * @param columnStyleIndex
      * @return the correct columnStyleIndex 
      */
    private int getColumnStyleIndex(UIData uiData, int columnStyleIndex)
    {
        int colStyleIndex = 0;
        for (int i = 0; i < columnStyleIndex; i++)
        {
            UIComponent child = (UIComponent) uiData.getChildren().get(i % uiData.getChildCount());
            if (child instanceof UIColumns)
            {
                colStyleIndex += ((UIColumns) child).getRowCount();
                continue;
            }
            colStyleIndex++;
        }
        return colStyleIndex;
    }
    

    /**
     * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#renderColumnBody(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIData, javax.faces.component.UIComponent, org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase.Styles, int)
     */
    protected void renderColumnBody(FacesContext facesContext,
                                    ResponseWriter writer, UIData uiData,
                                    UIComponent component, Styles styles, int columnStyleIndex)
        throws IOException {
        if (isGroupedTable(uiData)) {

            HtmlDataTable htmlDataTable = (HtmlDataTable) uiData;
            List tableChildren = htmlDataTable.getChildren();

            int first = htmlDataTable.getFirst();
            int rowInfoIndex = htmlDataTable.getRowIndex() - first;
            int columnInfoIndex = tableChildren.indexOf(component);

            RowInfo rowInfo = (RowInfo) htmlDataTable.getTableContext().getRowInfos().get(rowInfoIndex);
            ColumnInfo columnInfo = (ColumnInfo) rowInfo.getColumnInfos().get(columnInfoIndex);

            if (!columnInfo.isRendered()) {
                return;
            }

            if (component instanceof HtmlColumn && amISpannedOver(null, component)) {
                return;
            }
            writer.startElement(HTML.TD_ELEM, uiData);
            String styleClass = null;
            if (component instanceof HtmlColumn) {
                styleClass = ((HtmlColumn) component).getStyleClass();
            }
            if (columnInfo.getStyleClass() != null) {
                styleClass = columnInfo.getStyleClass();
            }

            if (styles.hasColumnStyle()) {
                if (styleClass == null) {
                    styleClass = styles.getColumnStyle(columnStyleIndex);
                }
            }
            if (styleClass != null) {
                writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
            }

            if (columnInfo.getStyle() != null) {
                writer.writeAttribute(HTML.STYLE_ATTR, columnInfo.getStyle(), null);
            }

            if (columnInfo.getRowSpan() > 1) {
                writer.writeAttribute("rowspan", new Integer(columnInfo.getRowSpan()).toString(), null);
                if (columnInfo.getStyle() == null) {
                    writer.writeAttribute(HTML.STYLE_ATTR, "vertical-align:top", null);
                }
            }

            renderHtmlColumnAttributes(writer, component, null);

            RendererUtils.renderChild(facesContext, component);
            writer.endElement(HTML.TD_ELEM);
        }
        else if (component instanceof HtmlColumn) {
            if (amISpannedOver(null, component)) {
                return;
            }
            writer.startElement(HTML.TD_ELEM, uiData);
            String styleClass = ((HtmlColumn) component).getStyleClass();
            if (styles.hasColumnStyle()) {
                if (styleClass == null) {
                    styleClass = styles.getColumnStyle(columnStyleIndex);
                }
            }
            if (styleClass != null) {
                writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
            }
            renderHtmlColumnAttributes(writer, component, null);

            RendererUtils.renderChild(facesContext, component);
            writer.endElement(HTML.TD_ELEM);
        }
        else {
            super.renderColumnBody(facesContext, writer, uiData, component,
                styles, columnStyleIndex);
        }
    }

    /**
     * Render the header or footer of the specified column object.
     * <p/>
     * When the component is a UIColumn object, the inherited method is
     * invoked to render a single header cell.
     * <p/>
     * In addition to the inherited functionality, support is implemented
     * here for UIColumns children. When a UIColumns child is encountered:
     * <pre>
     * For each dynamic column in that UIColumns child:
     *   * Select the column (which sets variable named by the var attribute
     *     to refer to the current column object)
     *   * Call this.renderColumnHeaderCell or this.renderColumnFooterCell
     *     passing the header or footer facet of the UIColumns object.
     * </pre>
     * If the facet of the UIColumns accesses the variable named by the var
     * attribute on the UIColumns object, then it ends up rendering content
     * that is extracted from the current column object.
     *
     * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#renderColumnChildHeaderOrFooterRow(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIComponent, java.lang.String, boolean)
     */
    protected void renderColumnChildHeaderOrFooterRow(
        FacesContext facesContext, ResponseWriter writer,
        UIComponent uiComponent, String styleClass, boolean header)
        throws IOException {
        super.renderColumnChildHeaderOrFooterRow(facesContext, writer,
            uiComponent, styleClass, header);
        if (uiComponent instanceof UIColumns) {
            UIColumns columns = (UIColumns) uiComponent;
            for (int i = 0, size = columns.getRowCount(); i < size; i++) {
                columns.setRowIndex(i);
                if (header) {
                    renderColumnHeaderCell(facesContext, writer, columns,
                        columns.getHeader(), styleClass, 0);
                }
                else {
                    renderColumnFooterCell(facesContext, writer, columns,
                        columns.getFooter(), styleClass, 0);
                }
            }
            columns.setRowIndex(-1);
        }
    }

    /**
     * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#renderColumnHeaderCell(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIComponent, javax.faces.component.UIComponent, java.lang.String, int)
     */
    protected void renderColumnHeaderCell(FacesContext facesContext,
                                          ResponseWriter writer, UIComponent uiComponent,
                                          UIComponent facet, String headerStyleClass, int colspan)
        throws IOException {
        if (uiComponent instanceof HtmlColumn) {

            HtmlColumn column = (HtmlColumn) uiComponent;

            if (amISpannedOver("header", uiComponent)) {
                return;
            }

            writer.startElement(determineHeaderCellTag(facesContext, uiComponent.getParent()), uiComponent);

            String columnId = column.getColumnId();
            if (columnId != null)
            {
                writer.writeAttribute(HTML.ID_ATTR, columnId, null);
            }

            if (colspan > 1) {
                writer.writeAttribute(HTML.COLSPAN_ATTR, new Integer(colspan),
                    null);
            }
            String styleClass = ((HtmlColumn) uiComponent)
                .getHeaderstyleClass();
            if (styleClass == null) {
                styleClass = headerStyleClass;
            }
            if (styleClass != null) {
                writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
            }
            renderHtmlColumnAttributes(writer, uiComponent, "header");
            if (facet != null) {
                RendererUtils.renderChild(facesContext, facet);
            }
            writer.endElement(determineHeaderCellTag(facesContext, uiComponent.getParent()));
        }
        else {
            super.renderColumnHeaderCell(facesContext, writer, uiComponent,
                facet, headerStyleClass, colspan);
        }
    }

    /**
     * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#renderColumnFooterCell(javax.faces.context.FacesContext, javax.faces.context.ResponseWriter, javax.faces.component.UIComponent, javax.faces.component.UIComponent, java.lang.String, int)
     */
    protected void renderColumnFooterCell(FacesContext facesContext,
                                          ResponseWriter writer, UIComponent uiComponent,
                                          UIComponent facet, String footerStyleClass, int colspan)
        throws IOException {
        if (uiComponent instanceof HtmlColumn) {
            if (amISpannedOver("footer", uiComponent)) {
                return;
            }
            writer.startElement(HTML.TD_ELEM, uiComponent);
            if (colspan > 1) {
                writer.writeAttribute(HTML.COLSPAN_ATTR, new Integer(colspan),
                    null);
            }
            String styleClass = ((HtmlColumn) uiComponent)
                .getFooterstyleClass();
            if (styleClass == null) {
                styleClass = footerStyleClass;
            }
            if (styleClass != null) {
                writer.writeAttribute(HTML.CLASS_ATTR, styleClass, null);
            }
            renderHtmlColumnAttributes(writer, uiComponent, "footer");
            if (facet != null) {
                RendererUtils.renderChild(facesContext, facet);
            }
            writer.endElement(HTML.TD_ELEM);
        }
        else {
            super.renderColumnFooterCell(facesContext, writer, uiComponent,
                facet, footerStyleClass, colspan);
        }
    }

    protected void renderHtmlColumnAttributes(ResponseWriter writer,
                                              UIComponent uiComponent, String prefix) throws IOException {
        String[] attrs = (String[]) ArrayUtils.concat(HTML.COMMON_PASSTROUGH_ATTRIBUTES_WITHOUT_STYLE, new String[]{HTML.COLSPAN_ATTR});
        for (int i = 0, size = attrs.length; i < size; i++) {
            String attributeName = attrs[i];
            String compAttrName = prefix != null ? prefix + attributeName : attributeName;
            HtmlRendererUtils.renderHTMLAttribute(writer, uiComponent,
                compAttrName, attributeName);
        }
        String compAttrName = prefix != null ? prefix + HTML.STYLE_ATTR : HTML.STYLE_ATTR;
        HtmlRendererUtils.renderHTMLAttribute(writer, uiComponent,
            compAttrName, HTML.STYLE_ATTR);

        HtmlRendererUtils.renderHTMLAttribute(writer, uiComponent,
            HTML.WIDTH_ATTR, HTML.WIDTH_ATTR);
    }

    /**
     * Return the number of columns spanned by the specified component.
     * <p/>
     * For normal components, use the inherited implementation.
     * For UIColumns children, return the number of dynamic columns rendered
     * by that child.
     *
     * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase
     *      #determineChildColSpan(javax.faces.component.UIComponent)
     */
    protected int determineChildColSpan(UIComponent uiComponent) {
        int result = super.determineChildColSpan(uiComponent);
        if (uiComponent instanceof UIColumns) {
            result += ((UIColumns) uiComponent).getRowCount();
        }
        return result;
    }

    /**
     * Return true if the specified component has a facet that needs to be
     * rendered in a THEAD or TFOOT section.
     *
     * @see org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlTableRendererBase#hasFacet(boolean, javax.faces.component.UIComponent)
     */
    protected boolean hasFacet(boolean header, UIComponent uiComponent) {
        boolean result = super.hasFacet(header, uiComponent);
        if (!result && uiComponent instanceof UIColumns) {
            // Why is this necessary? It seems to me that the inherited
            // implementation will work fine with a UIColumns component...
            UIColumns columns = (UIColumns) uiComponent;
            result = header ? columns.getHeader() != null : columns.getFooter() != null;
        }
        return result;
    }

    /**
     * Renders the column footer.
     * Rendering will be supressed if all of the facets have rendered="false"
     */
    protected void renderColumnFooterRow(FacesContext facesContext, ResponseWriter writer, UIComponent component, String footerStyleClass) throws IOException {
        if (determineRenderFacet(component, false)) {
            super.renderColumnFooterRow(facesContext, writer, component, footerStyleClass);
        }
    }

    /**
     * Renders the column header.
     * Rendering will be supressed if all of the facets have rendered="false"
     */
    protected void renderColumnHeaderRow(FacesContext facesContext, ResponseWriter writer, UIComponent component, String headerStyleClass) throws IOException {
        if (determineRenderFacet(component, true)) {
            super.renderColumnHeaderRow(facesContext, writer, component, headerStyleClass);
        }
    }

    /**
     * determine if the header or footer should be rendered.
     */
    protected boolean determineRenderFacet(UIComponent component, boolean header) {
        for (Iterator it = getChildren(component).iterator(); it.hasNext();) {
            UIComponent uiComponent = (UIComponent) it.next();
            if (uiComponent.isRendered() && determineChildColSpan(uiComponent) > 0) {
                UIComponent facet = header ? (UIComponent) uiComponent.getFacets().get(HEADER_FACET_NAME)
                    : (UIComponent) uiComponent.getFacets().get(FOOTER_FACET_NAME);

                if (facet != null && facet.isRendered()) {
                    return true;
                }
            }
        }

        return false;
    }

    protected void beforeColumn(FacesContext facesContext, UIData uiData, int columnIndex) throws IOException {
        super.beforeColumn(facesContext, uiData, columnIndex);

        if (uiData instanceof HtmlDataTable) {
            HtmlDataTable dataTable = (HtmlDataTable) uiData;

            putSortedReqScopeParam(facesContext, dataTable, columnIndex);
        }
    }

    protected void beforeColumnHeaderOrFooter(FacesContext facesContext, UIData uiData, boolean header, int columnIndex) throws IOException {
        super.beforeColumnHeaderOrFooter(facesContext, uiData, header, columnIndex);

        if (header && uiData instanceof HtmlDataTable) {
            HtmlDataTable dataTable = (HtmlDataTable) uiData;

            putSortedReqScopeParam(facesContext, dataTable, columnIndex);
        }
    }

    protected void putSortedReqScopeParam(FacesContext facesContext, HtmlDataTable dataTable, int columnIndex) {
        String sortedColumnVar = dataTable.getSortedColumnVar();
        Map requestMap = facesContext.getExternalContext().getRequestMap();

        if (columnIndex == dataTable.getSortColumnIndex()) {
            if (sortedColumnVar != null) {
                requestMap.put(sortedColumnVar, Boolean.TRUE);
            }
        }
        else if (sortedColumnVar != null) {
            requestMap.remove(sortedColumnVar);
        }
    }

    /**
     * specify if the header, footer or <td> is spanned over (not shown) because
     * of a colspan in a cell in a previous column
     *
     * @param prefix    header, footer or null
     * @param component
     */
    protected boolean amISpannedOver(String prefix, UIComponent component) {
        UIComponent table = component.getParent();
        int span = 0;
        for (Iterator it = table.getChildren().iterator(); it.hasNext();) {
            UIComponent columnComponent = (UIComponent) it.next();
            if (!(columnComponent instanceof HtmlColumn)) {
                continue;
            }
            if (span > 0) {
                span--;
            }
            if (columnComponent == component) {
                return span > 0;
            }
            if (span == 0) {
                try {
                    if (prefix == null && ((HtmlColumn) columnComponent).getColspan() != null) {
                        span = Integer.parseInt(((HtmlColumn) columnComponent).getColspan());
                    }
                    if ("header".equals(prefix) && ((HtmlColumn) columnComponent).getHeadercolspan() != null) {
                        span = Integer.parseInt(((HtmlColumn) columnComponent).getHeadercolspan());
                    }
                    if ("footer".equals(prefix) && ((HtmlColumn) columnComponent).getFootercolspan() != null) {
                        span = Integer.parseInt(((HtmlColumn) columnComponent).getFootercolspan());
                    }
                }
                catch (NumberFormatException ex) {
                    log.warn("Invalid " + ((prefix == null) ? "" : prefix) + "colspan attribute ignored");
                }
            }
        }
        return false;
    }


    /**
     * Perform any operations necessary in the TBODY start tag.
     *
     * @param facesContext the <code>FacesContext</code>.
     * @param uiData       the <code>UIData</code> being rendered.
     */
    protected void inBodyStart(FacesContext facesContext, UIData uiData) throws IOException {
        String bodyStyleClass;
        String bodyStyle;

        if (uiData instanceof HtmlDataTable) {
            bodyStyleClass = ((HtmlDataTable) uiData).getBodyStyleClass();
            bodyStyle = ((HtmlDataTable) uiData).getBodyStyle();
        }
        else {
            bodyStyleClass = (String) uiData.getAttributes().get(BODY_STYLE_CLASS);
            bodyStyle = (String) uiData.getAttributes().get(BODY_STYLE);
        }

        ResponseWriter writer = facesContext.getResponseWriter();
        if (bodyStyleClass != null) {
            writer.writeAttribute(HTML.CLASS_ATTR, bodyStyleClass,
                BODY_STYLE_CLASS);
        }
        if (bodyStyle != null) {
            writer.writeAttribute(HTML.STYLE_ATTR, bodyStyle, BODY_STYLE);
        }
    }


}
