/*
 *  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.awt.Color;

import java.io.IOException;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

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

import org.apache.myfaces.trinidad.util.ArrayMap;

import org.apache.myfaces.trinidadinternal.agent.TrinidadAgent;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.render.CoreRenderer;
import org.apache.myfaces.trinidad.skin.Icon;

/**
 * Utilities for miscellaneous HTML output.
 * @todo RENAME CLASS
 */
public class OutputUtils
{
  /**
   * Parse a styleclass string, which may have spaces, into a list
   * of style classes.   Returns null if it's just a single class.
   */
  public static List<String> parseStyleClassList(String styleClass)
  {
    if (styleClass == null)
      return null;

    // If there's no spaces, it's just a single class - return
    // AdamWiner: should we care about all Unicode whitspace?
    // This will catch 99.9% of cases, and this code needs to be
    // fast
    int spaceIndex = styleClass.indexOf(' ');
    if (spaceIndex < 0)
      return null;

    // Iterate through the string and build up the split list
    // AdamWiner: Regex split() would be a lot less code, but
    // it doesn't automatically trim empty strings.
    int prevSpaceIndex = 0;
    List<String> styleClasses = new ArrayList<String>();
    do
    {
      if (spaceIndex > prevSpaceIndex)
        styleClasses.add(styleClass.substring(prevSpaceIndex, spaceIndex));
      prevSpaceIndex = spaceIndex + 1;
      spaceIndex = styleClass.indexOf(' ', prevSpaceIndex);
    }
    while (spaceIndex >= 0);

    if (prevSpaceIndex < styleClass.length())
      styleClasses.add(styleClass.substring(prevSpaceIndex));

    return styleClasses;
  }


  /**
   * Gets the character encoding of the output.
   */
  public static String getOutputEncoding(FacesContext context)
  {
    return context.getResponseWriter().getCharacterEncoding();
  }

  public static void renderHiddenField(
    FacesContext context,
    String       name,
    String       value) throws IOException
  {
    ResponseWriter rw = context.getResponseWriter();
    rw.startElement("input", null);
    rw.writeAttribute("type", "hidden", null);
    rw.writeAttribute("name", name, null);
    rw.writeAttribute("value", value, null);
    rw.endElement("input");
  }

  public static void renderLayoutTableAttributes(
    FacesContext        context,
    RenderingContext    arc,
    Object              cellspacing,
    Object              tableWidth
    ) throws IOException
  {
    renderLayoutTableAttributes(context, arc, "0", cellspacing, tableWidth);
  }


  public static void renderLayoutTableAttributes(
    FacesContext        context,
    RenderingContext arc,
    Object              cellpadding,
    Object              cellspacing,
    Object              tableWidth
    ) throws IOException
  {
    renderLayoutTableAttributes(context, arc, cellpadding, cellspacing, "0",
                                tableWidth);
  }

  /**
   * Render layout table attributes. Includes appropriate attributes for layout table accessibility.
   */
  public static void renderLayoutTableAttributes(
    FacesContext        context,
    RenderingContext    arc,
    Object              cellpadding,
    Object              cellspacing,
    Object              border,
    Object              tableWidth
    ) throws IOException
  {
    _renderTableAttributes(context, arc, cellpadding, cellspacing, border, tableWidth, 
                           "" /* summary */ );

    //if in screen reader mode, also indicate that this table is not a data table
    if (CoreRenderer.isScreenReaderMode(arc))
    {
      ResponseWriter writer = context.getResponseWriter();
      writer.writeAttribute("datatable", "0", null);
    }
  }

  /**
   * @deprecated replaced by @link #renderDataTableAttributes(FacesContext, RenderingContext, Object, Object, Object, Object, Object)
   */
  public static void renderLayoutTableAttributes(
    FacesContext        context,
    RenderingContext    arc,
    Object              cellpadding,
    Object              cellspacing,
    Object              border,
    Object              tableWidth,
    Object              summary
    ) throws IOException
  {
    renderDataTableAttributes(context, arc, cellpadding, cellspacing, border, tableWidth, summary);
  }

  /**
   * Render data table attributes. Includes appropriate attributes for data table accessibility.
   */
  public static void renderDataTableAttributes(
    FacesContext        context,
    RenderingContext arc,
    Object              cellpadding,
    Object              cellspacing,
    Object              border,
    Object              tableWidth,
    Object              summary
    ) throws IOException
  {
    _renderTableAttributes(context, arc, cellpadding, cellspacing, border, tableWidth, summary);
  }

  /**
   * Generic method for rendering common table attributes. The desire here is that renderers don't 
   * call this method, but call in to either the renderLayoutTableAttributes or 
   * renderDataTableAttributes depending on the type of table.
   */
  private static void _renderTableAttributes(
    FacesContext        context,
    RenderingContext    arc,
    Object              cellpadding,
    Object              cellspacing,
    Object              border,
    Object              tableWidth,
    Object              summary
    ) throws IOException
  {
    ResponseWriter writer = context.getResponseWriter();
    writer.writeAttribute("cellpadding", cellpadding, null);
    writer.writeAttribute("cellspacing", cellspacing, null);
    writer.writeAttribute("border", border, null);
    writer.writeAttribute("width", tableWidth, null);

    if (!XhtmlRenderer.isInaccessibleMode(arc))
    {
      writer.writeAttribute("summary", summary, null);
    }
  }

  /**
  * Renders only the alt attribute
  * if that can be used as a tooltip on an image.
  * Otherwise it renders both the alt and the title attributes.
  */
  public static void renderAltAndTooltipForImage(
     FacesContext        context,
     RenderingContext afc,
     Object              textValue
     ) throws IOException
  {
    if (textValue == null)
      return;

    ResponseWriter writer = context.getResponseWriter();
    boolean wroteTitle = false;

    if (!supportsAltRendersTooltipOnImage(afc))
    {
      if (!"".equals(textValue))
      {
        writer.writeAttribute("title", textValue, null);
        wroteTitle = true;
      }
    }

    // only write out both title and alt if
    // we really need both
    if (!wroteTitle || !XhtmlRenderer.isInaccessibleMode(afc))
    {
      writer.writeAttribute("alt", textValue, null);
    }
  }

  /**
   * Returns true if the agent supports alt as a tooltip on images
   */
  public static boolean supportsAltRendersTooltipOnImage(
    RenderingContext     afc
    )
  {
    return Boolean.TRUE.equals(afc.getAgent().getCapabilities().get(
                        TrinidadAgent.CAP_ALT_RENDERS_TOOLTIP_ON_IMAGE));
  }

  /**
   * Returns the valign vAlign value for aligning image icons
   * vertically with text on the same line.
   */
  public static String getMiddleIconAlignment(
    RenderingContext arc)
  {
    // =-= AEW I haven't been able to find an image alignment
    // that works well for all browsers.  "absmiddle" looks
    // great in IE, but that's a nonstandard hack. "middle"
    // should work OK everywhere, but looks terrible in both
    // IE and Netscape (but OK in Mozilla) "top"'s OK in Netscape.
    // For now, "top" in Netscape, "absmiddle" everywhere else
    
    // =-=AEW Except, no more netscape support!
    
    // Previously we used "middle" for all other browsers except
    // for Safari, where "absmiddle" was required for reasonable
    // results.  However, as far as I can tell, for images which
    // are evenly padded on top/bottom, absmiddle also looks
    // best on IE and Mozilla.  So, let's use absmiddle for
    // these browsers too.
    // =-= MLL Update: to address Bug # 3426092, alignment has been set to
    // "middle" to comply with HTML 4.01 Transitional Spec.
    
    return XhtmlConstants.V_ALIGN_MIDDLE;
  }

  /**
   * Renders the specified Icon with the provided attributes.
   */
  public static void renderIcon(
    FacesContext        context,
    RenderingContext arc,
    Icon                icon,
    Object              shortDesc,
    Object              align
    ) throws IOException
  {
    renderIcon(context, arc, icon, shortDesc, align, false);
  }

  /**
   * Renders the specified Icon with the provided attributes.
   */
  public static void renderIcon(
    FacesContext        context,
    RenderingContext arc,
    Icon                icon,
    Object              shortDesc,
    Object              align,
    boolean             embedded
    ) throws IOException
  {
    if ((icon == null) || icon.isNull())
      return;

    Map<String, Object> attrs = null;

    if ((shortDesc != null)  ||
        (align != null)      ||
         embedded)
    {
      attrs = new ArrayMap<String, Object>(3);
      attrs.put(Icon.SHORT_DESC_KEY, shortDesc);
      attrs.put(Icon.ALIGN_KEY, align);
      attrs.put(Icon.EMBEDDED_KEY, Boolean.valueOf(embedded));
    }

    icon.renderIcon(context, arc, attrs);
  }

  /**
   * Renders an image tag.
   */
  static public void renderImage(
    FacesContext     context,
    RenderingContext arc,
    Object           absoluteUri,
    Object           width,
    Object           height,
    Object           id,
    Object           altText
    ) throws IOException
  {
    renderImage(context, arc, absoluteUri,
                width, height, id, altText, null);
  }

  static public void renderImage(
    FacesContext     context,
    RenderingContext arc,
    Object           absoluteUri,
    Object           width,
    Object           height,
    Object           id,
    Object           altText,
    UIComponent      comp
    ) throws IOException
  {
    renderImage(context, arc, absoluteUri,
                width, height, id, altText, comp, null, null);
  }

  static public void renderImage(
    FacesContext     context,
    RenderingContext arc,
    Object           absoluteUri,
    Object           width,
    Object           height,
    Object           id,
    Object           altText,
    UIComponent      comp,
    String           inlineStyle
    ) throws IOException
  {
    renderImage(context, arc, absoluteUri,
                width, height, id, altText, comp, inlineStyle, null);
  }
    
  static public void renderImage(
    FacesContext     context,
    RenderingContext arc,
    Object           absoluteUri,
    Object           width,
    Object           height,
    Object           id,
    Object           altText,
    UIComponent      comp,
    String           inlineStyle,
    String           styleClass
    ) throws IOException
  {
    if (absoluteUri == null)
      return;

    ResponseWriter writer = context.getResponseWriter();

    writer.startElement("img", comp);
    writer.writeAttribute("id", id, null);
    // Run through the ExternalContext resource URL encoder
    absoluteUri = context.getExternalContext().encodeResourceURL(
      absoluteUri.toString());
    writer.writeURIAttribute("src", absoluteUri, null);

    renderAltAndTooltipForImage(context, arc, altText);

    if (width != null)
    {
      writer.writeAttribute("width", width, null);
    }

    if (height != null)
    {
      writer.writeAttribute("height", height, null);
    }

    if (inlineStyle != null)
    {
      writer.writeAttribute("style", inlineStyle, null);
    }
    
    if (styleClass != null)
    {
      CoreRenderer.renderStyleClass(context, arc, styleClass);
    }

    writer.endElement("img");
  }

  /**
   * @todo Add real mechanism for getting the background color
   * if needed.
   */
  static public Color getBackgroundColor(RenderingContext arc)
  {
    return Color.WHITE;
  }
}
