/*
 * Copyright 2010 Google Inc.
 *
 * Licensed 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 com.google.gwt.i18n.shared;

import com.google.gwt.i18n.client.HasDirection.Direction;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.safehtml.shared.annotations.IsSafeHtml;
import com.google.gwt.safehtml.shared.annotations.SuppressIsSafeHtmlCastCheck;

/**
 * Utility class for formatting text for display in a potentially
 * opposite-direction context without garbling. The direction of the context is
 * set at formatter creation and the direction of the text can be either
 * estimated or passed in when known. Provides the following functionality:
 * <p>
 * 1. BiDi Wrapping: When text in one language is mixed into a document in
 * another, opposite-direction language, e.g. when an English business name is
 * embedded in a Hebrew web page, both the inserted string and the text
 * following it may be displayed incorrectly unless the inserted string is
 * explicitly separated from the surrounding text in a "wrapper" that declares
 * its direction at the start and then resets it back at the end. This wrapping
 * can be done in HTML mark-up (e.g. a 'span dir=rtl' tag) or - only in contexts
 * where mark-up cannot be used - in Unicode BiDi formatting codes (LRE|RLE and
 * PDF). Optionally, the mark-up can be inserted even when the direction is the
 * same, in order to keep the DOM structure more stable. Providing such wrapping
 * services is the basic purpose of the BiDi formatter.
 * <p>
 * 2. Direction estimation: How does one know whether a string about to be
 * inserted into surrounding text has the same direction? Well, in many cases,
 * one knows that this must be the case when writing the code doing the
 * insertion, e.g. when a localized message is inserted into a localized page.
 * In such cases there is no need to involve the BiDi formatter at all. In some
 * other cases, it need not be the same as the context, but is either constant
 * (e.g. urls are always LTR) or otherwise known. In the remaining cases, e.g.
 * when the string is user-entered or comes from a database, the language of the
 * string (and thus its direction) is not known a priori, and must be estimated
 * at run-time. The BiDi formatter can do this automatically.
 * <p>
 * 3. Escaping: When wrapping plain text - i.e. text that is not already HTML or
 * HTML-escaped - in HTML mark-up, the text must first be HTML-escaped to
 * prevent XSS attacks and other nasty business. This of course is always true,
 * but the escaping can not be done after the string has already been wrapped in
 * mark-up, so the BiDi formatter also serves as a last chance and includes
 * escaping services.
 * <p>
 * Thus, in a single call, the formatter will escape the input string as
 * specified, determine its direction, and wrap it as necessary. It is then up
 * to the caller to insert the return value in the output.
 *
 */
public class BidiFormatter extends BidiFormatterBase {

  static class Factory extends BidiFormatterBase.Factory<BidiFormatter> {
    @Override
    public BidiFormatter createInstance(Direction contextDir,
        boolean alwaysSpan) {
      return new BidiFormatter(contextDir, alwaysSpan);
    }
  }

  private static Factory factory = new Factory();

  /**
   * Factory for creating an instance of BidiFormatter given the context
   * direction. The default behavior of {@link #spanWrap} and its variations is
   * set to avoid span wrapping unless it's necessary ('dir' attribute needs to
   * be set).
   *
   * @param rtlContext Whether the context direction is RTL.
   *          In one simple use case, the context direction would simply be the
   *          locale direction, which can be retrieved using
   *          {@code LocaleInfo.getCurrentLocale().isRTL()}
   */
  public static BidiFormatter getInstance(boolean rtlContext) {
    return getInstance(rtlContext, false);
  }

  /**
   * Factory for creating an instance of BidiFormatter given the context
   * direction and the desired span wrapping behavior (see below).
   *
   * @param rtlContext Whether the context direction is RTL. See an example of
   *          a simple use case at {@link #getInstance(boolean)}
   * @param alwaysSpan Whether {@link #spanWrap} (and its variations) should
   *          always use a 'span' tag, even when the input direction is neutral
   *          or matches the context, so that the DOM structure of the output
   *          does not depend on the combination of directions
   */
  public static BidiFormatter getInstance(boolean rtlContext,
      boolean alwaysSpan) {
    return new BidiFormatter(rtlContext ? Direction.RTL : Direction.LTR,
        alwaysSpan);
  }

  /**
   * Factory for creating an instance of BidiFormatter given the context
   * direction. The default behavior of {@link #spanWrap} and its variations is
   * set to avoid span wrapping unless it's necessary ('dir' attribute needs to
   * be set).
   *
   * @param contextDir The context direction. See an example of a simple use
   *          case at {@link #getInstance(boolean)}. Note: Direction.DEFAULT
   *          indicates unknown context direction. Try not to use it, since it
   *          is impossible to reset the direction back to the context when it
   *          is unknown
   */
  public static BidiFormatter getInstance(Direction contextDir) {
    return getInstance(contextDir, false);
  }

  /**
   * Factory for creating an instance of BidiFormatter given the context
   * direction and the desired span wrapping behavior (see below).
   *
   * @param contextDir The context direction. See an example of a simple use
   *          case at {@link #getInstance(boolean)}. Note: Direction.DEFAULT
   *          indicates unknown context direction. Try not to use it, since it
   *          is impossible to reset the direction back to the context when it
   *          is unknown
   * @param alwaysSpan Whether {@link #spanWrap} (and its variations) should
   *          always use a 'span' tag, even when the input direction is neutral
   *          or matches the context, so that the DOM structure of the output
   *          does not depend on the combination of directions
   */
  public static BidiFormatter getInstance(Direction contextDir,
      boolean alwaysSpan) {
    return factory.getInstance(contextDir, alwaysSpan);
  }

  /**
   * Factory for creating an instance of BidiFormatter whose context direction
   * matches the current locale's direction. The default behavior of {@link
   * #spanWrap} and its variations is set to avoid span wrapping unless it's
   * necessary ('dir' attribute needs to be set).
   */
  public static BidiFormatter getInstanceForCurrentLocale() {
    return getInstanceForCurrentLocale(false);
  }

  /**
   * Factory for creating an instance of BidiFormatter whose context direction
   * matches the current locale's direction, and given the desired span wrapping
   * behavior (see below).
   *
   * @param alwaysSpan Whether {@link #spanWrap} (and its variations) should
   *          always use a 'span' tag, even when the input direction is neutral
   *          or matches the context, so that the DOM structure of the output
   *          does not depend on the combination of directions
   */
  public static BidiFormatter getInstanceForCurrentLocale(boolean alwaysSpan) {
    return getInstance(LocaleInfo.getCurrentLocale().isRTL(), alwaysSpan);
  }

  /**
   * @param contextDir The context direction
   * @param alwaysSpan Whether {@link #spanWrap} (and its variations) should
   *          always use a 'span' tag, even when the input direction is neutral
   *          or matches the context, so that the DOM structure of the output
   *          does not depend on the combination of directions
   */
  private BidiFormatter(Direction contextDir, boolean alwaysSpan) {
    super(contextDir, alwaysSpan);
  }

  /**
   * Like {@link #dirAttr(String, boolean)}, but assumes {@code isHtml} is
   * false.
   *
   * @param str String whose direction is to be estimated
   * @return "dir=rtl" for RTL text in non-RTL context; "dir=ltr" for LTR text
   *         in non-LTR context; else, the empty string.
   */
  public String dirAttr(String str) {
    return dirAttr(str, false);
  }

  /**
   * Returns "dir=ltr" or "dir=rtl", depending on {@code str}'s estimated
   * direction, if it is not the same as the context direction. Otherwise,
   * returns the empty string.
   *
   * @param str String whose direction is to be estimated
   * @param isHtml Whether {@code str} is HTML / HTML-escaped
   * @return "dir=rtl" for RTL text in non-RTL context; "dir=ltr" for LTR text
   *         in non-LTR context; else, the empty string.
   */
  public String dirAttr(String str, boolean isHtml) {
    return dirAttrBase(str, isHtml);
  }

  /**
   * Returns "left" for RTL context direction. Otherwise (LTR or default /
   * unknown context direction) returns "right".
   */
  public String endEdge() {
    return endEdgeBase();
  }

  /**
   * Returns "dir=ltr" or "dir=rtl", depending on the given direction, if it is
   * not the same as the context direction. Otherwise, returns the empty string.
   *
   * @param dir Given direction
   * @return "dir=rtl" for RTL text in non-RTL context; "dir=ltr" for LTR text
   *         in non-LTR context; else, the empty string.
   */
  public String knownDirAttr(Direction dir) {
    return knownDirAttrBase(dir);
  }

  /**
   * Returns the Unicode BiDi mark matching the context direction (LRM for LTR
   * context direction, RLM for RTL context direction), or the empty string for
   * default / unknown context direction.
   */
  public String mark() {
    return markBase();
  }

  /**
   * Like {@link #markAfter(String, boolean)}, but assumes {@code isHtml} is
   * false.
   *
   * @param str String after which the mark may need to appear
   * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
   *         else, the empty string.
   */
  public String markAfter(String str) {
    return markAfter(str, false);
  }

  /**
   * Returns a Unicode BiDi mark matching the context direction (LRM or RLM) if
   * either the direction or the exit direction of {@code str} is opposite to
   * the context direction. Otherwise returns the empty string.
   *
   * @param str String after which the mark may need to appear
   * @param isHtml Whether {@code str} is HTML / HTML-escaped
   * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
   *         else, the empty string.
   */
  public String markAfter(String str, boolean isHtml) {
    return markAfterBase(str, isHtml);
  }

  /**
   * Like {@link #spanWrap(String, boolean, boolean)}, but assumes {@code
   * isHtml} is false and {@code dirReset} is true.
   *
   * @param str The input string
   * @return Input string after applying the above processing.
   */
  @SuppressIsSafeHtmlCastCheck
  @IsSafeHtml
  public String spanWrap(String str) {
    return spanWrap(str, false, true);
  }

  /**
   * Like {@link #spanWrap(String, boolean, boolean)}, but assumes {@code
   * dirReset} is true.
   *
   * @param str The input string
   * @param isHtml Whether {@code str} is HTML / HTML-escaped
   * @return Input string after applying the above processing.
   */
  @IsSafeHtml
  public String spanWrap(@IsSafeHtml String str, boolean isHtml) {
    return spanWrap(str, isHtml, true);
  }

  /**
   * Formats a string of unknown direction for use in HTML output of the context
   * direction, so an opposite-direction string is neither garbled nor garbles
   * what follows it.
   * <p>
   * The algorithm: estimates the direction of input argument {@code str}. In
   * case its direction doesn't match the context direction, wraps it with a
   * 'span' tag and adds a "dir" attribute (either 'dir=rtl' or 'dir=ltr').
   * <p>
   * If {@code setAlwaysSpan(true)} was used, the input is always wrapped with
   * 'span', skipping just the dir attribute when it's not needed.
   * <p>
   * If {@code dirReset}, and if the overall direction or the exit direction of
   * {@code str} are opposite to the context direction, a trailing unicode BiDi
   * mark matching the context direction is appended (LRM or RLM).
   * <p>
   * If !{@code isHtml}, HTML-escapes {@code str} regardless of wrapping.
   *
   * @param str The input string
   * @param isHtml Whether {@code str} is HTML / HTML-escaped
   * @param dirReset Whether to append a trailing unicode bidi mark matching the
   *          context direction, when needed, to prevent the possible garbling
   *          of whatever may follow {@code str}
   * @return Input string after applying the above processing.
   */
  @IsSafeHtml
  public String spanWrap(@IsSafeHtml String str, boolean isHtml, boolean dirReset) {
    return spanWrapBase(str, isHtml, dirReset);
  }

  /**
   * Like
   * {@link #spanWrapWithKnownDir(com.google.gwt.i18n.client.HasDirection.Direction, String, boolean, boolean)},
   * but assumes {@code isHtml} is false and {@code dirReset} is true.
   *
   * @param dir {@code str}'s direction
   * @param str The input string
   * @return Input string after applying the above processing.
   */
  @SuppressIsSafeHtmlCastCheck
  @IsSafeHtml
  public String spanWrapWithKnownDir(Direction dir, String str) {
    return spanWrapWithKnownDir(dir, str, false, true);
  }

  /**
   * Like
   * {@link #spanWrapWithKnownDir(com.google.gwt.i18n.client.HasDirection.Direction, String, boolean, boolean)},
   * but assumes {@code dirReset} is true.
   *
   * @param dir {@code str}'s direction
   * @param str The input string
   * @param isHtml Whether {@code str} is HTML / HTML-escaped
   * @return Input string after applying the above processing.
   */
  @IsSafeHtml
  public String spanWrapWithKnownDir(Direction dir, @IsSafeHtml String str, boolean isHtml) {
    return spanWrapWithKnownDir(dir, str, isHtml, true);
  }

  /**
   * Formats a string of given direction for use in HTML output of the context
   * direction, so an opposite-direction string is neither garbled nor garbles
   * what follows it.
   * <p>
   * The algorithm: estimates the direction of input argument {@code str}. In
   * case its direction doesn't match the context direction, wraps it with a
   * 'span' tag and adds a "dir" attribute (either 'dir=rtl' or 'dir=ltr').
   * <p>
   * If {@code setAlwaysSpan(true)} was used, the input is always wrapped with
   * 'span', skipping just the dir attribute when it's not needed.
   * <p>
   * If {@code dirReset}, and if the overall direction or the exit direction of
   * {@code str} are opposite to the context direction, a trailing unicode BiDi
   * mark matching the context direction is appended (LRM or RLM).
   * <p>
   * If !{@code isHtml}, HTML-escapes {@code str} regardless of wrapping.
   *
   * @param dir {@code str}'s direction
   * @param str The input string
   * @param isHtml Whether {@code str} is HTML / HTML-escaped
   * @param dirReset Whether to append a trailing unicode bidi mark matching the
   *          context direction, when needed, to prevent the possible garbling
   *          of whatever may follow {@code str}
   * @return Input string after applying the above processing.
   */
  @IsSafeHtml
  public String spanWrapWithKnownDir(
      Direction dir, @IsSafeHtml String str, boolean isHtml, boolean dirReset) {
    return spanWrapWithKnownDirBase(dir, str, isHtml, dirReset);
  }

  /**
   * Returns "right" for RTL context direction. Otherwise (LTR or default /
   * unknown context direction) returns "left".
   */
  public String startEdge() {
    return startEdgeBase();
  }

  /**
   * Like {@link #unicodeWrap(String, boolean, boolean)}, but assumes {@code
   * isHtml} is false and {@code dirReset} is true.
   *
   * @param str The input string
   * @return Input string after applying the above processing.
   */
  public String unicodeWrap(String str) {
    return unicodeWrap(str, false, true);
  }

  /**
   * Like {@link #unicodeWrap(String, boolean, boolean)}, but assumes {@code
   * dirReset} is true.
   *
   * @param str The input string
   * @param isHtml Whether {@code str} is HTML / HTML-escaped
   * @return Input string after applying the above processing.
   */
  public String unicodeWrap(String str, boolean isHtml) {
    return unicodeWrap(str, isHtml, true);
  }

  /**
   * Formats a string of unknown direction for use in plain-text output of the
   * context direction, so an opposite-direction string is neither garbled nor
   * garbles what follows it. As opposed to {@link #spanWrap}, this makes use of
   * Unicode BiDi formatting characters. In HTML, its *only* valid use is inside
   * of elements that do not allow mark-up, e.g. an 'option' tag.
   * <p>
   * The algorithm: estimates the direction of input argument {@code str}. In
   * case it doesn't match the context direction, wraps it with Unicode BiDi
   * formatting characters: RLE+{@code str}+PDF for RTL text, or LRE+ {@code
   * str}+PDF for LTR text.
   * <p>
   * If {@code opt_dirReset}, and if the overall direction or the exit direction
   * of {@code str} are opposite to the context direction, a trailing unicode
   * BiDi mark matching the context direction is appended (LRM or RLM).
   * <p>
   * Does *not* do HTML-escaping regardless of the value of {@code isHtml}.
   *
   * @param str The input string
   * @param isHtml Whether {@code str} is HTML / HTML-escaped
   * @param dirReset Whether to append a trailing unicode bidi mark matching the
   *          context direction, when needed, to prevent the possible garbling
   *          of whatever may follow {@code str}
   * @return Input string after applying the above processing.
   */
  public String unicodeWrap(String str, boolean isHtml, boolean dirReset) {
    return unicodeWrapBase(str, isHtml, dirReset);
  }

  /**
   * Like
   * {@link #unicodeWrapWithKnownDir(com.google.gwt.i18n.client.HasDirection.Direction, String, boolean, boolean)},
   * but assumes {@code isHtml} is false and {@code dirReset} is true.
   *
   * @param dir {@code str}'s direction
   * @param str The input string
   * @return Input string after applying the above processing.
   */
  public String unicodeWrapWithKnownDir(Direction dir, String str) {
    return unicodeWrapWithKnownDir(dir, str, false, true);
  }

  /**
   * Like
   * {@link #unicodeWrapWithKnownDir(com.google.gwt.i18n.client.HasDirection.Direction, String, boolean, boolean)},
   * but assumes {@code dirReset} is true.
   *
   * @param dir {@code str}'s direction
   * @param str The input string
   * @param isHtml Whether {@code str} is HTML / HTML-escaped
   * @return Input string after applying the above processing.
   */
  public String unicodeWrapWithKnownDir(Direction dir, String str,
      boolean isHtml) {
    return unicodeWrapWithKnownDir(dir, str, isHtml, true);
  }

  /**
   * Formats a string of given direction for use in plain-text output of the
   * context direction, so an opposite-direction string is neither garbled nor
   * garbles what follows it. As opposed to {@link #spanWrapWithKnownDir}, this
   * makes use of unicode BiDi formatting characters. In HTML, its *only* valid
   * use is inside of elements that do not allow mark-up, e.g. an 'option' tag.
   * <p>
   * The algorithm: estimates the direction of input argument {@code str}. In
   * case it doesn't match the context direction, wraps it with Unicode BiDi
   * formatting characters: RLE+{@code str}+PDF for RTL text, or LRE+ {@code
   * str}+PDF for LTR text.
   * <p>
   * If {@code opt_dirReset}, and if the overall direction or the exit direction
   * of {@code str} are opposite to the context direction, a trailing unicode
   * BiDi mark matching the context direction is appended (LRM or RLM).
   * <p>
   * Does *not* do HTML-escaping regardless of the value of {@code isHtml}.
   *
   * @param dir {@code str}'s direction
   * @param str The input string
   * @param isHtml Whether {@code str} is HTML / HTML-escaped
   * @param dirReset Whether to append a trailing unicode bidi mark matching the
   *          context direction, when needed, to prevent the possible garbling
   *          of whatever may follow {@code str}
   * @return Input string after applying the above processing.
   */
  public String unicodeWrapWithKnownDir(Direction dir, String str,
      boolean isHtml, boolean dirReset) {
    return unicodeWrapWithKnownDirBase(dir, str, isHtml, dirReset);
  }
}
