/*
 *  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.ui.laf.base.desktop;

import java.awt.Color;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;

import javax.faces.component.UIComponent;
import javax.faces.context.ResponseWriter;

import org.apache.myfaces.trinidadinternal.agent.TrinidadAgent;
import org.apache.myfaces.trinidadinternal.image.ImageContext;
import org.apache.myfaces.trinidadinternal.image.ImageProvider;
import org.apache.myfaces.trinidadinternal.image.ImageProviderRequest;
import org.apache.myfaces.trinidadinternal.image.ImageProviderResponse;
import org.apache.myfaces.trinidadinternal.image.cache.CompositeButtonKey;
import org.apache.myfaces.trinidadinternal.renderkit.core.xhtml.SkinSelectors;
import org.apache.myfaces.trinidadinternal.style.Style;
import org.apache.myfaces.trinidadinternal.style.util.CSSUtils;
import org.apache.myfaces.trinidadinternal.style.util.FontProxy;
import org.apache.myfaces.trinidadinternal.style.util.MutableFontProxy;
import org.apache.myfaces.trinidadinternal.ui.AttributeKey;
import org.apache.myfaces.trinidadinternal.ui.Renderer;
import org.apache.myfaces.trinidadinternal.ui.UIConstants;
import org.apache.myfaces.trinidadinternal.ui.UINode;
import org.apache.myfaces.trinidadinternal.ui.UIXRenderingContext;
import org.apache.myfaces.trinidadinternal.ui.action.ClientAction;
import org.apache.myfaces.trinidadinternal.ui.action.ClientActionUtils;
import org.apache.myfaces.trinidadinternal.ui.action.FireAction;


/**
 * Renderer for button nodes.
 *
 * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/ui/laf/oracle/desktop/ButtonRenderer.java#1 $) $Date: 11-nov-2005.14:59:41 $
 * @deprecated This class comes from the old Java 1.2 UIX codebase and should not be used anymore.
 */
@Deprecated
abstract public class ButtonRenderer extends GeneratedImageRenderer
{
  /**
   * Override of BaseRenderer.prerender().  Note that ButtonRenderer
   * performs the actual button content generation in
   * <code>renderImageContent(RenderingContext,
   *         UINode,
   *         ImageProviderResponse)
   * </code>
   * Subclasses should override <code>renderImageContent()</code>instead of
   * prerender() if additional prerendering is needed.
   * @param context The rendering context
   * @param node the node to be rendered
   * @throws IOException
   * @see #renderContent(UIXRenderingContext,UINode)
   * @see #renderImageContent(UIXRenderingContext,UINode,ImageProviderResponse)
   */
  @Override
  protected final void prerender(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    // Don't bother with prerendering - it's all done in renderContent()
    // Note: we explicitly avoid calling super.prerender(), since we
    // want prerender() to be a no-op.
  }

  /**
   * No post rendering action required all action is done in
   * <code>renderContent(RenderingContext,UINode)</code>
   * @param context The rendering context
   * @param node the node to be rendered
   * @throws IOException
   */
  @Override
  protected final void postrender(
    UIXRenderingContext context,
    UINode node
    ) throws IOException
  {
    // Don't bother with postrendering - it's all done in renderContent()
    // Note: we explicitly avoid calling super.postrender(), since we
    // want postrender() to be a no-op.
  }

  /**
   * Override of BaseRender.renderContent().  renderContent() attempts
   * to generate the button image, and then delegates to one of two
   * methods: <code>renderImageContent()</code> is called if image
   * generation succeeds. <code> Otherwise renderAltContent()</code> is called.
   * @param context The rendering context
   * @param node the node to be rendered
   * @throws IOException
   */
  @Override
  protected final void renderContent(
    UIXRenderingContext context,
    UINode           node
    ) throws IOException
  {
    ImageProviderResponse response = _getImage(context, node);

    if (response != null)
      renderImageContent(context, node ,response);
    else
      renderAltContent(context, node);
  }

  /**
   * Tests whether the button should be rendered as an image.
   * If true, the button will be rendered as an image.  If
   * false, the button will be rendered using alternate content
   */
  protected boolean doRenderImageContent(
    UIXRenderingContext context,
    UINode           node
    )
  {
    // We render the button as an image as long as we
    // aren't running in screen reader mode.
    return !(isScreenReaderMode(context));
  }

  /**
   * Renders the button as an image using the image specified by
   * the ImageProviderResponse.  This method is called by
   * <code>renderContent()</code> when image generation succeeds.  Otherwise,
   * <code>renderAltContent()</code> is called.
   * @param context The rendering context
   * @param node the node to be rendered
   * @param response ImageProviderResponse which descibes the button
   *  image to render
   * @throws IOException
   */
  protected void renderImageContent(
    UIXRenderingContext context,
    UINode node,
    ImageProviderResponse response
    ) throws IOException
  {
    // If we've got a ClientAction, let it write its dependencies
    // before we start rendering the link
    _writeClientActionDependency(context, node);

    renderImage(context, node, response, null);

    // render onkeydown if adfbtn attribute has been rendered.
    // this means that the accessKey has been rendered in IE
    _renderOnKeyDownScript(context);
  }

  /**
   * Override of BaseRenderer.render().
   * Subclasses that need to customize button rendering should
   * override <code>renderImageContent()</code> or
   * <code>renderAltContent()</code>.
   * @param context The rendering context
   * @param node the node to be rendered
   * image to render
   * @throws IOException
   */
  @Override
  public final void render(UIXRenderingContext context, UINode node)
    throws IOException
  {
    // We override render() so that we can bypass the normal rendering
    // mechanism when we are running in screen reader mode.  We don't
    // want to render image content in screen reader mode, and instead
    // we delegate to the laf.base.xhtml.ButtonRenderer to render a
    // standard HTML button in this mode.
    //
    // Note: we do this check in render() instead of renderContent(),
    // because we want to be able to short-circuit to the alternate Renderer
    // *before* any prerendering is performed.
    //
    // And also note: because we don't really use renderContent(), prerender()
    // and postrender() they way that they are meant to be used, we probably
    // should even extend BaseRenderer at all.  But we want to extend
    // BlafRenderer so that we can pick up its convenience functions,
    // so we end up inheriting these methods anyway.

    // In "printable" facet, do not render buttons at all (bug 3327791)
    if (supportsNavigation(context))
    {
      if (doRenderImageContent(context, node))
      {
        super.render(context, node);
      }
      else
      {
        renderAltContent(context, node);
      }
    }
  }


  /**
   * Renders alternate content in the event that the button can not
   * or should not be rendered as an image.  This is called either if
   * image generation fails, or if we are running in screen reader mode.
   * @param context The rendering context
   * @param node the node to be rendered
   * @throws IOException
   */
  protected void renderAltContent(
    UIXRenderingContext context,
    UINode node
    ) throws IOException
  {
    _getAlternateRenderer(context).render(context, node);
  }

  /**
   * Returns the alternate Renderer for button.
   * @return xhtml ButtonRenderer
   */
    protected Renderer getAltRenderer()
    {
      return _ALTERNATE_RENDERER;
    }
    
  @Override
  protected Object getOnClick(
    UIXRenderingContext context,
    UINode           node
    )
  {
    // We build up the actual onclick handler by chaining the value the
    // ONCLICK_ATTR with the script of the PRIMARY_CLIENT_ACTION_ATTR.
    // Since we get the onclick handler multiple times (getDestination(),
    // which is called multiple times, and renderEventHandlers()), and
    // since building up the handler can be expensive, we store the
    // script in a local property, so that this work does not need to
    // be repeated.
    Object prop = context.getLocalProperty(0,
                                           _LOCAL_ON_CLICK_KEY,
                                           _ON_CLICK_NONE);
    if (prop != _ON_CLICK_NONE)
      return prop;

    Object onClick = super.getOnClick(context, node);
    ClientAction action = ClientActionUtils.getPrimaryClientAction(context,
                                                                   node);
    String actionScript = null;

    // TODO: This is a temporary way of setting blocking. Once the
    // AutoSubmitUtils makes it into the main branch, dump this and pass a
    // parameter to getSubmitScript(), which will tell it to look for and turn
    // on blocking. Similar code in LinkRenderer also has to change.
    if (action instanceof FireAction)
    {
      UIComponent component;
      component = node.getUIComponent();
      if ((component != null)
          && Boolean.TRUE.equals(component.getAttributes().get("blocking")))
        ((FireAction) action).setBlocking(true);
    }

    if (action != null)
      actionScript = action.getScript(context, node, Boolean.FALSE);

    Object chainedScript = BaseDesktopUtils.getChainedJS(onClick,
                                                      actionScript,
                                                      true);

    // Store away the script for next time
    context.setLocalProperty(_LOCAL_ON_CLICK_KEY, chainedScript);
    return chainedScript;
  }

  @Override
  protected Object getText(
    UIXRenderingContext context,
    UINode node
    )
  {
    Object text = node.getAttributeValue(context, TEXT_ATTR);

    // If the text is null, create a button with an empty label
    if (text == null)
      return _EMPTY_LABEL;

    return text;
  }

  /**
   * Override to provide defaults
   */
  @Override
  protected Object getStyleClass(
    UIXRenderingContext context,
    UINode           node
    )
  {
    // Buttons should not render the style class - style info
    // is passed directly to Tecate.
    return null;
  }

  // Returns the vertical alignment
  @Override
  protected Object getVAlign(UIXRenderingContext context, UINode node)
  {
    // This is kind of a hack to address bug #2047577
    // Instead of adding an attribute to control placement of the button,
    // we just force it to middle (which is what everybody wants anyway).
    // Mozilla renders "absmiddle" the same as middle
    // IE renders "absmiddle" better than middle.
    // For some reason, the default is "bottom".
    // Bug #2268480
    // Netscape screws up absmiddle in some cases, it usually does better
    // with just plain middle.
    // Bug #2471515
    // Mozilla likes Top alignment best, but middle is almost as good and
    // doesn't cause problems in table cells.
    // Bug #3426092
    // In screen reader mode, "middle" instead of "absmiddle" for vAlign to
    // comply with 4.01 HTML Spec.

    int application = context.getAgent().getAgentApplication();
    Object align;

    if ((application == TrinidadAgent.APPLICATION_NETSCAPE)
        || (application == TrinidadAgent.APPLICATION_GECKO)
        || (isScreenReaderMode(context)))
    {
      align = UIConstants.V_ALIGN_MIDDLE;
    }
    else
    {
      // default to absMiddle
      align  = UIConstants.V_ALIGN_ABSMIDDLE;
    }
    return align;
  }

  @Override
  protected void renderButtonAccessKey(
    UIXRenderingContext context,
    UINode           node
    )    throws IOException
  {
    Object accessKey = node.getAttributeValue(context, ACCESS_KEY_ATTR);
    if (accessKey != null)
    {
      renderAttribute(context, "accesskey", accessKey);
      // for IE, but not on a Mac, we want the accesskey to work on these
      // buttons like it does for a <button> html element, and that is
      // it activates the button rather than set focus like it does for a link.
      // We have javascript that handles this, but the javascript code must
      // know that the element is our uix button. So we set a flag attribute
      // for this purpose, called "adfbtn". We also set the rendering property
      // so that in renderImageContent we know that we rendered this special
      // attribute and then we will register the onkeydown event handler.
      if ((isIE(context)) &&
          !(context.getAgent().getAgentOS() == TrinidadAgent.OS_MACOS ))
      {
        context.getResponseWriter().writeAttribute("adfbtn", "t", null);
        setRenderingProperty(context, _ADF_BTN_ATTR, Boolean.TRUE);
      }
    }
  }

  /**
   * Creates the ImageProviderRequest to use when looking up the
   * button image.
   */
  abstract protected ImageProviderRequest createImageProviderRequest(
    UIXRenderingContext context,
    Object       name,
    Object       text,
    Color        foreground,
    Color        background,
    Color        surroundingColor,
    FontProxy    font,
    boolean      disabled,
    boolean      textAntialias,
    boolean      startRounded,
    boolean      endRounded,
    char         accessKey
    );

  /**
   * Returns the name of the server-side style for styling
   * button text.
   */
  protected String getServerStyleName(
    UIXRenderingContext context,
    UINode           node,
    boolean          disabled
    )
  {
    return disabled ? _SERVER_DISABLED_STYLE_NAME : _SERVER_STYLE_NAME;
  }

  // This method collects all of the UINode attributes which affect
  // image generation and makes a request to the ImageProvider to
  // generate the corresponding image.
  private ImageProviderResponse _getImage(
    UIXRenderingContext context,
    UINode           node
    )
  {
    // First, make sure we have an ImagePRovider
    ImageProvider provider = (ImageProvider)
      context.getProperty(TECATE_NAMESPACE, IMAGE_PROVIDER_PROPERTY);
    if (provider == null)
      return null;

    // Round up all of our properties
    boolean disabled = isDisabled(context, node);
    Object text = getText(context, node);
    boolean startRounded =
      getBooleanAttributeValue(context, node, _START_ROUNDED_ATTR, true);
    boolean endRounded =
      getBooleanAttributeValue(context, node, _END_ROUNDED_ATTR, true);
    Object name = getImageName(context, node);
    String styleName = getServerStyleName(context, node, disabled);
    Style style = getStyle(context, node, styleName);

    Style inlineStyle;
    Object inlineStyleObj = getInlineStyle(context, node);
    if (inlineStyleObj == null)
      inlineStyle = null;
    else
      inlineStyle = CSSUtils.parseStyle(inlineStyleObj.toString());

    Color background =
      getBackground(context, node, style, inlineStyle, styleName);
    Color foreground =
      getForeground(context, node, style, inlineStyle, styleName);
    Color surroundingColor = getSurroundingColor(context);
    char realAccessKey = _getAccessKey(context, node);

    char accessKey = supportsAccessKeys(context)
                       ? realAccessKey
                 : CompositeButtonKey.ACCESS_KEY_UNDEFINED;

    boolean textAntialias =
      isTextAntialiased(context, node, style, inlineStyle);
    int fontStyle =
      getFontStyle(context, node, style, inlineStyle, styleName);
    int fontSize =
      getFontSize(context, node, style, inlineStyle, styleName);
    Collection<Object> fontFamilies =
      getFontFamilies(context, node, style, styleName);
    Collection<Object> inlineFontFamilies =
      getFontFamilies(context, node, inlineStyle, null);
    MutableFontProxy font = new MutableFontProxy(null, fontStyle, fontSize);

    // Now that we've got all the properties, create an ImageProviderRequest
    // with the properties.
    ImageProviderRequest request = createImageProviderRequest(context,
                                                              name,
                                                              text,
                                                              foreground,
                                                              background,
                                                              surroundingColor,
                                                              font,
                                                              disabled,
                                                              textAntialias,
                                                              startRounded,
                                                              endRounded,
                                                              accessKey);
    // Do the look up
    return _getImage(context,
                     provider,
                     request,
                     font,
                     fontFamilies,
                     inlineFontFamilies);
  }

  // This overload of _getImage() loops through all inline font families
  // and style class font families trying to find a match.
  private ImageProviderResponse _getImage(
    UIXRenderingContext context,
    ImageProvider    provider,
    ImageProviderRequest request,
    MutableFontProxy font,
    Collection<Object> fontFamilies,
    Collection<Object> inlineFontFamilies
    )
  {
    ImageContext imageContext = context.getImageContext();
    ImageProviderResponse response = null;

    // First, loop through inline font families, if we've got any
    if (inlineFontFamilies != null)
    {
      Iterator<Object> inlineFontFamiliesIterator = inlineFontFamilies.iterator();
      while(inlineFontFamiliesIterator.hasNext())
      {
        String family = (String)inlineFontFamiliesIterator.next();
        font.setName(family);

        response = provider.getImage(imageContext, request);

        if (response != null)
          return response;
      }
    }

    // Next, try the font families defined by the style class
    if (fontFamilies != null)
    {
      Iterator<Object> fontFamiliesIterator = fontFamilies.iterator();
      while(fontFamiliesIterator.hasNext())
      {
        String family = (String)fontFamiliesIterator.next();
        font.setName(family);

        response = provider.getImage(imageContext, request);

        if (response != null)
          return response;
      }
    }

    // Finally, we match any font family by specifying a null font name
    font.setName(null);
    return provider.getImage(imageContext, request);
  }

  private char _getAccessKey(UIXRenderingContext context, UINode node)
  {
    char ch = BaseDesktopUtils.getCharacterAttr(context, node, ACCESS_KEY_ATTR);

    // In practice, these are the same, but let's be clean...
    if (ch == BaseDesktopUtils.CHAR_UNDEFINED)
      return CompositeButtonKey.ACCESS_KEY_UNDEFINED;

    return ch;
  }

  /**
    * If we've got a ClientAction, let it write its dependencies
    * This is normally done before rendering the link
    * @param context The rendering context
    * @param node the node to be rendered
    * @throws IOException
    */
  private static void _writeClientActionDependency(
    UIXRenderingContext context,
    UINode node
    ) throws IOException
  {
    ClientAction action = ClientActionUtils.getPrimaryClientAction(context,
                                                                   node);
    if (action != null)
    {
      action.writeDependencies(context, node);
    }
  }

  /**
   * Render onKeydown if adfbtn attribute has been rendered. This
   * is means accessKey has been rendered in IE
   * @param context the rendering context
   * @throws IOException
   */
  private  static void _renderOnKeyDownScript(
    UIXRenderingContext context
    ) throws IOException
  {
    if (Boolean.TRUE.equals(getRenderingProperty(context, _ADF_BTN_ATTR)))
      {
        renderScriptOnce(context,
                         "document.onkeydown=_monitor;",
                         _ON_KEY_DOWN_RENDERED);
      }
  }

  private boolean _useLinkAlternateContent(UIXRenderingContext context)
  {
    return  (!supportsAdvancedButtons(context) &&
      (getParentFormName(context) == null));
  }


  /**
   *  Returns the Renderer to use to render the alternate content.
   */
  private Renderer _getAlternateRenderer (UIXRenderingContext context)
  {
    // We need to use a special Renderer for Netscape when there
    // is no form, but other than that we delegate to a
    // laf.base.xhtml.ButtonRenderer instance.
    if (_useLinkAlternateContent(context))
    {
      return _LINK_ALTERNATE_RENDERER;
    }
    else
    {
      return  getAltRenderer();
    }
  }

  // Fallback Renderer that is used for Netscape when the button is
  // not contained within a form.  We use this instead of the standard
  // fallback, since Netscape does not support <input type="button">
  // outside of a form.
  /**
   * @deprecated This class comes from the old Java 1.2 UIX codebase and should not be used anymore.
   */
  @Deprecated
  private class AlternateRenderer implements Renderer
  {
   /**
    * Render as links
    * @param context The rendering context
    * @param node the node to be rendered
    * @throws IOException
    */
    public void render(
      UIXRenderingContext context,
      UINode node
      ) throws IOException
    {

        // If we've got a ClientAction, let it write its dependencies
        // before we start rendering the link
        _writeClientActionDependency(context,node);

        Object text        = getText(context, node);
        Object destination = getDestination(context, node);
        boolean disabled   = isDisabled(context, node);
        boolean isLink     = ((destination != null) && !disabled);
        String element     = (isLink) ? "a" : "span";

        ResponseWriter writer = context.getResponseWriter();
        writer.startElement(element, node.getUIComponent());
        renderAttributes(context, node);

        if (isLink)
        {
           renderEncodedActionURI(context, "href", destination);
           renderAttribute(context, node, "target", TARGET_FRAME_ATTR);

          // Don't render access key on Netscape... Netscape doesn't
          // support access keys - if this ever changes, it would
          // be confusing if we rendered the accessKey attr without
          // also underlining the access key in the button text.
          if (supportsAccessKeys(context))
          {
             renderButtonAccessKey(context, node);
          }
          renderAccessKeyText(context, node, text, 
                              SkinSelectors.AF_LINKACCESSKEY_STYLE_CLASS);
        }
        else
        {
          renderText(context, node);
        }
        writer.endElement(element);
      }
  }

  // =-=ags These should be public and on ImageConstants once we decide on
  //        the correct API.
  private static final AttributeKey _START_ROUNDED_ATTR =
                  getAttributeKey("_startRounded");
  private static final AttributeKey _END_ROUNDED_ATTR =
                  getAttributeKey("_endRounded");

  private static final String _EMPTY_LABEL = "";

  // Server style class names
  private static final String _SERVER_STYLE_NAME =
    "BLAFServerButtonText";
  private static final String _SERVER_DISABLED_STYLE_NAME =
    "BLAFServerButtonTextDisabled";

  // object indicating that there is no onClick handler
  private static final Object _ON_CLICK_NONE = new Object();

  // object used to store the local copy of the onClick handler
  private static final Object _LOCAL_ON_CLICK_KEY = new Object();

  private static final Object _ON_KEY_DOWN_RENDERED = new Object();
  private static final Object _ADF_BTN_ATTR = new Object();

  // Alternate renderer in screen reader mode
  private static final Renderer _ALTERNATE_RENDERER =
    new org.apache.myfaces.trinidadinternal.ui.laf.base.xhtml.ButtonRenderer();

  // since the class AlternateRenderer is not static and we use the methods
  // of the enclosing class - this class is not static in nature.
  private   final Renderer _LINK_ALTERNATE_RENDERER =
    new AlternateRenderer();

}


