/*
 *  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.beans.Beans;

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.html.HtmlBody;
import org.apache.myfaces.trinidad.context.RequestContext;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.render.ExtendedRenderKitService;
import org.apache.myfaces.trinidad.skin.Skin;
import org.apache.myfaces.trinidad.util.Service;


/**
 * Renderer for the panelPartialRoot.
 * <p>
 * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/renderkit/core/xhtml/BodyRenderer.java#1 $) $Date: 11-nov-2005.14:59:41 $
 */
public class BodyRenderer extends PanelPartialRootRenderer
{
  public BodyRenderer()
  {
    this(HtmlBody.TYPE);
  }

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

  @Override
  protected void findTypeConstants(FacesBean.Type type)
  {
    super.findTypeConstants(type);
    _firstClickPassedKey = type.findKey("firstClickPassed");
    _initialFocusIdKey = type.findKey("initialFocusId");
    _onloadKey = type.findKey("onload");
    _onunloadKey = type.findKey("onunload");
  }

  @Override
  protected void encodeAll(
    FacesContext        context,
    RenderingContext arc,
    UIComponent         component,
    FacesBean           bean) throws IOException
  {
    ResponseWriter writer = context.getResponseWriter();
    writer.startElement("body", component);
    renderId(context, component);
    renderAllAttributes(context, arc, bean);
    super.encodeAll(context, arc, component, bean);

    // Output a version comment at the bottom of the body
    _writeVersionInformation(context, arc);
  }

  @Override
  protected void renderAtEnd(
    FacesContext context,
    RenderingContext arc) throws IOException
  {
    _encodeServiceScripts(context);

    context.getResponseWriter().endElement("body");

    _renderInitialFocusScript(context, arc);
  }

  @Override
  protected void renderPPRSupport(
    FacesContext        context,
    RenderingContext arc,
    UIComponent         component,
    FacesBean           bean) throws IOException
  {
    super.renderPPRSupport(context, arc, component, bean);
    if (getFirstClickPassed(bean))
    {
      ResponseWriter writer = context.getResponseWriter();
      writer.startElement("script", null);
      renderScriptDeferAttribute(context, arc);
      renderScriptTypeAttribute(context, arc);
      writer.writeText("var _pprFirstClickPass=true;", null);
      writer.endElement("script");
    }
  }

  @Override
  protected void renderContent(
    FacesContext        context,
    RenderingContext arc,
    UIComponent         component,
    FacesBean           bean) throws IOException
  {
    boolean isPartialPass = PartialPageUtils.isPartialRenderingPass(arc);

    _renderAnchorForTop(context);
    
    // Since we are supporting Non-JavaScript browsers of generic PDA, 
    // we don't need to prompt any message in the client side regarding 
    // the JavaScript capability of generic PDA browsers.

    if (supportsScripting(arc))
    {
      _renderNoScript(context, arc);
      _storeInitialFocus(arc, bean);
    }

    if (!isPartialPass)
    {
      // start the span here, and end it in postrender
      _renderPartialBackSupportSpan(context, arc, true);
    }

    super.renderContent(context, arc, component, bean);

    if (!isPartialPass)
    {
      // end the span for PPR Back button support (and render the hidden fields)
      _renderPartialBackSupportSpan(context, arc, false);
    }
  }

  @Override
  protected void renderEventHandlers(
    FacesContext context,
    FacesBean    bean) throws IOException
  {
    super.renderEventHandlers(context, bean);
    RenderingContext arc = RenderingContext.getCurrentInstance();
    ResponseWriter rw = context.getResponseWriter();

    if (PartialPageUtils.isPartialRenderingPass(arc))
    {
      rw.writeAttribute("onunload", _PARTIAL_ONUNLOAD_HANDLER, null);
    }
    else
    {
      rw.writeAttribute("onload", getOnload(arc, bean), "onload");
      rw.writeAttribute("onunload", getOnunload(arc, bean), "onunload");

      // If partial back is supported,
      // render an onbeforeunload event handler. This javascript function
      // will save the page's state when the page is unloaded. This way if the
      // user goes back to the page via the Back button, we'll be able to
      // restore the state: the html and the javascript.
      if (_isPartialBackSupported(arc))
      {
        rw.writeAttribute("onbeforeunload",
                          _PPR_BACK_UNLOAD_SCRIPT,
                          null);
      }
    }
  }

  protected boolean getFirstClickPassed(FacesBean bean)
  {
    // =-=AEW firstClickPassed is not currently supported on document
    if (_firstClickPassedKey == null)
      return false;

    Object o = bean.getProperty(_firstClickPassedKey);
    if (o == null)
      o = _firstClickPassedKey.getDefault();
    return Boolean.TRUE.equals(o);
  }

  protected String getInitialFocusId(FacesBean bean)
  {
    return toString(bean.getProperty(_initialFocusIdKey));
  }

  protected String getOnload(RenderingContext arc, FacesBean bean)
  {
    String onload;
    if (_onloadKey == null)
      onload = null;
    else
      onload = toString(bean.getProperty(_onloadKey));

    String checkLoad = "";

    //PH: Currently, if a browser supports PPR, _checkLoad function is called
    //that sets initialFocus if set.For non-PPR browsers like blackBerry 4.0,
    //no body onload function is called. hence, initialFocus cannot is not set.
    //Therefore, created another function _checkLoadNoPPR() This function is
    //called by the onLoad JS handler of body tag when device does not support
    //PPR
    if (PartialPageUtils.supportsPartialRendering(arc))
    {
      // Don't short circuit...
      //PH:_checkLoad(event) is replaced by _checkLoad() because on certain
      //devices like IE Mobile , event object is not defined. Moreover,
      //_checkLoad function does not use event object. So, remove it altogether
      //for all PPR devices
      checkLoad = "_checkLoad()";
    }
    else
    {
      //HKuhn - in printable mode we don't need PPR checking
      // Check only, if Agents supports Navigation or Editing
      if (supportsNavigation(arc) || supportsEditing(arc))
        checkLoad = "_checkLoadNoPPR()";
    }
    
    onload = XhtmlUtils.getChainedJS(checkLoad, onload, false);

    return onload;
  }

  protected String getOnunload(RenderingContext arc, FacesBean bean)
  {
    String onunload;
    if (_onunloadKey == null)
      onunload = null;
    else
      onunload = toString(bean.getProperty(_onunloadKey));
    if (PartialPageUtils.supportsPartialRendering(arc))
    {
      // Don't short circuit...
      onunload = XhtmlUtils.getChainedJS("_checkUnload(event)",
                                         onunload,
                                         false);
    }

    return onunload;
  }



  /**
   * Renders a top anchor at the top of the page
   * In quirks mode this is not required,but Mozilla will complain
   * in standards mode.
   * @param context
   * @throws IOException
   */
  private void _renderAnchorForTop(FacesContext context) throws IOException
  {
    ResponseWriter writer = context.getResponseWriter();
    writer.startElement("a",null);
    writer.writeAttribute("name","top",null);
    writer.endElement("a");
  }

  private void _renderNoScript(
    FacesContext        context,
    RenderingContext arc) throws IOException
  {
    // Some accessibility standards rather oddly claim that NOSCRIPT
    // tags are essential for compliance.  So, render NOSCRIPT, at
    // least when we're not in "inacessible" mode.
    //
    // But don't bother in design time mode - this check is
    // largely there for JDev 10.1.3 preview, which was rendering
    // the contents of any NOSCRIPT tags in the VE, but it's
    // a check that does no harm.
    if (!isInaccessibleMode(arc) && !Beans.isDesignTime())
    {
      ResponseWriter writer = context.getResponseWriter();
      writer.startElement("noscript",null);
      String message = arc.getTranslatedString("NO_SCRIPT_MESSAGE");
      writer.writeText(message, null);
      writer.endElement("noscript");
    }
  }

  private void _storeInitialFocus(
    RenderingContext arc,
    FacesBean           bean)
  {

    // The initialFocus functionality is only supported in inaccessible mode,
    // and only platforms that support scripting.
    if (!isInaccessibleMode(arc) || !supportsScripting(arc))
    {
      return;
    }

    // initial focus is the id of the component to which you want the
    // focus to be when the page full-page loads. In a PPR, the focus is
    // not set, which is a good thing.
    String initialFocusID = getInitialFocusId(bean);
    if (initialFocusID != null)
    {
      // Put the initial focus id on the rendering context for use in
      // postrender and also so that it can be modified
      // by the component with this id if necessary. For example,
      // the NavigationBar needs its initial focus on the Next button, but
      // the component's id does not get rendered on the Next button. The
      // NavigationBar creates a special id for the Next button, and sticks
      // this id back on the AdfRenderingContext for the body to know about in
      // postrender.
      arc.getProperties().put(XhtmlConstants.INITIAL_FOCUS_CONTEXT_PROPERTY,
                              initialFocusID);
    }
  }

  //
  // Writes a small script that sets the _initialFocusID variable on the page.
  //
  private void _renderInitialFocusScript(
    FacesContext        context,
    RenderingContext arc
    ) throws IOException
  {

    // The initialFocus functionality is not supported if not inaccessible mode
    // nor on Netscape nor on platforms that do not support scripting.
    if (!isInaccessibleMode(arc) || !supportsScripting(arc))
    {
      return;
    }

    // Render the initial focus id, if it exists on the rendering context.
    // The initial focus id was initially set in prerender, and may have
    // been overwritten by the component's renderer if need be.
    String initialFocusID = (String) arc.getProperties().get(
                                    XhtmlConstants.INITIAL_FOCUS_CONTEXT_PROPERTY);

    if (initialFocusID != null)
    {
      ResponseWriter writer = context.getResponseWriter();
      writer.startElement("script", null);
      XhtmlRenderer.renderScriptTypeAttribute(context, arc);
      writer.writeText("_initialFocusID='", null);
      writer.writeText(initialFocusID, null);
      writer.writeText("';", null);
      writer.endElement("script");
    }
  }

  // If partial back is supported,
  // render a span with the _PPR_BACK_CONTENT_ID id.
  // This is used to be able to save the html content of the page
  // when the user leaves the page. This way we can restore the html if
  // the user used the Back button to go back to the page.
  private static void _renderPartialBackSupportSpan(
    FacesContext        context,
    RenderingContext arc,
    boolean             isStart
    ) throws IOException
  {
    if (_isPartialBackSupported(arc))
    {
      ResponseWriter writer = context.getResponseWriter();

      if (isStart)
      {
        writer.startElement("span", null);
        writer.writeAttribute("id", _PPR_BACK_CONTENT_ID, null);
      }
      else
      {
        writer.endElement("span");
        // render these hidden, disabled fields to save the javascript
        // library names, the inline scripts, and the page's contents
        // these will be restored when we come back to the page after
        // leaving it.
        _renderPartialBackSupportHiddenFields(writer, _PPR_BACK_SAVE_LIBRARY_ID);
        _renderPartialBackSupportHiddenFields(writer, _PPR_BACK_SAVE_SCRIPT_ID);
        _renderPartialBackSupportHiddenFields(writer, _PPR_BACK_SAVE_CONTENT_ID);
      }
    }
  }

  // given the field name, render an input element, with the fieldName
  // as the id, and make it hidden and disabled, so that the values do
  // not go to the server when the form is submitted.
  private static void _renderPartialBackSupportHiddenFields(
    ResponseWriter writer,
    String       fieldName
    ) throws IOException
  {

    writer.startElement("input", null);

    writer.writeAttribute("id", fieldName, null);
    writer.writeAttribute("type", "hidden", null);
    writer.writeAttribute("disabled", Boolean.TRUE, null);

    writer.endElement("input");
  }


  private static boolean _isPartialBackSupported(
    RenderingContext arc
    )
  {
    /*
      // Only supported on IE  - but comment this out while
      // it's off altogether
    if (!isIE(arc))
      return false;
    */

    // =-=AEW We need a mechanism to turn it on... or we need
    // to remove it altogether
    return false;
  }

  //
  // Writes version information about the page.
  //
  static private void _writeVersionInformation(
    FacesContext context,
    RenderingContext arc)
    throws IOException
  {
    String comment = _VERSION_COMMENT;

    Class<BodyRenderer> implClass = BodyRenderer.class;
    Package implPkg = implClass.getPackage();

    Class<FacesBean> apiClass  =  FacesBean.class;
    Package apiPkg = apiClass.getPackage();

    String versionInfo = _getVersionInfo(apiPkg, implPkg);

    comment += versionInfo;

    String accessibilityMode = null;
    if (isInaccessibleMode(arc))
      accessibilityMode = "disabled";
    else if (isScreenReaderMode(arc))
      accessibilityMode = "enhanced";

    if (accessibilityMode != null)
    {
      comment += ", Accessibility:"+accessibilityMode;
    }

    // Tack on the Skin id
    Skin skin = arc.getSkin();
    String skinId = skin.getId();
    if (skinId != null)
    {
      comment += ", skin:" + skinId;

      // Also log preferred Skin if we have one
      RequestContext requestContext = RequestContext.getCurrentInstance();
      String preferredSkin = requestContext.getSkinFamily();
      if (preferredSkin != null)
        comment += (" (" + preferredSkin + ")");
    }

    context.getResponseWriter().writeComment(comment);
  }


  static private void _encodeServiceScripts(FacesContext context)
    throws IOException
  {
    ExtendedRenderKitService service =
      Service.getRenderKitService(context, ExtendedRenderKitService.class);
    if (service != null)
    {
      service.encodeScripts(context);
    }
  }

  static private String _getVersionInfo(Package apiPackage, Package implPackage)
  {

    String versionInfo    = "";
    String apiSpecTitle   = "Apache Trinidad API";
    String implSpecTitle  = "Apache Trinidad Implementation";
    String apiVersion     = "??";
    String implVersion    = "??";

    String temp;
    // This normally happens in dev environment, when we have mapped to the
    // dependencies. If you need to see the version info, then map it to the
    // api snapshot and impl snapshot jar and remove the dependency on api and impl
    // in the project. If we don't want to impact golden files, we can simply
    // return a blank string.
    if (apiPackage == null && implPackage == null)
    {
      return "(Version unknown)";
    }
    else
    {
      if (apiPackage != null)
      {
        apiSpecTitle = (((temp = apiPackage.getSpecificationTitle()) != null)?
                        temp : apiSpecTitle);

        apiVersion   = (((temp = apiPackage.getImplementationVersion()) != null)?
                        temp : apiVersion);
      }

      if(implPackage != null)
      {
        implSpecTitle = (((temp = implPackage.getSpecificationTitle())!= null)?
                        temp : implSpecTitle);

        implVersion   = (((temp = implPackage.getImplementationVersion())!= null)?
                        temp : implVersion);
       }

      // if there is version mismatch let us mark and print a verbose
      // information
      if (apiVersion == "??" || implVersion == "??")
        versionInfo = "Version unknown: ";
      else if (apiVersion != "??" && implVersion != "??"
                && !apiVersion.equals(implVersion))
        versionInfo = "Version mismatch: ";
    }

    return "(" + versionInfo + apiSpecTitle  + " - " + apiVersion  + "/"
                             + implSpecTitle + " - " + implVersion + ")";
  }

  private PropertyKey _firstClickPassedKey;
  private PropertyKey _initialFocusIdKey;
  private PropertyKey _onloadKey;
  private PropertyKey _onunloadKey;

  // Onunload handler used for partial page rendering
  private static final String _PARTIAL_ONUNLOAD_HANDLER = "_partialUnload()";

  static private final String _VERSION_COMMENT = "Created by Apache Trinidad ";

  // Constants for PPR back
  static private final String _PPR_BACK_UNLOAD_SCRIPT = "_savePageStateIE()";
  static private final String _PPR_BACK_CONTENT_ID = "_pprPageContent";
  static private final String _PPR_BACK_SAVE_CONTENT_ID = "_pprSavePage";
  static private final String _PPR_BACK_SAVE_SCRIPT_ID = "_pprSaveScript";
  static private final String _PPR_BACK_SAVE_LIBRARY_ID = "_pprSaveLib";
}
