/*
 *  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 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.core.layout.CoreShowDetail;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.context.FormData;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.skin.Icon;

/**
 * This needs to be massively cleaned up...
 * @todo TEST NON-PPR!  I removed the non-PPR case from the
 *   JS script
 */
public class ShowDetailRenderer extends ShowDetailItemRenderer
{
  public ShowDetailRenderer()
  {
    this(CoreShowDetail.TYPE);
  }

  protected ShowDetailRenderer(FacesBean.Type type)
  {
    super(type);
  }

  @Override
  protected void findTypeConstants(FacesBean.Type type)
  {
    super.findTypeConstants(type);
    _immediateKey = type.findKey("immediate");
    _disclosedTextKey = type.findKey("disclosedText");
    _undisclosedTextKey = type.findKey("undisclosedText");
  }

  /**
   */
  @Override
  protected void encodeAll(
    FacesContext        context,
    RenderingContext arc,
    UIComponent         component,
    FacesBean           bean) throws IOException
  {
    
    boolean javaScriptSupport = supportsScripting(arc);
    boolean disclosed = getDisclosed(bean);

    ResponseWriter rw = context.getResponseWriter();
    rw.startElement("span", component);
    if (!isTableAllDisclosure() && !isTableDetailDisclosure())
      renderId(context, component);

    renderPromptStart(context, arc, component, bean);
    String sourceValue = getClientId(context, component);
    String linkId = getLinkId(sourceValue, disclosed);
    
    String parameterString;
    if (javaScriptSupport)
    {
      _renderScripts(context, arc, component);
      parameterString = _generateOnClickString(context, 
                                               arc, 
                                               component, 
                                               bean, 
                                               disclosed);
    }
    else
    { 
      parameterString = _encodedParam(context, 
                                      arc, 
                                      component, 
                                      bean, 
                                      disclosed);

    }
    
    _renderLinkStart(context, arc, parameterString);

    if (linkId != null)
      rw.writeAttribute("id", linkId, null);

    if (!isTableAllDisclosure())
    {
      renderStyleClasses(context, arc, getDisclosureIconLinkStyleClasses());
      renderDisclosureIcon(context, arc, disclosed);
      _renderLinkEnd(context, arc);
    }

    UIComponent prompt = getFacet(component,
                                  CoreShowDetail.PROMPT_FACET);
    if (prompt == null)
    {
      String text = getDisclosureText(arc, bean, disclosed);
      if (text != null)
      {
        if (!isTableAllDisclosure())
          _renderLinkStart(context, arc, parameterString);
        renderStyleClasses(context, arc, getLinkStyleClasses());
        if (javaScriptSupport)
        {
          rw.writeText(text,
                     disclosed ? "disclosedText" : "undisclosedText");
        }
        else
        {
          // Since for Non-JavaScript browsers we render an input tag, set the 
          // value attribute to text
          rw.writeAttribute("value", text, 
                           disclosed ? "disclosedText" : "undisclosedText");
        }
        _renderLinkEnd(context, arc);
      }
    }

    if (isTableAllDisclosure() && prompt != null)
      _renderLinkEnd(context, arc);

    if (prompt != null)
    {
      encodeChild(context, prompt);
    }

    renderPromptEnd(context);

    if (disclosed &&
        !isTableAllDisclosure() &&
        !isTableDetailDisclosure() &&
        !renderAsInline())
    {
      rw.startElement("div", null);
      encodeAllChildren(context, component);
      rw.endElement("div");
    }

    rw.endElement("span");

  }

  protected void renderPromptStart(
    FacesContext        context,
    RenderingContext arc,
    UIComponent         component,
    FacesBean           bean) throws IOException
  {
    ResponseWriter writer = context.getResponseWriter();

    // Start a different span;  this is where all attributes
    // except the ID will go.
    if ( renderAsInline())
      writer.startElement("span", component);
    else
      writer.startElement("div", component);

    renderAllAttributes(context, arc, bean);
  }

  protected void renderPromptEnd(
    FacesContext        context) throws IOException
  {
    ResponseWriter writer = context.getResponseWriter();

    if ( renderAsInline())
      writer.endElement("span");
    else
      writer.endElement("div");
  }


  static public void renderDisclosureIcon(
    FacesContext        context,
    RenderingContext arc,
    boolean             disclosed,
    String              disclosedAltTextKey,
    String              undisclosedAltTextKey) throws IOException
  { 
    String key = disclosed ? disclosedAltTextKey : undisclosedAltTextKey;
    // Get the alt text
    String altText = arc.getTranslatedString(key);
    // Since we render input element for Non-JavaScript browsers, we cannot
    // render image element as its child. So set the value attribute of 
    // input element to the icon symbol 
    if (!supportsScripting(arc))
    {
      ResponseWriter rw = context.getResponseWriter();
      String icon = disclosed ? XhtmlConstants.NON_JS_DETAIL_DISCLOSED_ICON :
                                XhtmlConstants.NON_JS_DETAIL_UNDISCLOSED_ICON;  
      rw.writeAttribute("title", altText, null);
      rw.writeAttribute("value", icon, null);
      String linkConverter = "border: none; background: inherit;";
      rw.writeAttribute("style", linkConverter,null);
    }
    else
    {
      Icon icon = _getDisclosureIcon(arc, disclosed);
      if (icon != null)
      {
        // Get the align
        String align = OutputUtils.getMiddleIconAlignment(arc);
        // Render the icon with the specified attrs
        OutputUtils.renderIcon(context, arc, icon, altText, align);
      }
    }
  }


  protected void renderDisclosureIcon(
    FacesContext        context,
    RenderingContext arc,
    boolean             disclosed) throws IOException
  {
    renderDisclosureIcon(context, arc, disclosed,
                         _DISCLOSED_TIP_KEY, _UNDISCLOSED_TIP_KEY);
  }

  /**
   * Always render an ID, needed for proper PPR.
   */
  @Override
  protected boolean shouldRenderId(
    FacesContext context,
    UIComponent  component)
  {
    return true;
  }


  // Returns the disclosure Icon
  private static Icon _getDisclosureIcon(
    RenderingContext arc,
    boolean             disclosed
    )
  {
    String iconName = (disclosed
                       ? SkinSelectors.AF_SHOW_DETAIL_DISCLOSED_ICON_NAME
                       : SkinSelectors.AF_SHOW_DETAIL_UNDISCLOSED_ICON_NAME);

    return arc.getIcon(iconName);
  }

  protected String getDisclosureText(
    RenderingContext arc,
    FacesBean           bean,
    boolean             disclosed)
  {
    String text;
    if (disclosed)
    {
      text = getDisclosedText(bean);
      if (text == null)
      {
        //smo: This functionality was added durring API Merge.  If getDisclosedText is not present
        //we'll render undislosedText text if it's present before rendering the defaults
        text = getUndisclosedText(bean);
        if(text == null)
        {
          text = arc.getTranslatedString(_DISCLOSED_KEY);
        }
      }
    }
    else
    {
      text = getUndisclosedText(bean);
      if (text == null)
      {
        text = getDisclosedText(bean);
        if(text == null)
        {
          text = arc.getTranslatedString(_UNDISCLOSED_KEY);
        }
      }
    }

    return text;
  }

  private void _renderScripts(
    FacesContext        context,
    RenderingContext arc,
    UIComponent         component) throws IOException
  {
    if (!supportsNavigation(arc))
      return;

    FormData fData = arc.getFormData();
    if (fData == null)
    {
      _LOG.warning("SHOWDETAIL_NOT_IN_FORM_WILLNOT_FUNCTION_PROPERLY");
      return;
    }

    String valueValue = getValueParameter(component);


    ResponseWriter rw = context.getResponseWriter();
    boolean partial = PartialPageUtils.isPPRActive(context);

    // the first time, render the necessary Javascript
    if (arc.getProperties().get(_SHOW_DETAIL_SUBMIT_JS_RENDERED) == null)
    {
      arc.getProperties().put(_SHOW_DETAIL_SUBMIT_JS_RENDERED,
                              Boolean.TRUE);
      // write the submit function

      String js;
      // Javascript function, optimized to reduce size.  Parameters are:
      // a: form name
      // v: validate?
      // b: event name
      // c: source parameter (id)
      // l: ID of link (for focusing)
      // d: value parameter (for use in table)
      // =-=AEW Why bother including "document" and "window" in
      // _setRequestedFocusNode() call?  It could derive them itself.
      if (partial)
      {
        js =
          "function _submitHideShow(a,v,b,c,l,d) {" +
            "var o = {"+
                  XhtmlConstants.EVENT_PARAM + ":b," +
                  XhtmlConstants.SOURCE_PARAM + ":c};" +
            "if (d!=(void 0)) o." +
                  XhtmlConstants.VALUE_PARAM + "=d;" +
            "_setRequestedFocusNode(document,l,false,window);" +
            "_submitPartialChange(a,v,o);" +
            "return false;}";
      }
      else
      {
        js =
          "function _submitHideShow(a,v,b,c,l,d) {" +
            "var o={"+
                  XhtmlConstants.EVENT_PARAM + ":b," +
                  XhtmlConstants.SOURCE_PARAM + ":c};" +
            "if (d!=(void 0)) o." +
                  XhtmlConstants.VALUE_PARAM + "=d;" +
            "submitForm(a,v,o);" +
            "return false;}";
      }

      // write the submit function
      rw.startElement("script", null);
      renderScriptDeferAttribute(context, arc);
      renderScriptTypeAttribute(context, arc);

      rw.writeText(js, null);
      rw.endElement("script");

      // Add these needed values at most once per page
      fData.addNeededValue(XhtmlConstants.EVENT_PARAM);
      fData.addNeededValue(XhtmlConstants.SOURCE_PARAM);
      if (partial)
        fData.addNeededValue(XhtmlConstants.PARTIAL_PARAM);
    }

    // And add this needed value if it ever comes up
    if (valueValue != null)
      fData.addNeededValue(XhtmlConstants.VALUE_PARAM);

  }

  private void _renderLinkStart(
    FacesContext        context,
    RenderingContext arc,
    String           parameterString ) throws IOException
  {
    ResponseWriter rw = context.getResponseWriter();
    if (!supportsNavigation(arc)) {
      rw.startElement("span", null);
    }
    else if (supportsScripting(arc))
    {
      rw.startElement("a", null);
      rw.writeAttribute("onclick", parameterString, null);
      rw.writeURIAttribute("href", "#", null);
    }
    // For Non-JavaScript browsers, render an input element(type=submit) to 
    // submit the page. 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.
    else
    {
      rw.startElement("input", null);
      rw.writeAttribute("type", "submit", null);
      rw.writeURIAttribute("name", parameterString, null);
    }
  }

  private String _generateOnClickString(
    FacesContext        context,
    RenderingContext arc,
    UIComponent         component,
    FacesBean           bean,
    boolean             disclosed)
  {
    FormData fData = arc.getFormData();
    if (fData == null)
       return null;

    String sourceValue = getClientId(context, component);
    String formName = fData.getName();
    String eventValue = (disclosed
                         ? XhtmlConstants.HIDE_EVENT
                         : XhtmlConstants.SHOW_EVENT);
    String valueValue = getValueParameter(component);
    String linkId = getLinkId(sourceValue, disclosed);

    int linkBufferLen = 41 +
                        formName.length() +
                        eventValue.length() +
                        sourceValue.length();

    if (valueValue != null)
      linkBufferLen += valueValue .length();
    else
      linkBufferLen += 4;


    StringBuffer linkBuffer = new StringBuffer(linkBufferLen);
    linkBuffer.append("return _submitHideShow('");
    linkBuffer.append(formName);
    linkBuffer.append("',");
    linkBuffer.append(getImmediate(bean) ? '0' : '1');
    linkBuffer.append(",'");
    linkBuffer.append(eventValue);
    linkBuffer.append("','");
    linkBuffer.append(sourceValue);
    linkBuffer.append("',");

    if (linkId != null)
      linkBuffer.append("'").append(linkId).append("'");
    else
      linkBuffer.append("null");

    if (valueValue != null)
      linkBuffer.append(",'").append(valueValue).append("'");

    linkBuffer.append(")");
    return linkBuffer.toString();
  }


  private void _renderLinkEnd(
    FacesContext        context,
    RenderingContext arc) throws IOException
  {
    ResponseWriter rw = context.getResponseWriter();
    if (!supportsNavigation(arc))
      rw.endElement("span");
    else if (supportsScripting(arc))
      rw.endElement("a");
    else
      rw.endElement("input");
  }
  
  /**
   * @return encoded parameter name and value pairs for Non-JavaScript
   * browsers 
   */
  private String _encodedParam(
    FacesContext        context,
    RenderingContext arc,
    UIComponent         component,
    FacesBean           bean,
    boolean             disclosed)
  {
    FormData fData = arc.getFormData();
    if (fData == null)
       return null;
       
    String sourceValue = getClientId(context, component);
    String eventValue = (disclosed
                         ? XhtmlConstants.HIDE_EVENT
                         : XhtmlConstants.SHOW_EVENT);
                         
    String valueValue = getValueParameter(component);
    String linkId = getLinkId(sourceValue, disclosed);
  
    String nameAttri = XhtmlUtils.getEncodedParameter
                                  (XhtmlConstants.SOURCE_PARAM)
                       + XhtmlUtils.getEncodedParameter(sourceValue)
                       + XhtmlUtils.getEncodedParameter
                                  (XhtmlConstants.EVENT_PARAM)
                       + XhtmlUtils.getEncodedParameter(eventValue)
                       + XhtmlUtils.getEncodedParameter
                                  (XhtmlConstants.VALUE_PARAM)
                       + valueValue;  
    return nameAttri;
  }

  /**
   * Hook for table;  it'd be cleaner to simply expose
   * more protected hooks in general, and eliminate this
   * Table-ShowDetail dependency.
   */
  protected boolean isTableDetailDisclosure()
  {
    return false;
  }

  /**
   * Hook for table;  it'd be cleaner to simply expose
   * more protected hooks in general, and eliminate this
   * Table-ShowDetail dependency.
   */
  protected boolean isTableAllDisclosure()
  {
    return false;
  }

  protected boolean renderAsInline()
  {
    return false;
  }

  protected String getValueParameter(UIComponent component)
  {
    return null;
  }

  /**
   * Returns the style classes to use for links rendered by the
   * ShowDetailRenderer
   */
  protected String[] getLinkStyleClasses()
  {
    return PROMPT_LINK_STYLE_CLASSES;
  }

  /**
   */
  protected String getLinkId(String rootId, boolean disclosed)
  {
    return XhtmlUtils.getCompositeId(rootId, null);
  }

  protected boolean getImmediate(FacesBean bean)
  {
    Object o = bean.getProperty(_immediateKey);
    if (o == null)
      o = _immediateKey.getDefault();

    return Boolean.TRUE.equals(o);
  }


  protected String getDisclosedText(FacesBean bean)
  {
    // It can be null in the table...
    if (_disclosedTextKey == null)
      return null;

    return toString(bean.getProperty(_disclosedTextKey));
  }

  protected String getUndisclosedText(FacesBean bean)
  {
    // It can be null in the table...
    if (_undisclosedTextKey == null)
      return null;

    return toString(bean.getProperty(_undisclosedTextKey));
  }

  protected String[] getDisclosureIconLinkStyleClasses() {
    return DISCLOSURE_ICON_LINK_STYLE_CLASSES;
  }

  protected String getPromptStyleClass(boolean disclosed) {
    return disclosed
        ? SkinSelectors.AF_SHOW_DETAIL_PROMPT_DISCLOSED_STYLE_CLASS
        : SkinSelectors.AF_SHOW_DETAIL_PROMPT_UNDISCLOSED_STYLE_CLASS;
  }

  protected String getDefaultStyleClass(FacesBean bean) {
    return getPromptStyleClass(getDisclosed(bean));
  }

  private PropertyKey _immediateKey;
  private PropertyKey _disclosedTextKey;
  private PropertyKey _undisclosedTextKey;

  private static final Object _SHOW_DETAIL_SUBMIT_JS_RENDERED =
                             new Object();

  private static final String _DISCLOSED_KEY =
    "af_showDetail.DISCLOSED";
  private static final String _UNDISCLOSED_KEY =
    "af_showDetail.UNDISCLOSED";

  private static final String _DISCLOSED_TIP_KEY =
    "af_showDetail.DISCLOSED_TIP";
  private static final String _UNDISCLOSED_TIP_KEY =
    "af_showDetail.UNDISCLOSED_TIP";

  private static final String[] PROMPT_LINK_STYLE_CLASSES =
    {SkinSelectors.LINK_STYLE_CLASS,
     SkinSelectors.AF_SHOW_DETAIL_PROMPT_LINK_STYLE_CLASS};

  private static final String[]DISCLOSURE_ICON_LINK_STYLE_CLASSES =
    {SkinSelectors.LINK_STYLE_CLASS,
     SkinSelectors.AF_SHOW_DETAIL_DISCLOSURE_ICON_LINK_STYLE_CLASS};

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