/*
 * 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.tobago.renderkit.html.scarborough.standard.tag;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.tobago.TobagoConstants;
import org.apache.myfaces.tobago.ajax.api.AjaxPhaseListener;
import org.apache.myfaces.tobago.ajax.api.AjaxRenderer;
import org.apache.myfaces.tobago.ajax.api.AjaxResponseRenderer;
import org.apache.myfaces.tobago.ajax.api.AjaxUtils;
import org.apache.myfaces.tobago.component.ComponentUtil;
import org.apache.myfaces.tobago.component.UIColumnEvent;
import org.apache.myfaces.tobago.component.UIColumnSelector;
import org.apache.myfaces.tobago.component.UICommand;
import org.apache.myfaces.tobago.component.UIData;
import org.apache.myfaces.tobago.component.UIMenu;
import org.apache.myfaces.tobago.component.UIMenuCommand;
import org.apache.myfaces.tobago.component.UIPage;
import org.apache.myfaces.tobago.component.UIReload;
import org.apache.myfaces.tobago.component.UISelectOne;
import org.apache.myfaces.tobago.config.TobagoConfig;
import org.apache.myfaces.tobago.context.ClientProperties;
import org.apache.myfaces.tobago.context.ResourceManager;
import org.apache.myfaces.tobago.context.ResourceManagerFactory;
import org.apache.myfaces.tobago.context.ResourceManagerUtil;
import org.apache.myfaces.tobago.event.PageAction;
import org.apache.myfaces.tobago.model.SheetState;
import org.apache.myfaces.tobago.renderkit.LayoutableRendererBase;
import org.apache.myfaces.tobago.renderkit.RenderUtil;
import org.apache.myfaces.tobago.renderkit.SheetRendererWorkaround;
import org.apache.myfaces.tobago.renderkit.html.CommandRendererHelper;
import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
import org.apache.myfaces.tobago.renderkit.html.HtmlConstants;
import org.apache.myfaces.tobago.renderkit.html.HtmlRendererUtil;
import org.apache.myfaces.tobago.renderkit.html.HtmlStyleMap;
import org.apache.myfaces.tobago.renderkit.html.StyleClasses;
import org.apache.myfaces.tobago.util.StringUtils;
import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;

import javax.faces.application.Application;
import javax.faces.component.UIColumn;
import javax.faces.component.UIComponent;
import javax.faces.component.UIPanel;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ACTION_ONCLICK;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_ALIGN;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_DIRECT_LINK_COUNT;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_DISABLED;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_FOOTER_HEIGHT;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_FORCE_VERTICAL_SCROLLBAR;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_IMAGE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INLINE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LABEL;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_HEIGHT;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_MENU_POPUP;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_MENU_POPUP_TYPE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SELECTED_LIST_STRING;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SHOW_DIRECT_LINKS;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SHOW_PAGE_RANGE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SHOW_ROW_RANGE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SORTABLE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE_BODY;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE_HEADER;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_TIP;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_WIDTH_LIST_STRING;
import static org.apache.myfaces.tobago.TobagoConstants.FACET_MENUPOPUP;
import static org.apache.myfaces.tobago.TobagoConstants.FACET_PAGER_PAGE;
import static org.apache.myfaces.tobago.TobagoConstants.FACET_PAGER_ROW;
import static org.apache.myfaces.tobago.TobagoConstants.FACET_RELOAD;
import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_LINK;
import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_MENUBAR;
import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_MENUCOMMAND;
import static org.apache.myfaces.tobago.TobagoConstants.SUBCOMPONENT_SEP;
import static org.apache.myfaces.tobago.component.UIData.ATTR_SCROLL_POSITION;
import static org.apache.myfaces.tobago.component.UIData.NONE;

public class SheetRenderer extends LayoutableRendererBase implements SheetRendererWorkaround, AjaxRenderer {

  private static final Log LOG = LogFactory.getLog(SheetRenderer.class);

  public static final String WIDTHS_POSTFIX = SUBCOMPONENT_SEP + "widths";
  public static final String SCROLL_POSTFIX = SUBCOMPONENT_SEP + "scrollPosition";
  public static final String SELECTED_POSTFIX = SUBCOMPONENT_SEP + "selected";

  private static final Integer HEIGHT_0 = 0;

  public void encodeEnd(FacesContext facesContext,
      UIComponent uiComponent) throws IOException {

    storeFooterHeight(facesContext, uiComponent);
    UIData data = (UIData) uiComponent;

    HtmlRendererUtil.createHeaderAndBodyStyles(facesContext, data);

    final String sheetId = data.getClientId(facesContext);
    HtmlStyleMap sheetStyle = (HtmlStyleMap) data.getAttributes().get(ATTR_STYLE);

    TobagoResponseWriter writer = HtmlRendererUtil.getTobagoResponseWriter(facesContext);

    // Outher sheet div
    writer.startElement(HtmlConstants.DIV, null);
    writer.writeIdAttribute(sheetId + "_outer_div");
    writer.writeClassAttribute("tobago-sheet-outer-div");
    writer.writeStyleAttribute(sheetStyle);
    UICommand clickAction = null;
    UICommand dblClickAction = null;
    int columnSelectorIndex = -1;
    int i = 0;
    for (UIComponent child : (List<UIComponent>) data.getChildren()) {
      if (child instanceof UIColumnEvent) {
        UIColumnEvent columnEvent = (UIColumnEvent) child;
        if (columnEvent.isRendered()) {
          UIComponent selectionChild = (UIComponent) child.getChildren().get(0);
          if (selectionChild != null && selectionChild instanceof UICommand && selectionChild.isRendered()) {
            UICommand action = (UICommand) selectionChild;
            if ("click".equals(columnEvent.getEvent())) {
              clickAction = action;
            }
            if ("dblclick".equals(columnEvent.getEvent())) {
              dblClickAction = action;
            }
          }
        }
      } else if (child instanceof UIColumnSelector) {
        columnSelectorIndex = i;
      }
      i++;
    }

    renderSheet(facesContext, data, (clickAction != null || dblClickAction != null));

    writer.endElement(HtmlConstants.DIV);

    boolean ajaxEnabled = TobagoConfig.getInstance(facesContext).isAjaxEnabled();

    final String[] styles = new String[]{"style/tobago-sheet.css"};
    final String[] scripts = new String[]{"script/tobago-sheet.js"};
    Integer frequency = null;
    UIComponent facetReload = data.getFacet(FACET_RELOAD);
    if (facetReload != null && facetReload instanceof UIReload && facetReload.isRendered()) {
      UIReload update = (UIReload) facetReload;
      frequency = update.getFrequency();
    }
    final String[] cmds = {
        "new Tobago.Sheet(\"" + sheetId + "\", " + ajaxEnabled
            + ", undefined, undefined, \"" + data.getSelectable()
            + "\", " + columnSelectorIndex + ", "+ frequency
            + ",  " + (clickAction!=null?HtmlRendererUtil.getJavascriptString(clickAction.getId()):null)
            + ",  " + HtmlRendererUtil.getRenderedPartiallyJavascriptArray(facesContext, clickAction)
            + ",  " + (dblClickAction!=null?HtmlRendererUtil.getJavascriptString(dblClickAction.getId()):null)
            + ",  " + HtmlRendererUtil.getRenderedPartiallyJavascriptArray(facesContext, dblClickAction)
            + ");"
    };
    UIPage page = ComponentUtil.findPage(facesContext, data);

    page.getStyleFiles().add(styles[0]);
    page.getScriptFiles().add(scripts[0]);

    if (!ajaxEnabled) {
      page.getOnloadScripts().add(cmds[0]);
    } else {
      HtmlRendererUtil.writeStyleLoader(facesContext, styles);
      HtmlRendererUtil.writeScriptLoader(facesContext, scripts, cmds);
    }
  }

  protected void renderSheet(FacesContext facesContext, UIData data, boolean hasClickAction) throws IOException {
    TobagoResponseWriter writer = HtmlRendererUtil.getTobagoResponseWriter(facesContext);
    ResourceManager resourceManager = ResourceManagerFactory.getResourceManager(facesContext);
    UIViewRoot viewRoot = facesContext.getViewRoot();
    String contextPath = facesContext.getExternalContext().getRequestContextPath();
    String sheetId = data.getClientId(facesContext);

    String image1x1 = contextPath + resourceManager.getImage(viewRoot, "image/1x1.gif");
    String selectorDisabled = contextPath + resourceManager.getImage(viewRoot, "image/sheetUncheckedDisabled.gif");
    String unchecked = contextPath + resourceManager.getImage(viewRoot, "image/sheetUnchecked.gif");

    Map attributes = data.getAttributes();
    HtmlStyleMap sheetStyle = (HtmlStyleMap) attributes.get(ATTR_STYLE);
    //HtmlStyleMap headerStyle = (HtmlStyleMap) attributes.get(ATTR_STYLE_HEADER);
//    String sheetWidthString = LayoutUtil.getStyleAttributeValue(sheetStyle,
//        "width");
    Integer sheetHeight = HtmlRendererUtil.getStyleAttributeIntValue(sheetStyle, "height");
    if (sheetHeight == null) {
      // FIXME: nullpointer if height not defined
      LOG.error("no height in parent container, setting to 100");
      sheetHeight = 100;
    }
    //HtmlStyleMap bodyStyle = (HtmlStyleMap) attributes.get(ATTR_STYLE_BODY);
    int footerHeight = (Integer) attributes.get(ATTR_FOOTER_HEIGHT);

    String selectable = data.getSelectable();

    SheetState state = data.getSheetState(facesContext);
    List<Integer> columnWidths = data.getWidthList();

    String selectedRows = StringUtils.toString(getSelectedRows(data, state));
    List<UIColumn> renderedColumnList = data.getRenderedColumns();

    writer.startElement(HtmlConstants.INPUT, null);
    writer.writeIdAttribute(sheetId + WIDTHS_POSTFIX);
    writer.writeNameAttribute(sheetId + WIDTHS_POSTFIX);
    writer.writeAttribute(HtmlAttributes.TYPE, "hidden", false);
    writer.writeAttribute(HtmlAttributes.VALUE, StringUtils.toString(columnWidths), false);
    writer.endElement(HtmlConstants.INPUT);

    writer.startElement(HtmlConstants.INPUT, null);
    writer.writeIdAttribute(sheetId + SCROLL_POSTFIX);
    writer.writeNameAttribute(sheetId + SCROLL_POSTFIX);
    writer.writeAttribute(HtmlAttributes.TYPE, "hidden", false);
    Integer[] scrollPosition = data.getScrollPosition();
    if (scrollPosition != null) {
      String scroll = scrollPosition[0] + ";" + scrollPosition[1];
      writer.writeAttribute(HtmlAttributes.VALUE, scroll, false);
    } else {
      writer.writeAttribute(HtmlAttributes.VALUE, "", false);
    }
    writer.endElement(HtmlConstants.INPUT);

    if (!NONE.equals(selectable)) {
      writer.startElement(HtmlConstants.INPUT, null);
      writer.writeIdAttribute(sheetId + SELECTED_POSTFIX);
      writer.writeNameAttribute(sheetId + SELECTED_POSTFIX);
      writer.writeAttribute(HtmlAttributes.TYPE, "hidden", false);
      writer.writeAttribute(HtmlAttributes.VALUE, selectedRows, true);
      writer.endElement(HtmlConstants.INPUT);
    }


    final boolean showHeader = data.isShowHeader();
    final boolean ie6SelectOneFix = showHeader
        && ClientProperties.getInstance(facesContext).getUserAgent().isMsie6()
            && ComponentUtil.findDescendant(data, UISelectOne.class) != null;
    if (showHeader) {
      // begin rendering header
      writer.startElement(HtmlConstants.DIV, null);
      writer.writeIdAttribute(sheetId + "_header_div");
      writer.writeClassAttribute("tobago-sheet-header-div");

      HtmlStyleMap headerStyle = (HtmlStyleMap) attributes.get(ATTR_STYLE_HEADER);
      Integer zIndex = getZIndex(facesContext);
      if (headerStyle != null) {
        headerStyle.put("z-index", zIndex+1);
        if (ie6SelectOneFix) {
          headerStyle.put("position", "relative");
        }
        writer.writeStyleAttribute(headerStyle);
      }

      int columnCount = 0;
      final int sortMarkerWidth = getAscendingMarkerWidth(facesContext, data);
      String imageAscending = contextPath + resourceManager.getImage(viewRoot, "image/ascending.gif");
      String imageDescending = contextPath + resourceManager.getImage(viewRoot, "image/descending.gif");
      String img = resourceManager.getImage(viewRoot, "image/unsorted.gif", true);
      String imageUnsorted = image1x1;
      if (img != null) {
        imageUnsorted = contextPath + img;
      }
      for (UIColumn column : renderedColumnList) {
        renderColumnHeader(facesContext, writer, data, columnCount, column,
            imageAscending, imageDescending, imageUnsorted, image1x1, sortMarkerWidth);
        columnCount++;
      }
      writer.startElement(HtmlConstants.DIV, null);
      writer.writeIdAttribute(sheetId + "_header_box_filler");
      writer.writeClassAttribute("tobago-sheet-header-box tobago-sheet-header-filler");
      writer.writeAttribute(HtmlAttributes.STYLE, "width: 0px", false);

      writer.startElement(HtmlConstants.DIV, null);
      writer.writeClassAttribute("tobago-sheet-header");
      writer.flush();
      writer.write("&nbsp;");
      writer.endElement(HtmlConstants.DIV);

      writer.endElement(HtmlConstants.DIV);
      writer.endElement(HtmlConstants.DIV);
      // end rendering header
    }

// BEGIN RENDER BODY CONTENT
    HtmlStyleMap bodyStyle = (HtmlStyleMap) attributes.get(ATTR_STYLE_BODY);
    int bodyHeight = (sheetHeight - footerHeight);
    HtmlRendererUtil.replaceStyleAttribute(data, ATTR_STYLE_BODY, "height", bodyHeight);
    writer.startElement(HtmlConstants.DIV, null);
    writer.writeIdAttribute(sheetId + "_data_div");
    writer.writeClassAttribute("tobago-sheet-body-div ");
    if (ie6SelectOneFix) {
      int headerHeight = getConfiguredValue(facesContext, data, "headerHeight");
      bodyStyle.put("height", bodyHeight - headerHeight);
      bodyStyle.put("position", "relative");
    }
    if (!showHeader||ie6SelectOneFix) {
      bodyStyle.put("padding-top", "0");
    }
    writer.writeAttribute(HtmlAttributes.STYLE, bodyStyle.toString(), false);
    if (ie6SelectOneFix) {
      bodyStyle.remove("position");
    }
    if (!showHeader||ie6SelectOneFix) {
      bodyStyle.remove("padding-top");
    }
    bodyStyle.remove("height");
    Integer space = HtmlRendererUtil.getStyleAttributeIntValue(bodyStyle, "width");
    HtmlStyleMap sheetBodyStyle = (HtmlStyleMap) bodyStyle.clone();
    //String sheetBodyStyle;
    if (space != null) {
//      intSpace -= columnWidths.get(columnWidths.size() - 1);
      space -= getContentBorder(facesContext, data);
      if (needVerticalScrollbar(facesContext, data)) {
        space -= getScrollbarWidth(facesContext, data);
      }
      sheetBodyStyle.put("width", space);
    }



    writer.startElement(HtmlConstants.TABLE, null);
    writer.writeAttribute(HtmlAttributes.CELLSPACING, 0);
    writer.writeAttribute(HtmlAttributes.CELLPADDING, 0);
    writer.writeAttribute(HtmlAttributes.SUMMARY, "", false);
    writer.writeClassAttribute("tobago-sheet-body-table");
    writer.writeStyleAttribute(sheetBodyStyle);

    if (columnWidths != null) {
      writer.startElement(HtmlConstants.COLGROUP, null);
      for (Integer columnWidth : columnWidths) {
        writer.startElement(HtmlConstants.COL, null);
        writer.writeAttribute(HtmlAttributes.WIDTH, columnWidth);
        writer.endElement(HtmlConstants.COL);
      }
      writer.endElement(HtmlConstants.COLGROUP);
    }

    // Print the Content

    if (LOG.isDebugEnabled()) {
      LOG.debug("first = " + data.getFirst() + "   rows = " + data.getRows());
    }

    final String var = data.getVar();

    boolean odd = false;
    int visibleIndex = -1;
    final int last = data.getFirst() + data.getRows();
    for (int rowIndex = data.getFirst(); rowIndex < last; rowIndex++) {
      data.setRowIndex(rowIndex);
      if (!data.isRowAvailable()) {
        break;
      }
      visibleIndex++;
      odd = !odd;
      final String rowClass = odd ? "tobago-sheet-content-odd " : "tobago-sheet-content-even ";

      if (LOG.isDebugEnabled()) {
        LOG.debug("var       " + var);
        LOG.debug("list      " + data.getValue());
      }

      writer.startElement(HtmlConstants.TR, null);
      writer.writeClassAttribute(rowClass);
      writer.writeIdAttribute(sheetId + "_data_tr_" + rowIndex);
      writer.flush();


      int columnIndex = -1;
      for (UIColumn column : renderedColumnList) {
        columnIndex++;

        StyleClasses tdClass = new StyleClasses();
        tdClass.addClass("sheet", "cell-td"); // XXX not a standard compliant name
        tdClass.addMarkupClass(column, "column");
        if (columnIndex == 0) {
          tdClass.addClass("sheet", "cell-first-column"); // XXX not a standard compliant name
        }
        if (hasClickAction) {
          tdClass.addClass("sheet", "cell-clickable");
        }
        StyleClasses cellClass = StyleClasses.ensureStyleClasses(column);
        tdClass.addClasses(cellClass);

        writer.startElement(HtmlConstants.TD, column);

        writer.writeClassAttribute(tdClass.toString());
        final String align = (String) column.getAttributes().get(ATTR_ALIGN);
        if (align != null) {
          writer.writeStyleAttribute(HtmlRendererUtil.toStyleString("text-align", align));
        }
        writer.startElement(HtmlConstants.DIV, null);
        writer.writeIdAttribute(
            sheetId + "_data_row_" + visibleIndex + "_column" + columnIndex);
        writer.writeClassAttribute("tobago-sheet-cell-outer");
        writer.writeStyleAttribute(HtmlRendererUtil.toStyleString("width", columnWidths.get(columnIndex)));

        writer.startElement(HtmlConstants.DIV, null);
        writer.writeClassAttribute("tobago-sheet-cell-inner");
        writer.flush();

        if (column instanceof UIColumnSelector) {
          final boolean disabled = ComponentUtil.getBooleanAttribute(column, ATTR_DISABLED);
          writer.startElement(HtmlConstants.INPUT, null);
          writer.writeAttribute(HtmlAttributes.TYPE, "checkbox", false);
          writer.writeAttribute(HtmlAttributes.DISABLED, disabled);
          writer.writeIdAttribute(sheetId + "_data_row_selector_" + rowIndex);
          writer.writeClassAttribute("tobago-selectBooleanCheckbox-default");
          writer.endElement(HtmlConstants.INPUT);
        } else {
          List<UIComponent> childs = data.getRenderedChildrenOf(column);
          for (UIComponent grandkid : childs) {
            // set height to 0 to prevent use of layoutheight from parent
            grandkid.getAttributes().put(ATTR_LAYOUT_HEIGHT, HEIGHT_0);
            RenderUtil.encode(facesContext, grandkid);
          }
          if (childs.size() > 1) {
            if (LOG.isInfoEnabled()) {
              LOG.info(
                  "Column should not contain more than one child. Please surround the components with a tc:panel.");
            }
          }
        }

        writer.endElement(HtmlConstants.DIV);
        writer.endElement(HtmlConstants.DIV);
        writer.endElement(HtmlConstants.TD);
      }

      writer.startElement(HtmlConstants.TD, null);
      writer.writeClassAttribute("tobago-sheet-cell-td tobago-sheet-cell-filler");

      writer.startElement(HtmlConstants.DIV, null);
      writer.writeIdAttribute(
          sheetId + "_data_row_" + visibleIndex + "_column_filler");
      writer.writeClassAttribute("tobago-sheet-cell-outer");
      writer.writeStyleAttribute("width: 0px;");
      writer.flush();
      writer.write("&nbsp;");

      writer.endElement(HtmlConstants.DIV);
      writer.endElement(HtmlConstants.TD);

      writer.endElement(HtmlConstants.TR);
    }

    data.setRowIndex(-1);

    if (visibleIndex == -1 && showHeader) {
      writer.startElement(HtmlConstants.TR, null);
      writer.flush();
      int columnIndex = -1;
      for (UIColumn column : renderedColumnList) {
        columnIndex++;
        writer.startElement(HtmlConstants.TD, null);
        writer.startElement(HtmlConstants.DIV, null);
        writer.writeStyleAttribute(HtmlRendererUtil.toStyleString("width", columnWidths.get(columnIndex)));
        writer.endElement(HtmlConstants.DIV);
        writer.endElement(HtmlConstants.TD);
      }
      writer.startElement(HtmlConstants.TD, null);
      writer.startElement(HtmlConstants.DIV, null);
      writer.writeClassAttribute("tobago-sheet-cell-outer");
      writer.writeStyleAttribute("width: 0px;");
      writer.flush();
      writer.write("&nbsp;");
      writer.endElement(HtmlConstants.DIV);
      writer.endElement(HtmlConstants.TD);
      writer.endElement(HtmlConstants.TR);
    }

    writer.endElement(HtmlConstants.TABLE);
    writer.endElement(HtmlConstants.DIV);

// END RENDER BODY CONTENT


    renderFooter(facesContext, data, writer, sheetId, sheetHeight, footerHeight, bodyStyle);
    if (showHeader) {
      resetZIndex(facesContext);
    }
  }

  private Integer getZIndex(FacesContext facesContext) {
    Integer zIndex = (Integer) facesContext.getExternalContext().getRequestMap().get(TobagoConstants.ATTR_ZINDEX);
    if (zIndex == null) {
      zIndex = 1;
    } else {
      zIndex = zIndex + 4;
    }
    facesContext.getExternalContext().getRequestMap().put(TobagoConstants.ATTR_ZINDEX, zIndex);
    return zIndex;
  }

  private void resetZIndex(FacesContext facesContext) {
    Integer zIndex = (Integer) facesContext.getExternalContext().getRequestMap().get(TobagoConstants.ATTR_ZINDEX);
    if (zIndex != null) {
      if (zIndex == 1) {
        zIndex = null;
      } else {
        zIndex = zIndex - 4;
      }
      facesContext.getExternalContext().getRequestMap().put(TobagoConstants.ATTR_ZINDEX, zIndex);
    }
  }

  protected void renderFooter(FacesContext facesContext, UIData data, TobagoResponseWriter writer,
      String sheetId, Integer sheetHeight, int footerHeight, HtmlStyleMap bodyStyle) throws IOException {
    final String showRowRange
        = getPagingAttribute(data, ATTR_SHOW_ROW_RANGE);
    final String showPageRange
        = getPagingAttribute(data, ATTR_SHOW_PAGE_RANGE);
    final String showDirectLinks
        = getPagingAttribute(data, ATTR_SHOW_DIRECT_LINKS);

    if (isValidPagingValue(showRowRange)
        || isValidPagingValue(showPageRange)
        || isValidPagingValue(showDirectLinks)) {
      final Application application = facesContext.getApplication();
      final HtmlStyleMap footerStyle = (HtmlStyleMap) bodyStyle.clone();
      footerStyle.put("height", footerHeight);
      footerStyle.put("top", (sheetHeight - footerHeight));

        //  "height", MessageFormat.format("{0}px", footerHeight));
        //  + " top: " + (sheetHeight - footerHeight) + "px;";

      writer.startElement(HtmlConstants.DIV, data);
      writer.writeClassAttribute("tobago-sheet-footer");
      writer.writeStyleAttribute(footerStyle);


      if (isValidPagingValue(showRowRange)) {
        UICommand pagerCommand = (UICommand) data.getFacet(FACET_PAGER_ROW);
        if (pagerCommand == null) {
          pagerCommand = createPagingCommand(application, PageAction.TO_ROW, false);
          data.getFacets().put(FACET_PAGER_ROW, pagerCommand);
        }
        String pagingOnClick = new CommandRendererHelper(facesContext, pagerCommand).getOnclickDoubleQuoted();
        final String pagerCommandId = pagerCommand.getClientId(facesContext);

        final String className = "tobago-sheet-paging-rows-span"
            + " tobago-sheet-paging-span-" + showRowRange;

        writer.startElement(HtmlConstants.SPAN, null);
        writer.writeAttribute(HtmlAttributes.ONCLICK, "tobagoSheetEditPagingRow(this, '"
            + pagerCommandId + "', '" + pagingOnClick + "')", true);
        writer.writeClassAttribute(className);
        writer.writeAttribute(HtmlAttributes.TITLE, ResourceManagerUtil.getPropertyNotNull(
            facesContext, "tobago", "sheetPagingInfoRowPagingTip"), true);
        writer.writeText("");
        writer.write(createSheetPagingInfo(data, facesContext,
            pagerCommandId, true));
        writer.endElement(HtmlConstants.SPAN);
      }


      if (isValidPagingValue(showDirectLinks)) {
        final String className = "tobago-sheet-paging-links-span"
            + " tobago-sheet-paging-span-" + showDirectLinks;

        writer.startElement(HtmlConstants.SPAN, null);
        writer.writeClassAttribute(className);
        writer.writeIdAttribute(sheetId + SUBCOMPONENT_SEP + "pagingLinks");
        writeDirectPagingLinks(writer, facesContext, application, data);
        writer.endElement(HtmlConstants.SPAN);
      }

      if (isValidPagingValue(showPageRange)) {
        UICommand pagerCommand
            = (UICommand) data.getFacet(FACET_PAGER_PAGE);
        if (pagerCommand == null) {
          pagerCommand = createPagingCommand(
              application, PageAction.TO_PAGE, false);
          data.getFacets().put(FACET_PAGER_PAGE, pagerCommand);
        }
        String pagingOnClick = new CommandRendererHelper(facesContext, pagerCommand).getOnclickDoubleQuoted();
        final String pagerCommandId = pagerCommand.getClientId(facesContext);

        final String className = "tobago-sheet-paging-pages-span"
            + " tobago-sheet-paging-span-" + showPageRange;


        writer.startElement(HtmlConstants.SPAN, null);
        writer.writeClassAttribute(className);
        writer.writeIdAttribute(sheetId + SUBCOMPONENT_SEP + "pagingPages");
        writer.writeText("");

        boolean atBeginning = data.isAtBeginning();
        link(facesContext, application, atBeginning, PageAction.FIRST, data);
        link(facesContext, application, atBeginning, PageAction.PREV, data);
        writer.startElement(HtmlConstants.SPAN, null);
        writer.writeClassAttribute("tobago-sheet-paging-pages-text");
        writer.writeAttribute(HtmlAttributes.ONCLICK, "tobagoSheetEditPagingRow(this, '"
            + pagerCommandId + "', '" + pagingOnClick + "')", true);
        writer.writeAttribute(HtmlAttributes.TITLE, ResourceManagerUtil.getPropertyNotNull(
            facesContext, "tobago", "sheetPagingInfoPagePagingTip"), true);
        writer.writeText("");
        writer.write(createSheetPagingInfo(
            data, facesContext, pagerCommandId, false));
        writer.endElement(HtmlConstants.SPAN);
        boolean atEnd = data.isAtEnd();
        link(facesContext, application, atEnd, PageAction.NEXT, data);
        link(facesContext, application, atEnd||!data.hasRowCount(), PageAction.LAST, data);
        writer.endElement(HtmlConstants.SPAN);
      }

      writer.endElement(HtmlConstants.DIV);
    }
  }

  private String createSheetPagingInfo(UIData data,
      FacesContext facesContext, String pagerCommandId, boolean row) {
    String sheetPagingInfo;
    if (data.getRowCount() > 0) {
      Locale locale = facesContext.getViewRoot().getLocale();
      int first;
      int last;
      if (row) {
        first = data.getFirst() + 1;
        last = data.getLast();
      } else { // page
        first = data.getPage();
        last = data.getPages();
      }
      String key;
      if (first != last) {
        key = ResourceManagerUtil.getPropertyNotNull(facesContext, "tobago",
            "sheetPagingInfo" + (row ? "Rows" : "Pages"));
      } else {
        key = ResourceManagerUtil.getPropertyNotNull(facesContext, "tobago",
            "sheetPagingInfoSingle" + (row ? "Row" : "Page"));
      }
      MessageFormat detail = new MessageFormat(key, locale);
      Object[] args = {
          first,
          last,
          data.getRowCount(),
          pagerCommandId + SUBCOMPONENT_SEP + "text"
      };
      sheetPagingInfo = detail.format(args);
    } else {
      sheetPagingInfo =
          ResourceManagerUtil.getPropertyNotNull(facesContext, "tobago",
              "sheetPagingInfoEmpty" + (row ? "Row" : "Page"));
    }
    return sheetPagingInfo;
  }

  public void decode(FacesContext facesContext, UIComponent component) {
    super.decode(facesContext, component);

    String key = component.getClientId(facesContext) + WIDTHS_POSTFIX;

    Map requestParameterMap = facesContext.getExternalContext()
        .getRequestParameterMap();
    if (requestParameterMap.containsKey(key)) {
      String widths = (String) requestParameterMap.get(key);
      if (widths.trim().length() > 0) {
        component.getAttributes().put(ATTR_WIDTH_LIST_STRING,
            widths);
      }
    }

    key = component.getClientId(facesContext) + SELECTED_POSTFIX;
    if (requestParameterMap.containsKey(key)) {
      String selected = (String) requestParameterMap.get(key);
      if (LOG.isDebugEnabled()) {
        LOG.debug("selected = " + selected);
      }
      List<Integer> selectedRows;
      try {
        selectedRows = StringUtils.parseIntegerList(selected);
      } catch (NumberFormatException e) {
        LOG.warn(selected, e);
        selectedRows = Collections.emptyList();
      }

      component.getAttributes().put(
          ATTR_SELECTED_LIST_STRING, selectedRows);
    }

    key = component.getClientId(facesContext) + SCROLL_POSTFIX;
    String value = (String) requestParameterMap.get(key);
    if (value != null) {
      Integer[] scrollPosition = SheetState.parseScrollPosition(value);
      if (scrollPosition != null) {
        //noinspection unchecked
        component.getAttributes().put(ATTR_SCROLL_POSITION, scrollPosition);
      }
    }

  }

  public boolean needVerticalScrollbar(FacesContext facesContext, UIData data) {
    // estimate need of height-scrollbar on client, if yes we have to consider
    // this when calculating column width's

    final Object forceScrollbar
        = data.getAttributes().get(ATTR_FORCE_VERTICAL_SCROLLBAR);
    if (forceScrollbar != null) {
      if ("true".equals(forceScrollbar)) {
        return true;
      } else if ("false".equals(forceScrollbar)) {
        return false;
      } else if (!"auto".equals(forceScrollbar)) {
        LOG.warn("Illegal value for attribute 'forceVerticalScrollbar' : \"" + forceScrollbar + "\"");
      }
    }

    HtmlStyleMap style = (HtmlStyleMap) data.getAttributes().get(ATTR_STYLE);
    Integer height = HtmlRendererUtil.getStyleAttributeIntValue(style, "height");
    if (height != null) {
      int first = data.getFirst();
      int rows = Math.min(data.getRowCount(), first + data.getRows()) - first;
      int heightNeeded = getHeaderHeight(facesContext, data)
          + getFooterHeight(facesContext, data)
          + (rows * getConfiguredValue(facesContext, data, "rowHeight"))
          + getRowPadding(facesContext, data);

      return heightNeeded > height;
    } else {
      return false;
    }
  }

  private int getRowPadding(FacesContext facesContext, UIComponent component) {
    return getConfiguredValue(facesContext, component, "rowPadding");
  }

  public int getScrollbarWidth(FacesContext facesContext,
      UIComponent component) {
    return getConfiguredValue(facesContext, component, "scrollbarWidth");
  }

  protected void storeFooterHeight(FacesContext facesContext,
      UIComponent component) {
    component.getAttributes().put(ATTR_FOOTER_HEIGHT,
        getFooterHeight(facesContext, component));
  }

  protected int getFooterHeight(FacesContext facesContext, UIComponent component) {
    int footerHeight;
    if (isValidPagingAttribute((UIData) component, ATTR_SHOW_ROW_RANGE)
        || isValidPagingAttribute((UIData) component, ATTR_SHOW_PAGE_RANGE)
        || isValidPagingAttribute((UIData) component, ATTR_SHOW_DIRECT_LINKS)) {
      footerHeight =
          getConfiguredValue(facesContext, component, "footerHeight");
    } else {
      footerHeight = 0;
    }
    return footerHeight;
  }

  private boolean isValidPagingAttribute(UIData component, String name) {
    return isValidPagingValue(getPagingAttribute(component, name));
  }

  private String getPagingAttribute(UIData component, String name) {
    String value = ComponentUtil.getStringAttribute(component, name);
    if (isValidPagingValue(value)) {
      return value;
    } else {
      if (!"none".equals(value)) {
        LOG.warn(
            "illegal value in sheet' paging attribute : \"" + value + "\"");
      }
      return "none";
    }
  }

  private boolean isValidPagingValue(String value) {
    return "left".equals(value) || "center".equals(value)
        || "right".equals(value);
  }

  protected int getAscendingMarkerWidth(FacesContext facesContext,
      UIComponent component) {
    return getConfiguredValue(facesContext, component, "ascendingMarkerWidth");
  }

  public boolean getRendersChildren() {
    return true;
  }

  protected List<Integer> getSelectedRows(UIData data, SheetState state) {
    List<Integer> selected = (List<Integer>)
        data.getAttributes().get(ATTR_SELECTED_LIST_STRING);
    if (selected == null && state != null) {
      selected = state.getSelectedRows();
    }
    if (selected == null) {
      selected = Collections.emptyList();
    }
    return selected;
  }

  private void link(FacesContext facesContext, Application application,
                           boolean disabled, PageAction command, UIData data)
      throws IOException {
    UICommand link= createPagingCommand(application, command, disabled);

    data.getFacets().put(command.getToken(), link);


    String tip = ResourceManagerUtil.getPropertyNotNull(facesContext, "tobago",
        "sheet" + command.getToken());
    String image = ResourceManagerUtil.getImageWithPath(facesContext,
        "image/sheet" + command.getToken() + (disabled ? "Disabled" : "") + ".gif");

    TobagoResponseWriter writer = HtmlRendererUtil.getTobagoResponseWriter(facesContext);
    writer.startElement(HtmlConstants.IMG, null);
    writer.writeIdAttribute(data.getClientId(facesContext)
        + SUBCOMPONENT_SEP + "pagingPages" + SUBCOMPONENT_SEP + command.getToken());
    writer.writeClassAttribute("tobago-sheet-footer-pager-button"
        + (disabled ? " tobago-sheet-footer-pager-button-disabled" : ""));
    writer.writeAttribute(HtmlAttributes.SRC, image, false);
    writer.writeAttribute(HtmlAttributes.TITLE, tip, true);
    writer.writeAttribute(HtmlAttributes.ALT, "", false);
    if (!disabled) {
      CommandRendererHelper helper = new CommandRendererHelper(facesContext, link);
      String onClick = helper.getOnclick();
      writer.writeAttribute(HtmlAttributes.ONCLICK, onClick, true);
    }
    writer.endElement(HtmlConstants.IMG);
  }

  protected void renderColumnHeader(FacesContext facesContext,
      TobagoResponseWriter writer, UIData component,
      int columnIndex, UIColumn column, String imageAscending, String imageDescending, String imageUnsorted,
      String image1x1, int sortMarkerWidth) throws IOException {
    renderColumnHeader(
        facesContext, writer,  component, columnIndex, column, imageAscending, imageDescending,
        imageUnsorted, image1x1, sortMarkerWidth,
        ComponentUtil.getBooleanAttribute(column, TobagoConstants.ATTR_RESIZABLE));
  }

  protected void renderColumnHeader(FacesContext facesContext,
      TobagoResponseWriter writer, UIData component,
      int columnIndex, UIColumn column, String imageAscending, String imageDescending, String imageUnsorted,
      String image1x1, int sortMarkerWidth, boolean headerResizer) throws IOException {
    String sheetId = component.getClientId(facesContext);
    Application application = facesContext.getApplication();

    List columnWidths = (List) component.getWidthList();
    String divWidth = "width: " + columnWidths.get(columnIndex) + "px;";

    writer.startElement(HtmlConstants.DIV, null);
    writer.writeIdAttribute(sheetId + "_header_box_" + columnIndex);
    writer.writeClassAttribute("tobago-sheet-header-box");
    writer.writeAttribute(HtmlAttributes.STYLE, divWidth, false);
    String tip = (String) column.getAttributes().get(ATTR_TIP);
    if (tip == null) {
      tip = "";
    }

// ############################################
// ############################################

    String sorterImage = null;
    String sorterClass = "";
    String sortTitle = null;
    boolean sortable = ComponentUtil.getBooleanAttribute(column, ATTR_SORTABLE);
    if (sortable && !(column instanceof UIColumnSelector)) {
      UICommand sortCommand = (UICommand) column.getFacet(UIData.FACET_SORTER);
      if (sortCommand == null) {
        String columnId = column.getClientId(facesContext);
        String sorterId = columnId.substring(columnId.lastIndexOf(":") + 1) + "_" + UIData.SORTER_ID;
        sortCommand = (UICommand) application.createComponent(UICommand.COMPONENT_TYPE);
        sortCommand.setRendererType(RENDERER_TYPE_LINK);
        sortCommand.setId(sorterId);
        column.getFacets().put(UIData.FACET_SORTER, sortCommand);
      }

      String onclick = "Tobago.submitAction2(this, '" + sortCommand.getClientId(facesContext) + "', null, null)";
      writer.writeAttribute(HtmlAttributes.ONCLICK, onclick, false);

      if (org.apache.commons.lang.StringUtils.isNotEmpty(tip)) {
        tip +=  " - ";
      }
      tip += ResourceManagerUtil.getPropertyNotNull(facesContext, "tobago", "sheetTipSorting");

      SheetState sheetState = component.getSheetState(facesContext);
      if (column.getId().equals(sheetState.getSortedColumnId())) {
        if (sheetState.isAscending()) {
          sorterImage = imageAscending;
          sortTitle = ResourceManagerUtil.getPropertyNotNull(facesContext, "tobago", "sheetAscending");
        } else {
          sorterImage = imageDescending;
          sortTitle = ResourceManagerUtil.getPropertyNotNull(facesContext, "tobago", "sheetDescending");
        }
      }
      sorterClass = " tobago-sheet-header-sortable";
    }

// ############################################
// ############################################

    writer.writeAttribute(HtmlAttributes.TITLE, tip, true);

    String align = (String) column.getAttributes().get(ATTR_ALIGN);

    writer.startElement(HtmlConstants.DIV, null);
    writer.writeIdAttribute(sheetId + "_header_outer_" + columnIndex);
    //if (columnIndex == 0) {
    //  writer.writeClassAttribute("tobago-sheet-header"+ sorterClass + " tobago-sheet-header-first-column");
    //} else {
      writer.writeClassAttribute("tobago-sheet-header" + sorterClass);
    //}
    if (align != null) {
      writer.writeStyleAttribute("text-align: " + align + ";");
    }

    String resizerClass;
    if (column instanceof UIColumnSelector) {
      resizerClass = "tobago-sheet-header-resize";
      renderColumnSelectorHeader(facesContext, writer, component, column);
    } else {
      resizerClass = "tobago-sheet-header-resize tobago-sheet-header-resize-cursor";
      renderColumnHeaderLabel(facesContext, writer, column, sortMarkerWidth, align, image1x1);
    }
    writer.endElement(HtmlConstants.DIV);
    if (headerResizer) {
      writer.startElement(HtmlConstants.DIV, null);
      writer.writeIdAttribute(sheetId + "_header_resizer_" + columnIndex);
      writer.writeClassAttribute(resizerClass);
      writer.flush();
      writer.write("&nbsp;");
      writer.endElement(HtmlConstants.DIV);
    }
// ############################################
// ############################################
    if (sortable && !(column instanceof UIColumnSelector)) {
      if (sorterImage == null && imageUnsorted != null) {
        sorterImage = imageUnsorted;
      }
      writer.startElement(HtmlConstants.DIV, null);
      writer.writeClassAttribute("tobago-sheet-header-sort-div");
      if (sortTitle != null) {
        writer.writeAttribute(HtmlAttributes.TITLE, sortTitle, true);
      }
      if (sorterImage != null) {
        writer.startElement(HtmlConstants.IMG, null);
        writer.writeAttribute(HtmlAttributes.SRC, sorterImage, false);
        writer.writeAttribute(HtmlAttributes.ALT, "", false);
        if (sortTitle != null) {
          writer.writeAttribute(HtmlAttributes.TITLE, sortTitle, true);
        }
        writer.endElement(HtmlConstants.IMG);
      }
      writer.endElement(HtmlConstants.DIV);
    }
// ############################################
// ############################################

    writer.endElement(HtmlConstants.DIV);
  }


  protected void renderColumnSelectorHeader(FacesContext facesContext,
      TobagoResponseWriter writer, UIData component, UIColumn column)
      throws IOException {
    UIPanel menu = (UIPanel) column.getFacet(FACET_MENUPOPUP);
    if (menu == null) {
      final Application application = facesContext.getApplication();
      menu = (UIPanel) application.createComponent(UIMenu.COMPONENT_TYPE);
      menu.setId("selectorMenu");
      column.getFacets().put(FACET_MENUPOPUP, menu);
      menu.setRendererType(RENDERER_TYPE_MENUBAR);
      menu.getAttributes().put(ATTR_MENU_POPUP, Boolean.TRUE);
      menu.getAttributes().put(ATTR_MENU_POPUP_TYPE, "SheetSelector");
      menu.getAttributes().put(ATTR_IMAGE, "image/sheetSelectorMenu.gif");

      String sheetId = column.getParent().getClientId(facesContext);

      String action = "Tobago.Sheets.selectAll('" + sheetId + "')";
      String label = ResourceManagerUtil.getPropertyNotNull(facesContext, "tobago",
          "sheetMenuSelect");
      UICommand menuItem = createMenuItem(application, label, action);
      menuItem.setId("menuSelectAll");
      menu.getChildren().add(menuItem);

      action = "Tobago.Sheets.unSelectAll('" + sheetId + "')";
      label = ResourceManagerUtil.getPropertyNotNull(facesContext, "tobago",
          "sheetMenuUnselect");
      menuItem = createMenuItem(application, label, action);
      menuItem.setId("menuUnselectAll");
      menu.getChildren().add(menuItem);

      action = "Tobago.Sheets.toggleAllSelections('" + sheetId + "')";
      label = ResourceManagerUtil.getPropertyNotNull(facesContext, "tobago",
          "sheetMenuToggleselect");
      menuItem = createMenuItem(application, label, action);
      menuItem.setId("menuToggleSelections");
      menu.getChildren().add(menuItem);
    }

    menu.setRendered(UIData.MULTI.equals(component.getSelectable()));

    writer.startElement(HtmlConstants.DIV, null);
    writer.writeIdAttribute(column.getClientId(facesContext));
    writer.writeClassAttribute("tobago-sheet-selector-menu");
    writer.endElement(HtmlConstants.DIV);
    RenderUtil.encode(facesContext, menu);
  }

  private UICommand createMenuItem(final Application application, String label,
      String action) {
    UICommand menuItem
        = (UICommand) application.createComponent(UIMenuCommand.COMPONENT_TYPE);
    menuItem.setRendererType(RENDERER_TYPE_MENUCOMMAND);
    menuItem.getAttributes().put(ATTR_ACTION_ONCLICK, action);
    menuItem.getAttributes().put(ATTR_LABEL, label);
    return menuItem;
  }

  private void renderColumnHeaderLabel(FacesContext facesContext,
                                       TobagoResponseWriter writer, UIColumn column,
                                       int sortMarkerWidth, String align,
                                       String image1x1) throws IOException {
    String label
        = (String) column.getAttributes().get(ATTR_LABEL);
    if (label != null) {
      writer.writeText(label, null);
      if (ComponentUtil.getBooleanAttribute(column, ATTR_SORTABLE) && "right".equals(align)) {
        writer.startElement(HtmlConstants.IMG, null);
        writer.writeAttribute(HtmlAttributes.SRC, image1x1, false);
        writer.writeAttribute(HtmlAttributes.ALT, "", false);
        writer.writeAttribute(HtmlAttributes.WIDTH, sortMarkerWidth);
        writer.writeAttribute(HtmlAttributes.HEIGHT, 1);
        writer.endElement(HtmlConstants.IMG);
      }
    } else {
      writer.startElement(HtmlConstants.IMG, null);
      writer.writeAttribute(HtmlAttributes.SRC, image1x1, false);
      writer.writeAttribute(HtmlAttributes.ALT, "", false);
      writer.endElement(HtmlConstants.IMG);
    }

    if (column.getFacet(FACET_MENUPOPUP) != null) {
      renderFilter(facesContext, writer, column);
    }
  }

  private void renderFilter(FacesContext facesContext, TobagoResponseWriter writer, UIComponent column)
      throws IOException {

    UIComponent facet = column.getFacet(FACET_MENUPOPUP);

    if (facet instanceof UIMenu) {

      if (facet.getAttributes().get(ATTR_MENU_POPUP) != Boolean.TRUE) {
        facet.setRendererType(RENDERER_TYPE_MENUBAR);
        facet.getAttributes().put(ATTR_MENU_POPUP, Boolean.TRUE);
        facet.getAttributes().put(ATTR_MENU_POPUP_TYPE, "SheetSelector");
      }
      if (org.apache.commons.lang.StringUtils.isBlank((String) facet.getAttributes().get(ATTR_IMAGE))) {
        facet.getAttributes().put(ATTR_IMAGE, "image/sheetSelectorMenu.gif");
      }


      writer.startElement(HtmlConstants.SPAN, null);
      writer.writeIdAttribute(column.getClientId(facesContext));
      writer.writeClassAttribute("tobago-sheet-selector-menu");
      writer.endElement(HtmlConstants.SPAN);
      RenderUtil.encode(facesContext, facet);

    } else {
      LOG.warn("Unknown filter component: " + facet);
    }



  }

  private void writeDirectPagingLinks(
      TobagoResponseWriter writer, FacesContext facesContext, Application application, UIData data)
      throws IOException {
    UICommand pagerCommand = (UICommand) data.getFacet(FACET_PAGER_PAGE);
    if (pagerCommand == null) {
      pagerCommand = createPagingCommand(
          application, PageAction.TO_PAGE, false);
      data.getFacets().put(FACET_PAGER_PAGE, pagerCommand);
    }
    String pagerCommandId = pagerCommand.getClientId(facesContext);
    String onclick = new CommandRendererHelper(facesContext, pagerCommand).getOnclickDoubleQuoted();
    String hrefPostfix = "', '" + onclick + "');";

    int linkCount = ComponentUtil.getIntAttribute(data, ATTR_DIRECT_LINK_COUNT);
    linkCount--;  // current page needs no link
    ArrayList<Integer> prevs = new ArrayList<Integer>(linkCount);
    int page = data.getPage();
    for (int i = 0; i < linkCount && page > 1; i++) {
      page--;
      if (page > 0) {
        prevs.add(0, page);
      }
    }

    ArrayList<Integer> nexts = new ArrayList<Integer>(linkCount);
    page = data.getPage();
    for (int i = 0; i < linkCount && page < data.getPages(); i++) {
      page++;
      if (page > 1) {
        nexts.add(page);
      }
    }

    if (prevs.size() > (linkCount / 2)
        && nexts.size() > (linkCount - (linkCount / 2))) {
      while (prevs.size() > (linkCount / 2)) {
        prevs.remove(0);
      }
      while (nexts.size() > (linkCount - (linkCount / 2))) {
        nexts.remove(nexts.size() - 1);
      }
    } else if (prevs.size() <= (linkCount / 2)) {
      while (prevs.size() + nexts.size() > linkCount) {
        nexts.remove(nexts.size() - 1);
      }
    } else {
      while (prevs.size() + nexts.size() > linkCount) {
        prevs.remove(0);
      }
    }

    String name;
    int skip = prevs.size() > 0 ? ((Integer) prevs.get(0)) : 1;
    if (skip > 1) {
      skip -= (linkCount - (linkCount / 2));
      skip--;
      name = "...";
      if (skip < 1) {
        skip = 1;
        if (prevs.get(0) == 2) {
          name = "1";
        }
      }
      writeLinkElement(writer, name, Integer.toString(skip),
          pagerCommandId, hrefPostfix, true);
    }
    for (Integer prev : prevs) {
      name = prev.toString();
      writeLinkElement(writer, name, name, pagerCommandId, hrefPostfix, true);
    }
    name = Integer.toString(data.getPage());
    writeLinkElement(writer, name, name, pagerCommandId, hrefPostfix, false);

    for (Integer next : nexts) {
      name = next.toString();
      writeLinkElement(writer, name, name, pagerCommandId, hrefPostfix, true);
    }

    skip = nexts.size() > 0
        ? ((Integer) nexts.get(nexts.size() - 1)) : data.getPages();
    if (skip < data.getPages()) {
      skip += linkCount / 2;
      skip++;
      name = "...";
      if (skip > data.getPages()) {
        skip = data.getPages();
        if ((nexts.get(nexts.size() - 1)) == skip - 1) {
          name = Integer.toString(skip);
        }
      }
      writeLinkElement(writer, name, Integer.toString(skip), pagerCommandId,
          hrefPostfix, true);
    }
  }

  private UICommand createPagingCommand(Application application,
                                               PageAction command, boolean disabled) {
    UICommand link;
    link = (UICommand) application.createComponent(UICommand.COMPONENT_TYPE);
    link.setRendererType(SheetPageCommandRenderer.PAGE_RENDERER_TYPE);
    link.setRendered(true);
    link.setId(command.getToken());
//    link.getAttributes().put(ATTR_ACTION_STRING, command);
    link.getAttributes().put(ATTR_INLINE, Boolean.TRUE);
    link.getAttributes().put(ATTR_DISABLED, disabled);
    return link;
  }

  private void writeLinkElement(TobagoResponseWriter writer, String str, String skip,
      String id, String hrefPostfix, boolean makeLink)
      throws IOException {
    String type = makeLink ? HtmlConstants.A : HtmlConstants.SPAN;
    writer.startElement(type, null);
    writer.writeClassAttribute("tobago-sheet-paging-links-link");
    if (makeLink) {
      writer.writeIdAttribute(id + SUBCOMPONENT_SEP + "link_" + skip);
      writer.writeAttribute(HtmlAttributes.HREF, "javascript: tobagoSheetSetPagerPage('"
          + id + "', '" + skip + hrefPostfix, null);
    }
    writer.flush();
    writer.write(str);
    writer.endElement(type);
  }

  public int getContentBorder(FacesContext facesContext, UIData data) {
    return getConfiguredValue(facesContext, data, "contentBorder");
  }

  public void encodeAjax(FacesContext facesContext, UIComponent component)
      throws IOException {
    AjaxUtils.checkParamValidity(facesContext, component, UIData.class);
    boolean update = true;
    final String ajaxId = (String) facesContext.getExternalContext()
        .getRequestParameterMap().get(AjaxPhaseListener.AJAX_COMPONENT_ID);
    if (ajaxId.equals(component.getClientId(facesContext))) {
      if (component.getFacet(FACET_RELOAD) != null && component.getFacet(FACET_RELOAD) instanceof UIReload
          && component.getFacet(FACET_RELOAD).isRendered()
          && ajaxId.equals(ComponentUtil.findPage(facesContext, component).getActionId())
          && !((UIReload) component.getFacet(FACET_RELOAD)).isImmediate()) {
        UIReload reload = (UIReload) component.getFacet(FACET_RELOAD);
        update = reload.getUpdate();
      }
    }
    if (update) {
      // TODO find a better way
      UICommand clickAction = null;
      UICommand dblClickAction = null;
      for (UIComponent child : (List<UIComponent>) component.getChildren()) {
        if (child instanceof UIColumnEvent) {
          UIColumnEvent columnEvent = (UIColumnEvent) child;
          if (columnEvent.isRendered()) {
            UIComponent selectionChild = (UIComponent) child.getChildren().get(0);
            if (selectionChild != null && selectionChild instanceof UICommand && selectionChild.isRendered()) {
              UICommand action = (UICommand) selectionChild;
              if ("click".equals(columnEvent.getEvent())) {
                clickAction = action;
              }
              if ("dblclick".equals(columnEvent.getEvent())) {
                dblClickAction = action;
              }
            }
          }
        }
      }
      renderSheet(facesContext, (UIData) component, (clickAction != null || dblClickAction != null));
    } else {
      facesContext.getResponseWriter().write(AjaxResponseRenderer.CODE_NOT_MODIFIED);
    }
  }

  @Override
  public int getFixedHeight(FacesContext facesContext, UIComponent component) {

    // todo: why this will be called?
    if (component == null) {
      return 0;
    }

    UIData data = (UIData) component;

//    int headerHeight = getConfiguredValue(facesContext, component, "headerHeight");
//    int footerHeight = getConfiguredValue(facesContext, component, "footerHeight");
    int headerHeight = getHeaderHeight(facesContext, component);
    int footerHeight = getFooterHeight(facesContext, component);

    int rowHeight = getConfiguredValue(facesContext, component, "rowHeight");

    int rows = data.getRows();

    if (LOG.isInfoEnabled()) {
      LOG.info(headerHeight + " " + footerHeight + " " + rowHeight + " " + rows);
    }

    return headerHeight + rows * rowHeight + footerHeight + 2; // XXX hotfix: + 1
  }

  public void encodeChildren(FacesContext context,
                               UIComponent component)
            throws IOException {
    // DO Nothing
  }
}
