/*
 *  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.trinidadinternal.renderkit.core.xhtml;

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 javax.faces.application.Application;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.apache.myfaces.trinidad.bean.FacesBean;
import org.apache.myfaces.trinidad.bean.PropertyKey;
import org.apache.myfaces.trinidad.component.TableUtils;
import org.apache.myfaces.trinidad.component.UIXCommand;
import org.apache.myfaces.trinidad.component.UIXHierarchy;
import org.apache.myfaces.trinidad.component.UIXNavigationLevel;
import org.apache.myfaces.trinidad.component.core.nav.CoreNavigationPane;
import org.apache.myfaces.trinidad.context.Agent;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.render.RenderUtils;
import org.apache.myfaces.trinidadinternal.agent.TrinidadAgent;
import org.apache.myfaces.trinidad.context.Agent;
import org.apache.myfaces.trinidad.context.RenderingContext;

public class NavigationPaneRenderer extends XhtmlRenderer
{
  public NavigationPaneRenderer()
  {
    super(CoreNavigationPane.TYPE);
  }

  @Override
  protected void findTypeConstants(FacesBean.Type type)
  {
    super.findTypeConstants(type);
    _hintKey = type.findKey("hint");
    _titleKey = type.findKey("title");
    _disabledKey = type.findKey("disabled");
  }

  @Override
  public boolean getRendersChildren()
  {
    return true;
  }

  @Override
  protected void encodeAll(
    FacesContext        context,
    RenderingContext    arc,
    UIComponent         component,
    FacesBean           bean) throws IOException
  {
    // Since NavigationPane is a naming container, we can be more
    // efficient about skipping its children
    if (!PartialPageUtils.containsPprTargets(arc,
                                             component,
                                             getClientId(context, component)))
    {
      return;
    }

    ResponseWriter writer = context.getResponseWriter();

    writer.startElement("div", component);
    renderAllAttributes(context, arc, bean, false);
    _renderStyleAttributes(context, arc, bean);
    renderId(context, component);

    int renderedItemCount = _getItemCount((UIXHierarchy)component);
    int renderedRowCount = ((UIXHierarchy)component).getRowCount();

    // no kids, no NavigationLevel -- but you still get the span.
    if (renderedItemCount > 0 || renderedRowCount > 0)
    {
      renderContent(context, arc,
                    (UIXHierarchy)component, bean);
    }

    writer.endElement("div");
  }

  /**
   * Gets the stamp to use to render each link
   */
  private UIComponent _getStamp(
    UIXHierarchy        component
    )
  {
    UIComponent stamp = component.getFacet("nodeStamp");
    return stamp;
  }

  @SuppressWarnings("unchecked")
  protected void renderContent(
    FacesContext        context,
    RenderingContext arc,
    UIXHierarchy        component,
    FacesBean           bean
    ) throws IOException
  {
    NavItemData navItemData = new NavItemData();
    List<UIComponent> nonNavItemList = new ArrayList<UIComponent>();
    String renderingHint = _getHint(bean);

    UIComponent nodeStamp = _getStamp(component);
    if (nodeStamp == null)
    {
      // we aren't stamping, but rather have explicitly defined children:
      for(UIComponent child : (List<UIComponent>)component.getChildren())
      {
        if (child.isRendered())
        {
          UIXCommand navItem;
          if (child instanceof UIXCommand)
          {
            navItem = (UIXCommand) child;
            // collect the information needed to render this nav item:
            _collectNavItemData(navItemData, navItem, -1, component);
          }
          else if(renderingHint == NavigationPaneRenderer._HINT_BAR ||
                  renderingHint == NavigationPaneRenderer._HINT_BUTTONS)
          {
            navItemData.addItemData(null);
            nonNavItemList.add(child);
          }
          else
          {
            // we don't support a non-command child for other hints.
            _LOG.severe("ILLEGAL_COMPONENT_HIERARCHY_UIXCOMMAND_EXPECTED");
            return;
          }
        }
      }
    }
    else
    {
      if (!(nodeStamp instanceof UIXCommand))
      {
        _LOG.severe("ILLEGAL_COMPONENT_HIERARCHY_UIXCOMMAND_EXPECTED");
        return;

      }

      UIXCommand navStamp = (UIXCommand) nodeStamp;

      // we are stamping the children:
      // Save the current key
      Object oldPath = component.getRowKey();

      _setStartDepthPath(component,
                         ((UIXNavigationLevel) component).getLevel());

      int componentRowCount = component.getRowCount();
      if (componentRowCount != 0)
      {
        int startIndex = component.getFirst();
        int endIndex = TableUtils.getLast(component, startIndex);

        for (int i = startIndex; i <= endIndex; i++)
        {
          component.setRowIndex(i);

          if (navStamp.isRendered())
          {
            // collect the information needed to render this nav item:
            _collectNavItemData(navItemData, navStamp, i, component);
          }
        }
      }

      // Restore the old path
      component.setRowKey(oldPath);
    }

    // We must loop this second time because we support overlapping items,
    // arbitrary child wrappers; we need to know information like being the
    // first, the last, or whether nearby another active item.
    int visibleItemCount = navItemData.getItemCount();
    if (visibleItemCount != 0)
    {
      // starting outer markup:
      ResponseWriter rw = context.getResponseWriter();
      boolean isRtl = arc.getLocaleContext().isRightToLeft();
      boolean isChoiceHint =
        (NavigationPaneRenderer._HINT_CHOICE.equals(renderingHint));
      String choiceSelectId = null;
      if (isChoiceHint)
      {
        choiceSelectId = getClientId(context, component) + _CHOICE_SELECT_ID_SUFFIX;
        if (isRtl)
        {
          _renderChoiceButton(context, arc, rw, isRtl, choiceSelectId);
        }
        else
        {
          _renderChoiceLabel(context, arc, rw, isRtl, bean);
        }

        rw.startElement("select", null);
        rw.writeAttribute("id", choiceSelectId, null);
        
        // For Non-JavaScript browsers, render the name attribute thus it would
        // enable the browsers to include the name and value of this element
        // in its payLoad.
       
        if ( !supportsScripting(arc) )
        {
          rw.writeAttribute("name", choiceSelectId, null);
        }
        
        renderStyleClass(context, arc,
          SkinSelectors.AF_NAVIGATION_LEVEL_CHOICE_OPTIONS_STYLE_CLASS);
        if (getDisabled(bean))
          rw.writeAttribute("disabled", Boolean.TRUE, null);
      }

      int lastRowIndex = visibleItemCount - 1;
      boolean previousActive = false;
      int nextActiveIndex = navItemData.getEffectiveActiveIndex() - 1;
      Object oldPath = component.getRowKey();

      _setStartDepthPath(component,
                           ((UIXNavigationLevel)component).getLevel());

      Iterator<UIComponent> iter = nonNavItemList.iterator();
      for (int i=0; i<visibleItemCount; i++)
      {
        Map<String, Object> currentItemData = navItemData.getItemData(i);
        //if currentItemData is null,we have a non-command child.
        if (currentItemData != null)
        {
          currentItemData.put("isFirst", (i == 0));
          currentItemData.put("isLast", (i == lastRowIndex));
          currentItemData.put("previousActive", previousActive);
          currentItemData.put("nextActive", (i == nextActiveIndex));
          Integer rowIndex = (Integer) currentItemData.get("rowIndex");
          if (rowIndex != null)
            component.setRowIndex(rowIndex.intValue());
          _renderNavigationItem(context, arc, rw, currentItemData,
              renderingHint, isRtl);
          previousActive =
              getBooleanFromProperty(currentItemData.get("isActive"));
        }
        else
        {
          renderNonCommandChild(i, context, arc,iter.next(), (i == lastRowIndex), renderingHint);
        }
      }
      component.setRowKey(oldPath);

      // Close up any opened choice tags:
      if (isChoiceHint)
      {
        rw.endElement("select");
        if (isRtl)
        {
          _renderChoiceLabel(context, arc, rw, isRtl, bean);
        }
        {
          _renderChoiceButton(context, arc, rw, isRtl, choiceSelectId);
        }
      }
    }
  }

  protected boolean hasChildren(UIComponent component)
  {
    int childCount = component.getChildCount();
    return childCount > 0;
  }

  /**
   * renderStyleAttributes - use the NavigationLevel style class as the default
   * styleClass
   */
  private void _renderStyleAttributes(
    FacesContext        context,
    RenderingContext arc,
    FacesBean           bean) throws IOException
  {
    String renderingHint = _getHint(bean);
    if (NavigationPaneRenderer._HINT_TABS.equals(renderingHint))
    {
      renderStyleAttributes(context, arc, bean,
                            SkinSelectors.AF_NAVIGATION_LEVEL_TABS_STYLE_CLASS);
    }
    else if (NavigationPaneRenderer._HINT_BAR.equals(renderingHint))
    {
      renderStyleAttributes(context, arc, bean,
                            SkinSelectors.AF_NAVIGATION_LEVEL_BAR_STYLE_CLASS);
    }
    else
    {
      renderStyleAttributes(context, arc, bean,
                            SkinSelectors.AF_NAVIGATION_LEVEL_STYLE_CLASS);
    }
  }

  private String _getHint(FacesBean bean)
  {
    String renderingHint = toString(bean.getProperty(_hintKey));

    if (renderingHint == null)
    {
      // =-= mcc TODO pull from arc, e.g. when placed by Page, PanelPage
      renderingHint = _HINT_DEFAULT;
    }
    return renderingHint;
  }

  protected String getTitle(FacesBean bean)
  {
    return toString(bean.getProperty(_titleKey));
  }

  protected boolean getDisabled(FacesBean bean)
  {
    Object o = bean.getProperty(_disabledKey);
    if (o == null)
      o = _disabledKey.getDefault();
    return Boolean.TRUE.equals(o);
  }

  /**
   * Renders the client ID as both "id" and "name"
   */
  private void _renderCommandChildId(
    FacesContext context,
    UIXCommand   component
    ) throws IOException
  {
    String clientId = getClientId(context, component);
    // For links, these are actually URI attributes
    context.getResponseWriter().writeURIAttribute("id", clientId, "id");
    context.getResponseWriter().writeURIAttribute("name", clientId, "id");
  }

  private Object _getFocusRowKey(
    UIXHierarchy component
    )
  {
    return component.getFocusRowKey();
  }

  private int _getItemCount(
    UIXHierarchy component
    )
  {
    Object focusPath = _getFocusRowKey(component);
    int kids = getRenderedChildCount(component);

    if (focusPath == null)
      return kids;

    // =-= mcc TODO level is different than path, so the following is wrong:
    return kids + component.getDepth(focusPath) + 1;
  }

  private void _collectNavItemData(
    NavItemData navItemData,
    UIXCommand commandChild,
    int        rowIndex,
    UIXHierarchy component)
  {
    int itemDataIndex = navItemData.getItemCount();
    boolean isActive;

    // If we're stamping, "active" is based on the model's focus row key
    if (rowIndex >= 0)
    {
      isActive = _isOnFocusPath(component);
    }
    // But if we're not stamping, "active" is just based on the
    // "selected" property of the command component
    else
    {
      isActive = getBooleanFromProperty(
                   _getCommandChildProperty(commandChild,"selected"));
    }

    if (isActive)
    {
      if (navItemData.getEffectiveActiveIndex() == -1)
      {
        navItemData.setEffectiveActiveIndex(itemDataIndex);
      }
      else
      {
        isActive = false; // there can only be 1 active item
      }
    }
    HashMap<String, Object> itemDataMap = new HashMap<String, Object>();
    itemDataMap.put("accessKey", _getCommandChildProperty(commandChild, "accessKey"));
    itemDataMap.put("component", commandChild);
    itemDataMap.put("dataIndex", itemDataIndex);
    itemDataMap.put("destination", _getCommandChildProperty(commandChild, "destination"));
    itemDataMap.put("icon", _getCommandChildProperty(commandChild, "icon"));
    itemDataMap.put("immediate", _getCommandChildProperty(commandChild, "immediate"));
    itemDataMap.put("inlineStyle", _getCommandChildProperty(commandChild, "inlineStyle"));
    itemDataMap.put("isActive", isActive);
    itemDataMap.put("isDisabled", _getCommandChildProperty(commandChild, "disabled"));
    itemDataMap.put("partialSubmit", _getCommandChildProperty(commandChild, "partialSubmit"));
    itemDataMap.put("shortDesc", _getCommandChildProperty(commandChild, "shortDesc"));
    itemDataMap.put("styleClass", _getCommandChildProperty(commandChild, "styleClass"));
    itemDataMap.put("targetFrame", _getCommandChildProperty(commandChild, "targetFrame"));
    itemDataMap.put("text", _getCommandChildProperty(commandChild, "text"));
    // Store the row index for this iteration so it can be re-set
    // when rendering
    if (rowIndex >= 0)
      itemDataMap.put("rowIndex", rowIndex);

    navItemData.addItemData(itemDataMap);
  }

  protected boolean getBooleanFromProperty(Object value)
  {
    if (value == null)
    {
      return false;
    }

    return ("true".equals(value.toString()));
  }


  private Object _getCommandChildProperty(
    UIXCommand commandChild,
    String propertyName)
  {
    FacesBean childFacesBean = commandChild.getFacesBean();
    FacesBean.Type type = childFacesBean.getType();
    PropertyKey propertyKey = type.findKey(propertyName);
    if (propertyKey == null)
    {
      if (_LOG.isSevere())
      {
        _LOG.severe("NAVIGATIONLEVELRENDERER_NOT_FOUND_CHILD_PROPERTY", propertyName);
      }
      return null;
    }
    else
    {
      return childFacesBean.getProperty(propertyKey);
    }
  }

  private void _renderNavigationItem(
    FacesContext context,
    RenderingContext arc,
    ResponseWriter rw,
    Map<String, Object> itemData,
    String renderingHint,
    boolean isRtl
    ) throws IOException
  {
    if (_HINT_BAR.equals(renderingHint))
    {
      renderNonOverlappingItem(context, arc, rw, itemData, isRtl, true, false);
    }
    else if (_HINT_BUTTONS.equals(renderingHint))
    {
      renderNonOverlappingItem(context, arc, rw, itemData, isRtl, false, false);
    }
    else if (_HINT_CHOICE.equals(renderingHint))
    {
      _renderChoiceItem(context, arc, rw, itemData);
    }
    else if (_HINT_LIST.equals(renderingHint))
    {
      renderNonOverlappingItem(context, arc, rw, itemData, isRtl, false, true);
    }
    else // _HINT_TABS
    {
      renderTabItem(context, arc, rw, itemData, isRtl);
    }
  }

  protected void writeInlineStyles(
    ResponseWriter rw,
    Object userInlineStyle,
    String appendedInlineStyle
    ) throws IOException
  {
    if (userInlineStyle == null)
    {
      rw.writeAttribute("style", appendedInlineStyle, null);
    }
    else
    {
      String userInlineStyleString = toString(userInlineStyle).trim();
      boolean needSemicolon = false;
      int lastIndex = userInlineStyleString.length()-1;
      if ( (lastIndex > 0) && (userInlineStyleString.charAt(lastIndex) != ';') )
      {
        needSemicolon = true;
      }
      StringBuilder itemInlineStyle = new StringBuilder();
      itemInlineStyle.append(userInlineStyleString);
      if (needSemicolon)
      {
        itemInlineStyle.append(";");
      }
      if (appendedInlineStyle != null)
      {
        itemInlineStyle.append(appendedInlineStyle);
      }
      rw.writeAttribute("style", itemInlineStyle.toString(), null);
    }
  }

  private void _writeInlineTbodyStyles(
    RenderingContext arc,
    ResponseWriter rw
    ) throws IOException
  {
    // IE and Firefox require the TABLE to be "inline".
    if (arc.getAgent().getAgentName() == Agent.AGENT_GECKO)
    {
      // Firefox 1.5 also requires the TBODY to be "inline":
      rw.writeAttribute("style", "display: inline;", null);
    }
  }

  protected void appendIconAndText(
    FacesContext context,
    RenderingContext arc,
    ResponseWriter rw,
    String iconUri,
    Map<String, Object> itemData,
    boolean isDisabled,
    boolean isRtl
    ) throws IOException
  {
    if ( (iconUri != null) && !isRtl )
    {
      _appendIcon(context, rw, iconUri, isRtl, arc);
    }
     _writeItemLink(context, arc, rw, itemData, isDisabled);
    if ( (iconUri != null) && isRtl )
    {
      _appendIcon(context, rw, iconUri, isRtl, arc);
    }
  }

  private void _appendIcon(
    FacesContext context,
    ResponseWriter rw,
    String iconUri,
    boolean isRtl,
    RenderingContext arc
    ) throws IOException
  {
    String styleAppender = "";
    rw.startElement("img", null);
    rw.writeAttribute("border", "0", null);
    rw.writeAttribute("align", "absmiddle", null);

    if (isPDA(arc))
    {
      if (isRtl)
      {
        rw.writeAttribute("style", "padding-left: 5px;", null);
      }
      else
      {
        rw.writeAttribute("style", "padding-right: 5px;", null);
      }
    }
    else
    {
      if (isRtl)
      {
        rw.writeAttribute("style", "padding-left: 5px; float: right;", null);
      }
      else
      {
        rw.writeAttribute("style", "padding-right: 5px; float: left;", null);
      }
    }

    Application application = context.getApplication();
    ViewHandler handler = application.getViewHandler();
    String resolvedIconUri = handler.getResourceURL(context, iconUri);
    renderEncodedResourceURI(context, "src", resolvedIconUri);
    rw.endElement("img");
  }

  @SuppressWarnings("unchecked")
  private void _renderCommandChildren(
    FacesContext context,
    UIXCommand uiComp) throws IOException
  {
    for(UIComponent child : (List<UIComponent>)uiComp.getChildren())
    {
      RenderUtils.encodeRecursive(context, child);
    }
  }

  private void _renderText(
    ResponseWriter rw,
    Map<String, Object> itemData) throws IOException
  {
    String text = toString(itemData.get("text"));
    if(text != null)
    {
      rw.write(text);
    }
  }

  private void _writeItemLink(
    FacesContext context,
    RenderingContext arc,
    ResponseWriter rw,
    Map<String, Object> itemData,
    boolean isDisabled
    ) throws IOException
  {

    UIXCommand commandChild = (UIXCommand)itemData.get("component");
    if (isDisabled)
    {
      _renderText(rw, itemData);
      _renderCommandChildren(context, commandChild);
      return;
    }

    String destination = toResourceUri(context, (itemData.get("destination")));
    boolean immediate = false;
    boolean partialSubmit = false;
    if (destination == null)
    {
      immediate = getBooleanFromProperty(itemData.get("immediate"));
      partialSubmit = getBooleanFromProperty(itemData.get("partialSubmit"));
      if (partialSubmit)
      {
        AutoSubmitUtils.writeDependencies(context, arc);
      }
      String clientId = commandChild.getClientId(context);
      // Make sure we don't have anything to save
      assert(arc.getCurrentClientId() == null);
      arc.setCurrentClientId(clientId);

      // Find the params up front, and save them off -
      // getOnClick() doesn't have access to the UIComponent
      String extraParams = AutoSubmitUtils.getParameters(commandChild);
      arc.getProperties().put(_EXTRA_SUBMIT_PARAMS_KEY, extraParams);
    }

    boolean isActive = getBooleanFromProperty(itemData.get("isActive"));
    boolean isDesktop = (arc.getAgent().getType().equals(Agent.TYPE_DESKTOP));
    
    boolean nonJavaScriptSubmit = (!supportsScripting(arc))
                                              && (destination == null);    
    
    // For non-javascript browsers, we need to render a submit element
    // instead of an anchor tag if the anchor tag doesn't have a destination
    
    if (nonJavaScriptSubmit)
    {
    
      rw.startElement("input", commandChild);
    
      rw.writeAttribute("type", "submit", null);
      rw.writeAttribute("value", toString(itemData.get("text")), "text");
    
      String clientId = getClientId(context, commandChild);
      rw.writeAttribute("id", clientId, "id");
      
      
      
      // For Non-JavaScript browsers, encode the name attribute with the 
      // parameter name and value thus it would enable the browsers to 
      // include the name of this element in its payLoad if it submits the
      // page.
       
      rw.writeAttribute("name", XhtmlUtils.getEncodedParameter
                                  (XhtmlConstants.SOURCE_PARAM)
                                   + clientId, null);
    
      String linkConverter = 
             "border:none;background:inherit;text-decoration:underline;";
    
      // Few mobile browsers couldn't apply css property for active elements
      // so making it inline
      if (isActive && !isDesktop)
      {
        linkConverter = linkConverter + "font-weight: bold;";
      }
    
      writeInlineStyles(rw, null,linkConverter);
    
    }
    else
    {
      rw.startElement("a", commandChild); // linkElement
  
      // Few mobile browsers couldn't apply css property for active elements
      // so making it inline
      if (isActive && !isDesktop)
      {
        writeInlineStyles(rw, null,"font-weight: bold;");
      }
      _renderCommandChildId(context, commandChild);
  
      if (destination == null)
      {
        rw.writeURIAttribute("href", "#", null); // required for IE to support ":hover" styles
      }
      else
      {
        renderEncodedActionURI(context, "href", destination);
        String targetFrame = toString(itemData.get("targetFrame"));
        if ( (targetFrame != null) && !Boolean.FALSE.equals(
          arc.getAgent().getCapabilities().get(TrinidadAgent.CAP_TARGET)) )
        {
          rw.writeAttribute("target", targetFrame, null);
        }
      }
  
      if (supportsScripting(arc))
      {
        // Cannot use super.renderEventHandlers(context, bean); because the wrong
        // property keys would be in use so must do it this way:
        _writeOnclickProperty(
          arc,
          rw,
          commandChild,
          (destination == null),
          immediate,
          partialSubmit); // special for actions!
        _writeCommandChildProperty(rw, commandChild, "ondblclick");
        _writeCommandChildProperty(rw, commandChild, "onkeydown");
        _writeCommandChildProperty(rw, commandChild, "onkeyup");
        _writeCommandChildProperty(rw, commandChild, "onkeypress");
        _writeCommandChildProperty(rw, commandChild, "onmousedown");
        _writeCommandChildProperty(rw, commandChild, "onmousemove");
        _writeCommandChildProperty(rw, commandChild, "onmouseout");
        _writeCommandChildProperty(rw, commandChild, "onmouseover");
        _writeCommandChildProperty(rw, commandChild, "onmouseup");
      }
    }
    
    String accessKey = toString(itemData.get("accessKey"));
    if ( !isDisabled && (accessKey != null) )
    {
      rw.writeAttribute("accessKey", accessKey, null);
    }
    
    if (nonJavaScriptSubmit)
    { 
      _renderCommandChildren(context, commandChild);
      rw.endElement("input"); 
    }
    else
    {
      _renderText(rw, itemData);
      _renderCommandChildren(context, commandChild);
      rw.endElement("a"); // linkElement
    }
    
    if (destination == null)
    {
      arc.setCurrentClientId(null);
      arc.getProperties().remove(_EXTRA_SUBMIT_PARAMS_KEY);
    }
  }

  private void _writeCommandChildProperty(
    ResponseWriter rw,
    UIXCommand commandChild,
    String propertyName
    ) throws IOException
  {
    rw.writeAttribute(
      propertyName,
      _getCommandChildProperty(commandChild, propertyName),
      propertyName);
  }

  private void _writeOnclickProperty(
    RenderingContext arc,
    ResponseWriter rw,
    UIXCommand commandChild,
    boolean actionSpecialCase,
    boolean immediate,
    boolean partialSubmit
    ) throws IOException
  {
    if (actionSpecialCase)
    {
      String onclick = (String)_getCommandChildProperty(commandChild, "onclick");
      String script = _getAutoSubmitScript(arc, immediate, partialSubmit);
      rw.writeAttribute(
        "onclick", XhtmlUtils.getChainedJS(onclick, script, true), "onclick");
    }
    else // simple case, e.g. (destination != null)
    {
      _writeCommandChildProperty(rw, commandChild, "onclick");
    }
  }

  private String _getAutoSubmitScript(
    RenderingContext arc,
    boolean immediate,
    boolean partialSubmit
    )
  {
    String id = arc.getCurrentClientId();

    String extraParams = (String)
      arc.getProperties().get(_EXTRA_SUBMIT_PARAMS_KEY);

    String script;
    if (partialSubmit)
    {
      script = AutoSubmitUtils.getSubmitScript(
        arc, id, immediate, false,
        null/* no event*/,
        extraParams,
        false);
    }
    else
    {
      script = AutoSubmitUtils.getFullPageSubmitScript(
        arc, id, immediate,
        null/*no event*/,
        extraParams,
        false/* return false*/);
    }
    return script;
  }

  protected void renderNonOverlappingItem(
    FacesContext context,
    RenderingContext arc,
    ResponseWriter rw,
    Map<String, Object> itemData,
    boolean isRtl,
    boolean isBar,
    boolean isList
    ) throws IOException
  {
    rw.startElement("table", null);
    OutputUtils.renderLayoutTableAttributes(context, arc, "0", null);
    String appendedStyle = null;
    if (!isList)
    {
      appendedStyle = "display: inline;"; // style to make the table inline
      
      // In Safari and webkit browsers display:inline doesn't work as expected, and 
      // display:inline-block need to be used to make the table inline.
      if (arc.getAgent().getAgentName() == Agent.AGENT_WEBKIT)
      {
        appendedStyle = "display: inline-block;";
      }
    }
    writeInlineStyles(rw, toString(itemData.get("inlineStyle")),
      appendedStyle); // user's style + what we must have on top of it
    rw.writeAttribute("title", itemData.get("shortDesc"), null);
    StringBuilder itemStyleClass = new StringBuilder();
    String userStyleClass = toString(itemData.get("styleClass"));
    if (userStyleClass != null)
    {
      itemStyleClass.append(userStyleClass);
      itemStyleClass.append(" "); // more style classes are appended below
    }

    // Assign the event handlers:
    boolean isDisabled = getBooleanFromProperty(itemData.get("isDisabled"));
    boolean isActive = getBooleanFromProperty(itemData.get("isActive"));
    if (isActive)
    {
      if (isDisabled)
      {
        if (isList)
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_LIST_ACTIVE_DISABLED_STYLE_CLASS);
        }
        else if (isBar)
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_BAR_ACTIVE_DISABLED_STYLE_CLASS);
        }
        else
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_BUTTONS_ACTIVE_DISABLED_STYLE_CLASS);
        }
      }
      else
      {
        if (isList)
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_LIST_ACTIVE_ENABLED_STYLE_CLASS);
        }
        else if (isBar)
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_BAR_ACTIVE_ENABLED_STYLE_CLASS);
        }
        else
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_BUTTONS_ACTIVE_ENABLED_STYLE_CLASS);
        }
      }
    }
    else
    {
      if (isDisabled)
      {
        if (isList)
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_LIST_INACTIVE_DISABLED_STYLE_CLASS);
        }
        else if (isBar)
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_BAR_INACTIVE_DISABLED_STYLE_CLASS);
        }
        else
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_BUTTONS_INACTIVE_DISABLED_STYLE_CLASS);
        }
      }
      else
      {
        if (isList)
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_LIST_INACTIVE_ENABLED_STYLE_CLASS);
        }
        else if (isBar)
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_BAR_INACTIVE_ENABLED_STYLE_CLASS);
        }
        else
        {
          itemStyleClass.append(
            SkinSelectors.AF_NAVIGATION_LEVEL_BUTTONS_INACTIVE_ENABLED_STYLE_CLASS);
        }
      }
    }
    renderStyleClass(context, arc, itemStyleClass.toString());
    rw.startElement("tbody", null);
    if (!isList)
    {
      _writeInlineTbodyStyles(arc, rw);
    }
    rw.startElement("tr", null);

    if (isList)
    {
      rw.startElement("td", null); // bulletCell
      renderStyleClass(
        context,
        arc,
        SkinSelectors.AF_NAVIGATION_LEVEL_LIST_BULLET_STYLE_CLASS);
      rw.startElement("div", null); // bulletContent
      rw.write(" ");
      rw.endElement("div"); // bulletContent
      rw.endElement("td"); // bulletCell
    }

    rw.startElement("td", null); // centerCell
    rw.startElement("div", null); // centerContent
    if (isList)
    {
      renderStyleClass(context, arc,
        SkinSelectors.AF_NAVIGATION_LEVEL_LIST_CONTENT_STYLE_CLASS);
    }
    else if (isBar)
    {
      renderStyleClass(context, arc,
        SkinSelectors.AF_NAVIGATION_LEVEL_BAR_CONTENT_STYLE_CLASS);
    }
    else
    {
      renderStyleClass(context, arc,
        SkinSelectors.AF_NAVIGATION_LEVEL_BUTTONS_CONTENT_STYLE_CLASS);
    }
    appendIconAndText(
      context,
      arc,
      rw,
      toString(itemData.get("icon")),
      itemData,
      isDisabled,
      isRtl);
    rw.endElement("div"); // centerContent
    rw.endElement("td"); // centerCell

    if ( !isList && !getBooleanFromProperty(itemData.get("isLast")) )
    {
      rw.startElement("td", null); // rightCell
      rw.startElement("div", null); // rightContent
      if (isBar)
      {
        renderStyleClass(context, arc,
          SkinSelectors.AF_NAVIGATION_LEVEL_BAR_SEPARATOR_STYLE_CLASS);
      }
      else
      {
        renderStyleClass(context, arc,
          SkinSelectors.AF_NAVIGATION_LEVEL_BUTTONS_SEPARATOR_STYLE_CLASS);
      }
      rw.write("|");
      rw.endElement("div"); // rightContent
      rw.endElement("td"); // rightCell
    }

    rw.endElement("tr");
    rw.endElement("tbody");
    rw.endElement("table");
  }

  /**
   * encodes non command children of navigationPane.
   * This is used only for hint="bar" and hint="buttons"
   * @param index
   * @param context
   * @param arc
   * @param child
   * @param isLastItem
   * @param hint
   * @throws IOException
   */
  protected void renderNonCommandChild(
    int index,
    FacesContext context,
    RenderingContext arc,
    UIComponent child,
    boolean isLastItem,
    String hint) throws IOException
  {
    ResponseWriter rw = context.getResponseWriter();
    rw.startElement("table", null);
    OutputUtils.renderLayoutTableAttributes(context, arc, "0", null);
    String appendedStyle = null;
    appendedStyle = "display: inline;"; // style to make the table inline    
      
    // In Safari and webkit browsers display:inline doesn't work as expected, and 
    // display:inline-block need to be used to make the table inline.
    if (arc.getAgent().getAgentName() == Agent.AGENT_WEBKIT)
    {
      appendedStyle = "display: inline-block;";
    }
    writeInlineStyles(rw, null, appendedStyle);
    rw.startElement("tbody", null);
    _writeInlineTbodyStyles(arc, rw);
    rw.startElement("tr", null);
    rw.startElement("td", null);
    rw.startElement("div", null);
    encodeChild(context, child);
    rw.endElement("div");
    rw.endElement("td");
    if(!isLastItem)
    {
      rw.startElement("td", null); // rightCell
      rw.startElement("div", null); // rightContent
      if (hint == _HINT_BAR)
      {
        renderStyleClass(context, arc,
          SkinSelectors.AF_NAVIGATION_LEVEL_BAR_SEPARATOR_STYLE_CLASS);
      }
      else
      {
        renderStyleClass(context, arc,
          SkinSelectors.AF_NAVIGATION_LEVEL_BUTTONS_SEPARATOR_STYLE_CLASS);
      }

      rw.write("|");
      rw.endElement("div"); // rightContent
      rw.endElement("td"); // rightCell
    }
    rw.endElement("tr");
    rw.endElement("tbody");
    rw.endElement("table");
  }

  private void _renderChoiceItem(
    FacesContext context,
    RenderingContext arc,
    ResponseWriter rw,
    Map<String, Object> itemData
    ) throws IOException
  {
    // Choice items do not support icons at this time.
    boolean isDisabled = getBooleanFromProperty(itemData.get("isDisabled"));
    // If the agent, doesn't support disabled options, don't render anything
    // for such options
    if ( !isDisabled ||
         Boolean.TRUE.equals(arc.getAgent().getCapabilities().get(
                TrinidadAgent.CAP_SUPPORTS_DISABLED_OPTIONS)))
    {
      boolean isActive = getBooleanFromProperty(itemData.get("isActive"));
      UIXCommand commandChild = null;
      String destination = null;
      boolean partialSubmit = false;
      if (!isDisabled)
      {  
         // Write the script to evaluate once the item is selected
         commandChild = (UIXCommand)itemData.get("component");
         destination = toString(itemData.get("destination"));
         if (destination == null)
         { 
           partialSubmit = getBooleanFromProperty(itemData.get("partialSubmit"));
           if (partialSubmit)
           {
             AutoSubmitUtils.writeDependencies(context, arc);
           }
         }
      }
      rw.startElement("option", null);
      if (isActive)
      {
        rw.writeAttribute("selected", Boolean.TRUE, null);
      }
      rw.writeAttribute("disabled", isDisabled, null);
      rw.writeAttribute("title", itemData.get("shortDesc"), null);

      if (!isDisabled)
      {
        boolean immediate = false;
        if (destination == null)
        {
          immediate = getBooleanFromProperty(itemData.get("immediate"));
          String clientId = commandChild.getClientId(context);
          // Make sure we don't have anything to save
          assert(arc.getCurrentClientId() == null);
          arc.setCurrentClientId(clientId);

          // Find the params up front, and save them off -
          // getOnClick() doesn't have access to the UIComponent
          String extraParams = AutoSubmitUtils.getParameters(commandChild);
          arc.getProperties().put(_EXTRA_SUBMIT_PARAMS_KEY, extraParams);
        }
        _renderCommandChildId(context, commandChild);
        String selectionScript;
        
        // For Non-javaScript browsers, set the value attribute to the id of 
        // the element instead of a javascript code
        
        if (!supportsScripting(arc))
        {
         selectionScript = arc.getCurrentClientId();
        }
        else if (destination == null)
        {
          selectionScript = _getAutoSubmitScript(arc, immediate, partialSubmit);

          // Trim off the "return false;" because we will be performing an
          // "eval" on the script and a "return" will yield a runtime error:
          selectionScript =
            selectionScript.substring(0, selectionScript.length() - 13);
        }
        else
        {
          String encodedDestination =
            context.getExternalContext().encodeActionURL(destination);
          String targetFrame = toString(itemData.get("targetFrame"));
          StringBuilder sb = new StringBuilder();
          if ( (targetFrame != null) && !Boolean.FALSE.equals(
            arc.getAgent().getCapabilities().get(TrinidadAgent.CAP_TARGET)) )
          {
            sb.append("window.open('");
            sb.append(encodedDestination);
            sb.append("','");
            sb.append(targetFrame);
            sb.append("');");
          }
          else
          {
            sb.append("self.location='");
            sb.append(encodedDestination);
            sb.append("';");
          }
          selectionScript = sb.toString();
        }
        rw.writeAttribute("value", selectionScript, null);

        if (destination == null)
        {
          arc.setCurrentClientId(null);
          arc.getProperties().remove(_EXTRA_SUBMIT_PARAMS_KEY);
        }
      }

      _renderText(rw, itemData);
      rw.endElement("option");
    }
  }

  private void _renderChoiceLabel(
    FacesContext context,
    RenderingContext arc,
    ResponseWriter rw,
    boolean isRtl,
    FacesBean bean
    ) throws IOException
  {
    String chooseText = getTitle(bean);
    if ( (chooseText == null) || (chooseText.length() == 0) )
    {
      chooseText = getShortDesc(bean);
    }

    if ( (chooseText != null) && (chooseText.length() != 0) )
    {
      if (isRtl)
      {
        _renderSpace(rw);
      }
      rw.startElement("span", null);
      renderStyleClass(context, arc,
        SkinSelectors.AF_NAVIGATION_LEVEL_CHOICE_LABEL_STYLE_CLASS);
      rw.write(chooseText);
      rw.endElement("span");
      if (!isRtl)
      {
        _renderSpace(rw);
      }
    }
  }

  private void _renderChoiceButton(
    FacesContext context,
    RenderingContext arc,
    ResponseWriter rw,
    boolean isRtl,
    String choiceSelectId
    ) throws IOException
  {
    if (!isRtl)
    {
      _renderSpace(rw);
    }
    //The button html element is not supported on all browsers;  use "input"
    //if it is not
    boolean useButtonTag = supportsAdvancedForms(arc);
    String element = useButtonTag ? "button" : "input";

    rw.startElement(element, null);
    renderStyleClass(context, arc,
      SkinSelectors.AF_NAVIGATION_LEVEL_CHOICE_BUTTON_STYLE_CLASS);
    String goText = arc.getSkin().getTranslatedString(
      arc.getLocaleContext(),
      _GO_BUTTON_LABEL_KEY);

    rw.writeAttribute("type", useButtonTag ? "button"  : "submit", null);
    
    // For Non-JavaScript browsers, encode the name attribute with the 
    // parameter name and value thus it would enable the browsers to 
    // include the name of this element in its payLoad if it submits
    // the page.
    
    if (!supportsScripting(arc) )
    {
    
      String nameAttri = XhtmlUtils.getEncodedParameter
                                     (XhtmlConstants.MULTIPLE_VALUE_PARAM)
                                     + choiceSelectId;
    
      rw.writeAttribute("name", nameAttri, null);
    
    }
    else
    {
      // The onclick handler will evaluate the value of the selected option:
      rw.writeAttribute(
        "onclick",
        "var navLevelSelect = document.getElementById('" +
          choiceSelectId +
          "'); eval(navLevelSelect.options[navLevelSelect.selectedIndex].value); return false;",
        null);
    }
 
    if (useButtonTag)
    {
      rw.write(goText);
    }
    else
    {
      rw.writeAttribute("value", goText, "text");
    }

    rw.endElement(element);

    if (isRtl)
    {
      _renderSpace(rw);
    }
  }

  private void _renderSpace(
    ResponseWriter rw
    ) throws IOException
  {
    rw.startElement("span", null);
    rw.writeAttribute("style", "width: 5px;", null);
    rw.write(" ");
    rw.endElement("span");
  }

  protected void renderTabItem(
    FacesContext context,
    RenderingContext arc,
    ResponseWriter rw,
    Map<String, Object> itemData,
    boolean isRtl
    ) throws IOException
  {
    rw.startElement("table", null);
    OutputUtils.renderLayoutTableAttributes(context, arc, "0", null);
    
    String appendedStyle = "display: inline;";    
      
    // In Safari and webkit browsers display:inline doesn't work as expected, and 
    // display:inline-block need to be used to make the table inline.
    if (arc.getAgent().getAgentName() == Agent.AGENT_WEBKIT)
    {
      appendedStyle = "display: inline-block;";
    }
    writeInlineStyles(rw, toString(itemData.get("inlineStyle")),
      appendedStyle); // user's style + what we must have on top of it
    rw.writeAttribute("title", itemData.get("shortDesc"), null);
    StringBuilder itemStyleClass = new StringBuilder();
    String userStyleClass = toString(itemData.get("styleClass"));
    if (userStyleClass != null)
    {
      itemStyleClass.append(userStyleClass);
      itemStyleClass.append(" "); // more style classes are appended below
    }

    // Assign the event handlers:
    boolean isDisabled = getBooleanFromProperty(itemData.get("isDisabled"));
    boolean isActive = getBooleanFromProperty(itemData.get("isActive"));
    String sectionStyleClass1;
    String sectionStyleClass2 = null;
    if (isActive)
    {
      if (isDisabled)
      {
        sectionStyleClass2 = SkinSelectors.P_AF_DISABLED;
      }
      sectionStyleClass1 =
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_ACTIVE_STYLE_CLASS;
    }
    else
    {
      if (isDisabled)
      {
        sectionStyleClass2 = SkinSelectors.P_AF_DISABLED;
      }
      sectionStyleClass1 =
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_INACTIVE_STYLE_CLASS;
    }
    renderStyleClass(context, arc, itemStyleClass.toString());
    rw.startElement("tbody", null);
    _writeInlineTbodyStyles(arc, rw);
    rw.startElement("tr", null);

    boolean isFirst = getBooleanFromProperty(itemData.get("isFirst"));
    boolean isLast = getBooleanFromProperty(itemData.get("isLast"));
    boolean previousActive = getBooleanFromProperty(itemData.get("previousActive"));

    // start portion of tab:
    if (isFirst)
    {
      // first start
      rw.startElement("td", null);
      _renderTabSection(
        context,
        arc,
        rw,
        sectionStyleClass1,
        sectionStyleClass2,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_START_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_START_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_START_CONTENT_STYLE_CLASS,
        null,
        isDisabled,
        isRtl);
      rw.endElement("td");
    }
    else if (previousActive)
    {
      // start-join-selected-to-deselected
      rw.startElement("td", null);
      _renderTabSection(
        context,
        arc,
        rw,
        sectionStyleClass1,
        sectionStyleClass2,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_START_JOIN_FROM_ACTIVE_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_END_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_MID_CONTENT_STYLE_CLASS,
        null,
        isDisabled,
        isRtl);
      rw.endElement("td");
    }
    else if (isActive)
    {
      // start-join-deselected-to-selected
      rw.startElement("td", null);
      _renderTabSection(
        context,
        arc,
        rw,
        sectionStyleClass1,
        sectionStyleClass2,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_START_JOIN_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_START_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_START_CONTENT_STYLE_CLASS,
        null,
        isDisabled,
        isRtl);
      rw.endElement("td");
    }
    else
    {
      // start-join-deselected-to-deselected
      rw.startElement("td", null);
      _renderTabSection(
        context,
        arc,
        rw,
        sectionStyleClass1,
        sectionStyleClass2,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_START_JOIN_FROM_INACTIVE_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_END_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_MID_CONTENT_STYLE_CLASS,
        null,
        isDisabled,
        isRtl);
      rw.endElement("td");
    }

    // mid portion of tab:
    rw.startElement("td", null);
    _renderTabSection(
      context,
      arc,
      rw,
      sectionStyleClass1,
      sectionStyleClass2,
      SkinSelectors.AF_NAVIGATION_LEVEL_TABS_MID_STYLE_CLASS,
      SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_MID_STYLE_CLASS,
      SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_MID_CONTENT_STYLE_CLASS,
      itemData,
      isDisabled,
      isRtl);
    rw.endElement("td");

    if (isLast)
    {
      // last end
      rw.startElement("td", null);
      _renderTabSection(
        context,
        arc,
        rw,
        sectionStyleClass1,
        sectionStyleClass2,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_END_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_END_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_END_CONTENT_STYLE_CLASS,
        null,
        isDisabled,
        isRtl);
      rw.endElement("td");
    }
    else if ( isActive ||  (!getBooleanFromProperty(itemData.get("nextActive"))) )
    {
      // end-join-selected-to-deselected or end-join-deselected-to-deselected
      rw.startElement("td", null);
      _renderTabSection(
        context,
        arc,
        rw,
        sectionStyleClass1,
        sectionStyleClass2,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_END_JOIN_TO_INACTIVE_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_END_JOIN_STYLE_CLASS,
        SkinSelectors.AF_NAVIGATION_LEVEL_TABS_BOTTOM_END_CONTENT_STYLE_CLASS,
        null,
        isDisabled,
        isRtl);
      rw.endElement("td");
    }

    rw.endElement("tr");
    rw.endElement("tbody");
    rw.endElement("table");
  }

  private void _renderTabSection(
    FacesContext context,
    RenderingContext arc,
    ResponseWriter rw,
    String sectionStyleClass1,
    String sectionStyleClass2,
    String topStyleClass,
    String bottomStyleClass,
    String bottomContentStyleClass,
    Map<String, Object> itemData,
    boolean isDisabled,
    boolean isRtl
    ) throws IOException
  {
    rw.startElement("table", null);
    OutputUtils.renderLayoutTableAttributes(context, arc, "0", null);
    if (sectionStyleClass2 == null)
    {
      renderStyleClass(context, arc, sectionStyleClass1);
    }
    else
    {
      String[] sectionStyleClasses = { sectionStyleClass1, sectionStyleClass2 };
      renderStyleClasses(context, arc, sectionStyleClasses);
    }
    rw.startElement("tbody", null);
    rw.startElement("tr", null);
    rw.startElement("td", null);
    renderStyleClass(context, arc, topStyleClass);
    if (itemData != null)
    {
      appendIconAndText(
        context,
        arc,
        rw,
        toString(itemData.get("icon")),
        itemData,
        isDisabled,
        isRtl);
    }
    rw.endElement("td");
    rw.endElement("tr");
    rw.startElement("tr", null);
    rw.startElement("td", null);
    renderStyleClass(context, arc, bottomStyleClass);
    if (bottomContentStyleClass != null)
    {
      rw.startElement("div", null);
      renderStyleClass(context, arc, bottomContentStyleClass);
      rw.endElement("div");
    }
    rw.endElement("td");
    rw.endElement("tr");
    rw.endElement("tbody");
    rw.endElement("table");
  }


  // sets the currency to the row or container that we want to start rendering
  // from.  (COPIED FROM HierarchyUtils, which is package-private)
  static private boolean _setStartDepthPath(
    UIXHierarchy component,
    int          startDepth
  )
  {
    boolean isNewPath = false;
    Object focusKey = component.getFocusRowKey();

    if (focusKey != null )
    {
      List<Object> focusPath = component.getAllAncestorContainerRowKeys(focusKey);
      focusPath = new ArrayList<Object>(focusPath);
      focusPath.add(focusKey);
      int focusSize =  focusPath.size();
      if ( focusSize > startDepth )
      {
        isNewPath = true;
        component.setRowKey(focusPath.get(startDepth));
      }
      else if ( focusSize == startDepth )
      {
        isNewPath = true;
        component.setRowKey(focusKey);
        component.enterContainer();
      }
    }
    else
    {
      if (startDepth  == 0)
      {
        isNewPath = true;
        component.setRowKey(null);
      }
    }

    return isNewPath;
  }

  /**
   * Check if a component is on a focus path
   */
  private static boolean _isOnFocusPath(UIXHierarchy component)
  {
    boolean isOnFocusPath = false;
    Object focusKey = component.getFocusRowKey();
    Object currentRowKey = component.getRowKey();
    if (focusKey != null)
    {
      List<Object> focusPath =
                component.getAllAncestorContainerRowKeys(focusKey);
      focusPath = new ArrayList<Object>(focusPath);
      focusPath.add(focusKey);
      int focusSize = focusPath.size();
      for (int i = 0; i < focusSize; i++)
      {
        Object rowKey = focusPath.get(i);
        if (rowKey.equals(currentRowKey))
        {
          isOnFocusPath = true;
          break;
        }
      }
    }

    return isOnFocusPath;
  }

  static private class NavItemData
  {
    NavItemData()
    {
      _list = new ArrayList<Map<String, Object>>();
      _effectiveActiveIndex = -1;
    }

    int getEffectiveActiveIndex()
    {
      return _effectiveActiveIndex;
    }

    void setEffectiveActiveIndex(int effectiveActiveIndex)
    {
      _effectiveActiveIndex = effectiveActiveIndex;
    }

    int getItemCount()
    {
      return _list.size();
    }

    void addItemData(Map<String, Object> itemData)
    {
      _list.add(itemData);
    }

    Map<String, Object> getItemData(int index)
    {
      return _list.get(index);
    }

    private List<Map<String, Object>> _list;
    private int _effectiveActiveIndex;
  }

  private PropertyKey _hintKey;
  private PropertyKey _titleKey;
  private PropertyKey _disabledKey;

  static private final String _HINT_TABS = "tabs";
  static private final String _HINT_BAR = "bar";
  static private final String _HINT_LIST = "list";
  static private final String _HINT_BUTTONS = "buttons";
  static private final String _HINT_CHOICE = "choice";
  static private final String _HINT_DEFAULT = NavigationPaneRenderer._HINT_TABS;

  private static final String _CHOICE_SELECT_ID_SUFFIX =
    "_af_choice_select";
  private static final String _GO_BUTTON_LABEL_KEY =
    "af_menuChoice.GO";

  static private final Object _EXTRA_SUBMIT_PARAMS_KEY = new Object();

  private static final TrinidadLogger _LOG =
    TrinidadLogger.createTrinidadLogger(NavigationPaneRenderer.class);
}
