/*
 * 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.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.safehtml.shared.annotations.SuppressIsSafeHtmlCastCheck;

import java.util.HashMap;

/**
 * A wrapper to {@link BidiFormatter} whose methods return {@code SafeHtml}
 * instead of {@code String}.
 */
public class SafeHtmlBidiFormatter extends BidiFormatterBase {

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

  private static Factory factory = new Factory();

  private static HashMap<String, SafeHtml> cachedSafeHtmlValues = null;

  /**
   * Factory for creating an instance of SafeHtmlBidiFormatter 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 SafeHtmlBidiFormatter getInstance(boolean rtlContext) {
    return getInstance(rtlContext, false);
  }

  /**
   * Factory for creating an instance of SafeHtmlBidiFormatter 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 SafeHtmlBidiFormatter getInstance(boolean rtlContext,
      boolean alwaysSpan) {
    return getInstance(rtlContext ? Direction.RTL : Direction.LTR, alwaysSpan);
  }

  /**
   * Factory for creating an instance of SafeHtmlBidiFormatter 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 SafeHtmlBidiFormatter getInstance(Direction contextDir) {
    return getInstance(contextDir, false);
  }

  /**
   * Factory for creating an instance of SafeHtmlBidiFormatter 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 SafeHtmlBidiFormatter getInstance(Direction contextDir,
      boolean alwaysSpan) {
    return factory.getInstance(contextDir, alwaysSpan);
  }

  /**
   * Factory for creating an instance of SafeHtmlBidiFormatter 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 SafeHtmlBidiFormatter getInstanceForCurrentLocale() {
    return getInstanceForCurrentLocale(false);
  }

  /**
   * Factory for creating an instance of SafeHtmlBidiFormatter 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 SafeHtmlBidiFormatter 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 SafeHtmlBidiFormatter(Direction contextDir, boolean alwaysSpan) {
    super(contextDir, alwaysSpan);
  }

  /**
   * @see BidiFormatter#dirAttr(String, boolean)
   *
   * @param html Html 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 SafeHtml dirAttr(SafeHtml html) {
    return cachedSafeHtml(dirAttrBase(html.asString(), true));
  }

  /**
   * @see BidiFormatter#dirAttr
   *
   * @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 SafeHtml dirAttr(String str) {
    return cachedSafeHtml(dirAttrBase(str, false));
  }

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

  /**
   * @see BidiFormatterBase#estimateDirection(String, boolean)
   *
   * @param html Html whose direction is to be estimated
   * @return {@code html}'s estimated overall direction
   */
  public Direction estimateDirection(SafeHtml html) {
    return estimateDirection(html.asString(), true);
  }

  /**
   * @see BidiFormatter#knownDirAttr
   *
   * @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 SafeHtml knownDirAttr(Direction dir) {
    return cachedSafeHtml(knownDirAttrBase(dir));
  }

  /**
   * @see BidiFormatter#mark
   */
  public SafeHtml mark() {
    return cachedSafeHtml(markBase());
  }

  /**
   * @see BidiFormatter#markAfter
   *
   * @param html Html 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 SafeHtml markAfter(SafeHtml html) {
    return cachedSafeHtml(markAfterBase(html.asString(), true));
  }

  /**
   * @see BidiFormatter#markAfter
   *
   * @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 SafeHtml markAfter(String str) {
    return cachedSafeHtml(markAfterBase(str, false));
  }

  /**
   * @see BidiFormatter#spanWrap(String, boolean)
   *
   * @param html The input html
   * @return Input html after applying the above processing.
   */
  public SafeHtml spanWrap(SafeHtml html) {
    return spanWrap(html, true);
  }

  /**
   * @see BidiFormatter#spanWrap(String, boolean, boolean)
   *
   * @param html The input html
   * @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 html}
   * @return Input html after applying the above processing.
   */
  public SafeHtml spanWrap(SafeHtml html, boolean dirReset) {
    return SafeHtmlUtils.fromTrustedString(
        spanWrapBase(html.asString(), true, dirReset));
  }

  /**
   * @see BidiFormatter#spanWrap(String)
   *
   * @param str The input string
   * @return Input string after applying the above processing.
   */
  public SafeHtml spanWrap(String str) {
    return spanWrap(str, true);
  }

  /**
   * @see BidiFormatter#spanWrap(String, boolean, boolean)
   *
   * @param str The input string
   * @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.
   */
  @SuppressIsSafeHtmlCastCheck
  public SafeHtml spanWrap(String str, boolean dirReset) {
    // This is safe since spanWrapBase escapes plain-text input.
    return SafeHtmlUtils.fromTrustedString(spanWrapBase(str, false, dirReset));
  }

  /**
   * @see BidiFormatter#spanWrapWithKnownDir(
   * com.google.gwt.i18n.client.HasDirection.Direction, String, boolean)
   *
   * @param dir {@code str}'s direction
   * @param html The input html
   * @return Input html after applying the above processing.
   */
  public SafeHtml spanWrapWithKnownDir(Direction dir, SafeHtml html) {
    return spanWrapWithKnownDir(dir, html, true);
  }

  /**
   * @see BidiFormatter#spanWrapWithKnownDir(
   * com.google.gwt.i18n.client.HasDirection.Direction, String, boolean, boolean)
   *
   * @param dir {@code html}'s direction
   * @param html The input html
   * @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 html}
   * @return Input html after applying the above processing.
   */
  public SafeHtml spanWrapWithKnownDir(Direction dir, SafeHtml html,
      boolean dirReset) {
    return SafeHtmlUtils.fromTrustedString(
        spanWrapWithKnownDirBase(dir, html.asString(), true, dirReset));
  }

  /**
   * @see BidiFormatter#spanWrapWithKnownDir(
   * com.google.gwt.i18n.client.HasDirection.Direction, String)
   *
   * @param dir {@code str}'s direction
   * @param str The input string
   * @return Input string after applying the above processing.
   */
  public SafeHtml spanWrapWithKnownDir(Direction dir, String str) {
    return spanWrapWithKnownDir(dir, str, true);
  }

  /**
   * @see BidiFormatter#spanWrapWithKnownDir(
   * com.google.gwt.i18n.client.HasDirection.Direction, String, boolean, boolean)
   *
   * @param dir {@code str}'s direction
   * @param str The input string
   * @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.
   */
  @SuppressIsSafeHtmlCastCheck
  public SafeHtml spanWrapWithKnownDir(Direction dir, String str, boolean dirReset) {
    // This is safe since spanWrapWithKnownDirBase escapes plain-text input.
    return SafeHtmlUtils.fromTrustedString(
        spanWrapWithKnownDirBase(dir, str, false, dirReset));
  }

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

  /**
   * @see BidiFormatter#unicodeWrap(String, boolean)
   *
   * @param html The input html
   * @return Input html after applying the above processing.
   */
  public SafeHtml unicodeWrap(SafeHtml html) {
    return unicodeWrap(html, true);
  }

  /**
   * @see BidiFormatter#unicodeWrap(String, boolean, boolean)
   *
   * @param html The input html
   * @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 html}
   * @return Input html after applying the above processing.
   */
  public SafeHtml unicodeWrap(SafeHtml html, boolean dirReset) {
    return SafeHtmlUtils.fromTrustedString(
        unicodeWrapBase(html.asString(), true, dirReset));
  }

  /**
   * @see BidiFormatter#unicodeWrap(String)
   *
   * @param str The input string
   * @return Input string after applying the above processing.
   */
  public SafeHtml unicodeWrap(String str) {
    return unicodeWrap(str, true);
  }

  /**
   * @see BidiFormatter#unicodeWrap(String, boolean, boolean)
   *
   * @param str The input string
   * @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 SafeHtml unicodeWrap(String str, boolean dirReset) {
    // unicodeWrapBase does not HTML-escape, so its return value is not trusted.
    return SafeHtmlUtils.fromString(unicodeWrapBase(str, false, dirReset));
  }

  /**
   * @see BidiFormatter#unicodeWrapWithKnownDir(
   * com.google.gwt.i18n.client.HasDirection.Direction, String, boolean)
   *
   * @param dir {@code html}'s direction
   * @param html The input html
   * @return Input html after applying the above processing.
   */
  public SafeHtml unicodeWrapWithKnownDir(Direction dir, SafeHtml html) {
    return unicodeWrapWithKnownDir(dir, html, true);
  }

  /**
   * @see BidiFormatter#unicodeWrapWithKnownDir(
   * com.google.gwt.i18n.client.HasDirection.Direction, String, boolean, boolean)
   *
   * @param dir {@code html}'s direction
   * @param html The input html
   * @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 html}
   * @return Input html after applying the above processing.
   */
  public SafeHtml unicodeWrapWithKnownDir(Direction dir, SafeHtml html,
      boolean dirReset) {
    return SafeHtmlUtils.fromTrustedString(
        unicodeWrapWithKnownDirBase(dir, html.asString(), true, dirReset));
  }

  /**
   * @see BidiFormatter#unicodeWrapWithKnownDir(
   * com.google.gwt.i18n.client.HasDirection.Direction, String)
   *
   * @param dir {@code str}'s direction
   * @param str The input string
   * @return Input string after applying the above processing.
   */
  public SafeHtml unicodeWrapWithKnownDir(Direction dir, String str) {
    return unicodeWrapWithKnownDir(dir, str, true);
  }

  /**
   * @see BidiFormatter#unicodeWrapWithKnownDir(
   * com.google.gwt.i18n.client.HasDirection.Direction, String, boolean, boolean)
   *
   * @param dir {@code str}'s direction
   * @param str The input string
   * @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 SafeHtml unicodeWrapWithKnownDir(Direction dir, String str,
      boolean dirReset) {
    /*
     * unicodeWrapWithKnownDirBase does not HTML-escape, so its return value is
     * not trusted.
     */
    return SafeHtmlUtils.fromString(
        unicodeWrapWithKnownDirBase(dir, str, false, dirReset));
  }

  /**
   * Converts an input string into a SafeHtml. Input String must be safe (see
   * {@link com.google.gwt.safehtml.shared.SafeHtml}).
   * Implementation: first, tries to find the input in the static map,
   * {@link #cachedSafeHtmlValues}. If not found, creates a SafeHtml instance
   * for the string and adds it to the map.
   *
   * @param str String to search for. Must be safe (see
   * {@link com.google.gwt.safehtml.shared.SafeHtml}).
   *
   * @return Input as SafeHtml
   */
  private SafeHtml cachedSafeHtml(String str) {
    if (cachedSafeHtmlValues == null) {
      cachedSafeHtmlValues = new HashMap<String, SafeHtml>();
    }
    SafeHtml entry = cachedSafeHtmlValues.get(str);
    if (entry == null) {
      entry = SafeHtmlUtils.fromString(str);
      cachedSafeHtmlValues.put(str, entry);
    }
    return entry;
  }
}
