/*
 * Copyright 2008 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.template.soy.jssrc;

import com.google.common.base.Preconditions;


/**
 * Compilation options for the JS Src output target (backend).
 *
 */
public final class SoyJsSrcOptions implements Cloneable {

  /** Whether to allow deprecated syntax (semi backwards compatible mode). */
  private boolean shouldAllowDeprecatedSyntax;

  /** Whether we should generate JSDoc with type info for the Closure Compiler. */
  private boolean shouldGenerateJsdoc;

  /** Whether we should generate code to provide/require Soy namespaces. */
  private boolean shouldProvideRequireSoyNamespaces;

  /** Whether we should generate code to provide/require template JS functions. */
  private boolean shouldProvideRequireJsFunctions;

  /** Whether we should generate code to provide both Soy namespaces and JS functions. */
  private boolean shouldProvideBothSoyNamespacesAndJsFunctions;

  /** Whether we should generate code to declare the top level namespace. */
  private boolean shouldDeclareTopLevelNamespaces;

  /** Whether we should generate code to declare goog.modules. */
  private boolean shouldGenerateGoogModules;

  /** Whether we should generate Closure Library message definitions (i.e. goog.getMsg). */
  private boolean shouldGenerateGoogMsgDefs;

  /** Whether the Closure Library messages are external, i.e. "MSG_EXTERNAL_[soyGeneratedMsgId]". */
  private boolean googMsgsAreExternal;

  /**
   * The bidi global directionality as a static value, 1: ltr, -1: rtl, 0: unspecified. If 0, and
   * useGoogIsRtlForBidiGlobalDir is false, the bidi global directionality will actually be inferred
   * from the message bundle locale. This must not be the case when shouldGenerateGoogMsgDefs is
   * true, but is the recommended mode of operation otherwise.
   */
  private int bidiGlobalDir;

  /**
   * Whether to determine the bidi global direction at template runtime by evaluating
   * goog.i18n.bidi.IS_RTL. May only be true when both shouldGenerateGoogMsgDefs and either
   * shouldProvideRequireSoyNamespaces or shouldProvideRequireJsFunctions is true.
   */
  private boolean useGoogIsRtlForBidiGlobalDir;


  public SoyJsSrcOptions() {
    shouldAllowDeprecatedSyntax = false;
    shouldGenerateJsdoc = false;
    shouldProvideRequireSoyNamespaces = false;
    shouldProvideRequireJsFunctions = false;
    shouldProvideBothSoyNamespacesAndJsFunctions = false;
    shouldDeclareTopLevelNamespaces = true;
    shouldGenerateGoogMsgDefs = false;
    shouldGenerateGoogModules = false;
    googMsgsAreExternal = false;
    bidiGlobalDir = 0;
    useGoogIsRtlForBidiGlobalDir = false;
  }

  private SoyJsSrcOptions(SoyJsSrcOptions orig) {
    this.shouldAllowDeprecatedSyntax = orig.shouldAllowDeprecatedSyntax;
    this.shouldGenerateJsdoc = orig.shouldGenerateJsdoc;
    this.shouldProvideRequireSoyNamespaces = orig.shouldProvideRequireSoyNamespaces;
    this.shouldProvideRequireJsFunctions = orig.shouldProvideRequireJsFunctions;
    this.shouldProvideBothSoyNamespacesAndJsFunctions =
        orig.shouldProvideBothSoyNamespacesAndJsFunctions;
    this.shouldDeclareTopLevelNamespaces = orig.shouldDeclareTopLevelNamespaces;
    this.shouldGenerateGoogModules = orig.shouldGenerateGoogModules;
    this.shouldGenerateGoogMsgDefs = orig.shouldGenerateGoogMsgDefs;
    this.googMsgsAreExternal = orig.googMsgsAreExternal;
    this.bidiGlobalDir = orig.bidiGlobalDir;
    this.useGoogIsRtlForBidiGlobalDir = orig.useGoogIsRtlForBidiGlobalDir;
  }

  /**
   * Sets whether to allow deprecated syntax (semi backwards compatible mode).
   * @param shouldAllowDeprecatedSyntax The value to set.
   */
  // TODO SOON: Deprecate. (Use setDeclaredSyntaxVersionName() on SoyFileSet or SoyGeneralOptions.)
  public void setShouldAllowDeprecatedSyntax(boolean shouldAllowDeprecatedSyntax) {
    this.shouldAllowDeprecatedSyntax = shouldAllowDeprecatedSyntax;
  }


  /**
   * Returns whether we're set to allow deprecated syntax (semi backwards compatible mode).
   * @deprecated Use {@code SoyGeneralOptions.getDeclaredSyntaxVersion()}.
   */
  @Deprecated
  public boolean shouldAllowDeprecatedSyntax() {
    return shouldAllowDeprecatedSyntax;
  }


  /**
   * Sets whether we should generate JSDoc with type info for the Closure Compiler.
   * @param shouldGenerateJsdoc The value to set.
   */
  public void setShouldGenerateJsdoc(boolean shouldGenerateJsdoc) {
    this.shouldGenerateJsdoc = shouldGenerateJsdoc;
  }


  /** Returns whether we should generate JSDoc with type info for the Closure Compiler. */
  public boolean shouldGenerateJsdoc() {
    return shouldGenerateJsdoc;
  }


  /**
   * Sets whether we should generate code to provide/require Soy namespaces.
   * @param shouldProvideRequireSoyNamespaces The value to set.
   */
  public void setShouldProvideRequireSoyNamespaces(
      boolean shouldProvideRequireSoyNamespaces) {
    // TODO(b/24275162) Replace these variables with a single Enum
    this.shouldProvideRequireSoyNamespaces = shouldProvideRequireSoyNamespaces;
    Preconditions.checkState(
        !(this.shouldProvideRequireSoyNamespaces && this.shouldProvideRequireJsFunctions),
        "Must not enable both shouldProvideRequireSoyNamespaces and" +
        " shouldProvideRequireJsFunctions.");
    Preconditions.checkState(
        !(!this.shouldDeclareTopLevelNamespaces && this.shouldProvideRequireSoyNamespaces),
        "Turning off shouldDeclareTopLevelNamespaces has no meaning when" +
        " shouldProvideRequireSoyNamespaces is enabled.");
  }


  /** Returns whether we're set to generate code to provide/require Soy namespaces. */
  public boolean shouldProvideRequireSoyNamespaces() {
    return shouldProvideRequireSoyNamespaces;
  }


  /**
   * Sets whether we should generate code to provide/require template JS functions.
   * @param shouldProvideRequireJsFunctions The value to set.
   */
  public void setShouldProvideRequireJsFunctions(
      boolean shouldProvideRequireJsFunctions) {
    // TODO(b/24275162) Replace these variables with a single Enum
    this.shouldProvideRequireJsFunctions = shouldProvideRequireJsFunctions;
    Preconditions.checkState(
        !(this.shouldProvideRequireSoyNamespaces && this.shouldProvideRequireJsFunctions),
        "Must not enable both shouldProvideRequireSoyNamespaces and" +
        " shouldProvideRequireJsFunctions.");
    Preconditions.checkState(
        !(!this.shouldDeclareTopLevelNamespaces && this.shouldProvideRequireJsFunctions),
        "Turning off shouldDeclareTopLevelNamespaces has no meaning when" +
        " shouldProvideRequireJsFunctions is enabled.");
  }


  /** Returns whether we're set to generate code to provide/require template JS functions. */
  public boolean shouldProvideRequireJsFunctions() {
    return shouldProvideRequireJsFunctions;
  }


  /**
   * Sets whether we should generate code to provide both Soy namespaces and JS functions.
   * @param shouldProvideBothSoyNamespacesAndJsFunctions The value to set.
   */
  public void setShouldProvideBothSoyNamespacesAndJsFunctions(
      boolean shouldProvideBothSoyNamespacesAndJsFunctions) {
    // TODO(b/24275162) Replace these variables with a single Enum
    this.shouldProvideBothSoyNamespacesAndJsFunctions =
        shouldProvideBothSoyNamespacesAndJsFunctions;
    if (shouldProvideBothSoyNamespacesAndJsFunctions) {
      Preconditions.checkState(
          this.shouldProvideRequireSoyNamespaces || this.shouldProvideRequireJsFunctions,
          "Must only enable shouldProvideBothSoyNamespacesAndJsFunctions after enabling either" +
          " shouldProvideRequireSoyNamespaces or shouldProvideRequireJsFunctions.");
    }
  }


  /** Returns whether we should generate code to provide both Soy namespaces and JS functions. */
  public boolean shouldProvideBothSoyNamespacesAndJsFunctions() {
    return shouldProvideBothSoyNamespacesAndJsFunctions;
  }


  /**
   * Sets whether we should generate code to declare the top level namespace.
   * @param shouldDeclareTopLevelNamespaces The value to set.
   */
  public void setShouldDeclareTopLevelNamespaces(
      boolean shouldDeclareTopLevelNamespaces) {
    // TODO(b/24275162) Replace these variables with a single Enum
    this.shouldDeclareTopLevelNamespaces = shouldDeclareTopLevelNamespaces;
    Preconditions.checkState(
        !(!this.shouldDeclareTopLevelNamespaces && this.shouldProvideRequireSoyNamespaces),
        "Turning off shouldDeclareTopLevelNamespaces has no meaning when" +
        " shouldProvideRequireSoyNamespaces is enabled.");
    Preconditions.checkState(
        !(!this.shouldDeclareTopLevelNamespaces && this.shouldProvideRequireJsFunctions),
        "Turning off shouldDeclareTopLevelNamespaces has no meaning when" +
        " shouldProvideRequireJsFunctions is enabled.");
  }


  /** Returns whether we should attempt to declare the top level namespace. */
  public boolean shouldDeclareTopLevelNamespaces() {
    return shouldDeclareTopLevelNamespaces;
  }


  /**
   * Sets whether goog.modules should be generated.
   * @param shouldGenerateGoogModules The value to set.
   */
  public void setShouldGenerateGoogModules(boolean shouldGenerateGoogModules) {
    // TODO(b/24275162) Replace these variables with a single Enum
    this.shouldGenerateGoogModules = shouldGenerateGoogModules;
    if (shouldGenerateGoogModules) {
      Preconditions.checkState(
          !shouldDeclareTopLevelNamespaces
              && !shouldProvideRequireSoyNamespaces
              && !shouldProvideRequireJsFunctions
              && !shouldProvideBothSoyNamespacesAndJsFunctions,
          "If generating goog.modules, shouldDeclareTopLevelNamespaces, "
              + "shouldProvideRequireSoyNamespaces, shouldProvideRequireJsFunctions and "
              + "shouldProvideBothSoyNamespacesAndJsFunctions should not be enabled.");

    }
  }


  /** Returns whether goog.modules should be generated. */
  public boolean shouldGenerateGoogModules() {
    return shouldGenerateGoogModules;
  }


  /**
   * Sets whether we should generate Closure Library message definitions (i.e. goog.getMsg).
   * @param shouldGenerateGoogMsgDefs The value to set.
   */
  public void setShouldGenerateGoogMsgDefs(boolean shouldGenerateGoogMsgDefs) {
    this.shouldGenerateGoogMsgDefs = shouldGenerateGoogMsgDefs;
  }


  /** Returns whether we should generate Closure Library message definitions (i.e. goog.getMsg). */
  public boolean shouldGenerateGoogMsgDefs() {
    return shouldGenerateGoogMsgDefs;
  }


  /**
   * Sets whether the generated Closure Library message definitions are for external messages
   * (only applicable if shouldGenerateGoogMsgDefs is true).
   *
   * If this option is true, then we generate
   *     var MSG_EXTERNAL_[soyGeneratedMsgId] = goog.getMsg(...);
   * If this option is false, then we generate
   *     var MSG_UNNAMED_[uniquefier] = goog.getMsg(...);
   *
   * @param googMsgsAreExternal The value to set.
   */
  public void setGoogMsgsAreExternal(boolean googMsgsAreExternal) {
    this.googMsgsAreExternal = googMsgsAreExternal;
  }


  /**
   * Returns whether the generated Closure Library message definitions are for external messages
   * (only applicable if shouldGenerateGoogMsgDefs is true).
   *
   * If this option is true, then we generate
   *     var MSG_EXTERNAL_[soyGeneratedMsgId] = goog.getMsg(...);
   * If this option is false, then we generate
   *     var MSG_UNNAMED_[uniquefier] = goog.getMsg(...);
   */
  public boolean googMsgsAreExternal() {
    return googMsgsAreExternal;
  }


  /**
   * Sets the bidi global directionality to a static value, 1: ltr, -1: rtl, 0: unspecified. If 0,
   * and useGoogIsRtlForBidiGlobalDir is false, the bidi global directionality will actually be
   * inferred from the message bundle locale. This is the recommended mode of operation when
   * shouldGenerateGoogMsgDefs is false. When shouldGenerateGoogMsgDefs is true, the bidi global
   * direction can not be left unspecified, but the recommended way of doing so is via
   * setUseGoogIsRtlForBidiGlobalDir(true). Thus, whether shouldGenerateGoogMsgDefs is true or not,
   * THERE IS USUALLY NO NEED TO USE THIS METHOD!
   *
   * @param bidiGlobalDir 1: ltr, -1: rtl, 0: unspecified. Checks that no other value is used.
   */
  public void setBidiGlobalDir(int bidiGlobalDir) {
    Preconditions.checkArgument(
        bidiGlobalDir >= -1 && bidiGlobalDir <= 1,
        "bidiGlobalDir must be 1 for LTR, or -1 for RTL (or 0 to leave unspecified).");
    Preconditions.checkState(
        !useGoogIsRtlForBidiGlobalDir || bidiGlobalDir == 0,
        "Must not specify both bidiGlobalDir and useGoogIsRtlForBidiGlobalDir.");
    this.bidiGlobalDir = bidiGlobalDir;
  }


  /**
   * Returns the static bidi global directionality, 1: ltr, -1: rtl, 0: unspecified.
   */
  public int getBidiGlobalDir() {
    return bidiGlobalDir;
  }


  /**
   * Sets the Javascript code snippet that will evaluate at template runtime to a boolean value
   * indicating whether the bidi global direction is rtl. Can only be used when
   * shouldGenerateGoogMsgDefs is true.
   *
   * @param useGoogIsRtlForBidiGlobalDir Whether to determine the bidi global direction at template
   *     runtime by evaluating goog.i18n.bidi.IS_RTL.
   */
  public void setUseGoogIsRtlForBidiGlobalDir(boolean useGoogIsRtlForBidiGlobalDir) {
    Preconditions.checkState(
        !useGoogIsRtlForBidiGlobalDir || shouldGenerateGoogMsgDefs,
        "Do not specify useGoogIsRtlForBidiGlobalDir without shouldGenerateGoogMsgDefs.");
    Preconditions.checkState(
        !useGoogIsRtlForBidiGlobalDir
        || shouldProvideRequireSoyNamespaces
        || shouldProvideRequireJsFunctions
        || shouldGenerateGoogModules,
        "Do not specify useGoogIsRtlForBidiGlobalDir without one of"
        + " shouldProvideRequireSoyNamespaces, shouldProvideRequireJsFunctions or "
        + " shouldGenerateGoogModules.");
    Preconditions.checkState(
        !useGoogIsRtlForBidiGlobalDir || bidiGlobalDir == 0,
        "Must not specify both bidiGlobalDir and useGoogIsRtlForBidiGlobalDir.");
    this.useGoogIsRtlForBidiGlobalDir = useGoogIsRtlForBidiGlobalDir;
  }


  /**
   * Returns whether to determine the bidi global direction at template runtime by evaluating
   * goog.i18n.bidi.IS_RTL. May only be true when shouldGenerateGoogMsgDefs is true.
   */
  public boolean getUseGoogIsRtlForBidiGlobalDir() {
    return useGoogIsRtlForBidiGlobalDir;
  }


  @Override public final SoyJsSrcOptions clone() {
    return new SoyJsSrcOptions(this);
  }
}
