/*
 * 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;

import com.google.common.base.Optional;
import com.google.inject.Module;
import com.google.template.soy.base.SoySyntaxException;
import com.google.template.soy.jssrc.SoyJsSrcOptions;
import com.google.template.soy.xliffmsgplugin.XliffMsgPluginModule;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.kohsuke.args4j.Option;

/**
 * Executable for compiling a set of Soy files into corresponding JS source files.
 *
 */
public final class SoyToJsSrcCompiler extends AbstractSoyCompiler {
  @Option(
    name = "--outputPathFormat",
    required = true,
    usage =
        "[Required] A format string that specifies how to build the path to each"
            + " output file. If not generating localized JS, then there will be one output"
            + " JS file (UTF-8) for each input Soy file. If generating localized JS, then"
            + " there will be one output JS file for each combination of input Soy file and"
            + " locale. The format string can include literal characters as well as the"
            + " placeholders {INPUT_PREFIX}, {INPUT_DIRECTORY}, {INPUT_FILE_NAME},"
            + " {INPUT_FILE_NAME_NO_EXT}, {LOCALE}, {LOCALE_LOWER_CASE}. Note"
            + " {LOCALE_LOWER_CASE} also turns dash into underscore, e.g. pt-BR becomes"
            + " pt_br."
  )
  protected String outputPathFormat;

  @Option(
    name = "--allowExternalCalls",
    usage =
        "Whether to allow external calls. New projects should set this to false, and"
            + " existing projects should remove existing external calls and then set this"
            + " to false. It will save you a lot of headaches. Currently defaults to true"
            + " for backward compatibility."
  )
  private boolean allowExternalCalls = true;

  @Option(
    name = "--syntaxVersion",
    usage = "User-declared syntax version for the Soy file bundle (e.g. 2.0, 2.3)."
  )
  private String syntaxVersion = "";

  @Option(
    name = "--shouldGenerateJsdoc",
    usage =
        "Whether we should generate JSDoc with type info for the Closure Compiler."
            + " Note the generated JSDoc does not have description text, only types for the"
            + " benefit of the Closure Compiler."
  )
  private boolean shouldGenerateJsdoc = false;

  @Option(
    name = "--shouldProvideRequireSoyNamespaces",
    usage =
        "When this option is used, each generated JS file will contain (a) one single"
            + " goog.provide statement for the corresponding Soy file's namespace and"
            + " (b) goog.require statements for the namespaces of the called templates."
  )
  private boolean shouldProvideRequireSoyNamespaces = false;

  @Option(
    name = "--shouldDeclareTopLevelNamespaces",
    usage =
        "[Only applicable when generating regular JS code to define namespaces (i.e."
            + " not generating goog.provide/goog.require).] When this option is set to"
            + " false, each generated JS file will not attempt to declare the top-level"
            + " name in its namespace, instead assuming the top-level name is already"
            + " declared in the global scope. E.g. for namespace aaa.bbb, the code will not"
            + " attempt to declare aaa, but will still define aaa.bbb if it's not already"
            + " defined."
  )
  private boolean shouldDeclareTopLevelNamespaces = true;

  @Option(
    name = "--locales",
    usage =
        "[Required for generating localized JS] Comma-delimited list of locales for"
            + " which to generate localized JS. There will be one output JS file for each"
            + " combination of input Soy file and locale.",
    handler = MainClassUtils.StringListOptionHandler.class
  )
  private List<String> locales = new ArrayList<>();

  @Option(
    name = "--messageFilePathFormat",
    usage =
        "[Required for generating localized JS] A format string that specifies how to"
            + " build the path to each translated messages file. The format string can"
            + " include literal characters as well as the placeholders {INPUT_PREFIX},"
            + " {LOCALE}, and {LOCALE_LOWER_CASE}. Note {LOCALE_LOWER_CASE} also turns dash"
            + " into underscore, e.g. pt-BR becomes pt_br. The format string must end with"
            + " an extension matching the message file format (case-insensitive)."
  )
  private String messageFilePathFormat = "";

  @Option(
    name = "--shouldGenerateGoogMsgDefs",
    usage =
        "When this option is used, all 'msg' blocks will be turned into goog.getMsg"
            + " definitions and corresponding usages. Must be used with either"
            + " --bidiGlobalDir, or --useGoogIsRtlForBidiGlobalDir, usually the latter."
            + " Also see --googMsgsAreExternal."
  )
  private boolean shouldGenerateGoogMsgDefs = false;

  @Option(
    name = "--googMsgsAreExternal",
    usage =
        "[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(...);\"."
            + "  [Explanation of true value]"
            + " Set this option to true if your project is having Closure Templates do"
            + " message extraction (e.g. with SoyMsgExtractor) and then having the Closure"
            + " Compiler do translated message insertion."
            + "  [Explanation of false value]"
            + " Set this option to false if your project is having the Closure Compiler do"
            + " all of its localization, i.e. if you want the Closure Compiler to do both"
            + " message extraction and translated message insertion. A significant drawback"
            + " to this setup is that, if your templates are used from both JS and Java, you"
            + " will end up with two separate and possibly different sets of translations"
            + " for your messages."
  )
  private boolean googMsgsAreExternal = false;

  @Option(
    name = "--bidiGlobalDir",
    usage =
        "The bidi global directionality (ltr=1, rtl=-1). Only applicable if your Soy"
            + " code uses bidi functions/directives. Also note that this flag is usually not"
            + " necessary if a message file is provided, because by default the bidi global"
            + " directionality is simply inferred from the message file."
  )
  private int bidiGlobalDir = 0;

  @Option(
    name = "--useGoogIsRtlForBidiGlobalDir",
    usage =
        "[Only applicable if both --shouldGenerateGoogMsgDefs and"
            +
            " --shouldProvideRequireSoyNamespaces"
            +
            " is true]"
            + " Whether to determine the bidi global direction at template runtime by"
            + " evaluating goog.i18n.bidi.IS_RTL. Do not combine with --bidiGlobalDir."
  )
  private boolean useGoogIsRtlForBidiGlobalDir = false;

  @Option(
    name = "--messagePluginModule",
    usage =
        "Specifies the full class name of a Guice module that binds a SoyMsgPlugin."
            + " If not specified, the default is"
            + " com.google.template.soy.xliffmsgplugin.XliffMsgPluginModule, which binds"
            + " the XliffMsgPlugin."
  )
  private Module messagePluginModule = new XliffMsgPluginModule();

  /**
   * Compiles a set of Soy files into corresponding JS source files.
   *
   * @param args Should contain command-line flags and the list of paths to the Soy files.
   * @throws IOException If there are problems reading the input files or writing the output file.
   * @throws SoySyntaxException If a syntax error is detected.
   */
  public static void main(final String[] args) throws IOException, SoySyntaxException {
    new SoyToJsSrcCompiler().runMain(args);
  }

  @Override
  void validateFlags() {
    if (outputPathFormat.isEmpty()) {
      exitWithError("Must provide the output path format.");
    }
  }

  @Override
  Optional<Module> msgPluginModule() {
    return Optional.of(messagePluginModule);
  }

  @Override
  void compile(SoyFileSet.Builder sfsBuilder) throws IOException {
    if (!syntaxVersion.isEmpty()) {
      sfsBuilder.setDeclaredSyntaxVersionName(syntaxVersion);
    }
    sfsBuilder.setAllowExternalCalls(allowExternalCalls);

    SoyFileSet sfs = sfsBuilder.build();

    // Create SoyJsSrcOptions.
    SoyJsSrcOptions jsSrcOptions = new SoyJsSrcOptions();
    jsSrcOptions.setShouldGenerateJsdoc(shouldGenerateJsdoc);
    jsSrcOptions.setShouldProvideRequireSoyNamespaces(shouldProvideRequireSoyNamespaces);
    jsSrcOptions.setShouldDeclareTopLevelNamespaces(shouldDeclareTopLevelNamespaces);
    jsSrcOptions.setShouldGenerateGoogMsgDefs(shouldGenerateGoogMsgDefs);
    jsSrcOptions.setGoogMsgsAreExternal(googMsgsAreExternal);
    jsSrcOptions.setBidiGlobalDir(bidiGlobalDir);
    jsSrcOptions.setUseGoogIsRtlForBidiGlobalDir(useGoogIsRtlForBidiGlobalDir);

    // Compile.
    boolean generateLocalizedJs = !locales.isEmpty();
    if (generateLocalizedJs) {
      sfs.compileToJsSrcFiles(
          outputPathFormat, inputPrefix, jsSrcOptions, locales, messageFilePathFormat);
    } else {
      sfs.compileToJsSrcFiles(outputPathFormat, inputPrefix, jsSrcOptions, locales, null);
    }
  }
}
