/*
 * Copyright 2009 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.msgs;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.io.Files;
import com.google.common.io.Resources;
import com.google.template.soy.base.internal.BaseUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.regex.Pattern;

import javax.inject.Inject;

/**
 * Handler for writing {@code SoyMsgBundle}s to file format and for creating {@code SoyMsgBundle}s
 * from files or resources.
 *
 * <p> Uses a {@code SoyMsgPlugin} to do the actual generation of the output data and the actual
 * parsing of the input data. The {@code SoyMsgPlugin} implements the specific message file format.
 *
 */
public class SoyMsgBundleHandler {


  /**
   * Options for generating an output messages file.
   *
   * This same class is used for both extracted messages files (source messages to be translated)
   * and translated messages files. Not all options will apply to both types of output files, and
   * not all options will apply to all message plugins.
   */
  public static class OutputFileOptions {

    /** The source locale string in the messages file. */
    private String sourceLocaleString;

    /** The target locale string in the messages file. */
    private String targetLocaleString;

    /**
     * This constructor sets default values for the source locale string and target locale string.
     * The source locale string defaults to "en" and the target locale string defaults to null.
     */
    public OutputFileOptions() {
      sourceLocaleString = "en";
      targetLocaleString = null;
    }

    /**
     * Sets the source locale string for an output messages file.
     * @param sourceLocaleString The source locale string.
     */
    public void setSourceLocaleString(String sourceLocaleString) {
      this.sourceLocaleString = sourceLocaleString;
    }

    /**
     * Returns the source locale string.
     */
    public String getSourceLocaleString() {
      return sourceLocaleString;
    }

    /**
     * Sets the target locale string for an output messages file.
     * @param targetLocaleString The target locale string.
     */
    public void setTargetLocaleString(String targetLocaleString) {
      this.targetLocaleString = targetLocaleString;
    }

    /**
     * Returns the target locale string.
     */
    public String getTargetLocaleString() {
      return targetLocaleString;
    }
  }


  /** For backwards-compatibility checking of file names that start with "en". */
  private static final Pattern FIRST_WORD_IS_EN_PATTERN = Pattern.compile("^en[^A-Za-z].*");


  private final SoyMsgPlugin msgPlugin;


  @Inject
  public SoyMsgBundleHandler(SoyMsgPlugin msgPlugin) {
    this.msgPlugin = msgPlugin;
  }


  /**
   * Reads a translated messages file and creates a SoyMsgBundle.
   *
   * @param inputFile The input file to read from.
   * @return The message bundle created from the messages file.
   * @throws IOException If there's an error while accessing the file.
   * @throws SoyMsgException If there's an error while processing the messages.
   */
  public SoyMsgBundle createFromFile(File inputFile) throws IOException, SoyMsgException {

    // TODO: This is for backwards-compatibility. Figure out how to get rid of this.
    // We special-case English locales because they often don't have translated files and falling
    // back to the Soy source should be fine.
    if (!inputFile.exists() && FIRST_WORD_IS_EN_PATTERN.matcher(inputFile.getName()).matches()) {
      return SoyMsgBundle.EMPTY;
    }

    try {
      String inputFileContent = Files.toString(inputFile, UTF_8);
      return msgPlugin.parseTranslatedMsgsFile(inputFileContent);

    } catch (SoyMsgException sme) {
      sme.setFileOrResourceName(inputFile.toString());
      throw sme;
    }
  }


  /**
   * Reads a translated messages resource and creates a SoyMsgBundle.
   *
   * @param inputResource The resource to read from.
   * @return The message bundle created from the messages resource.
   * @throws IOException If there's an error while accessing the resource.
   * @throws SoyMsgException If there's an error while processing the messages.
   */
  public SoyMsgBundle createFromResource(URL inputResource) throws IOException, SoyMsgException {

    try {
      String inputFileContent = Resources.toString(inputResource, UTF_8);
      return msgPlugin.parseTranslatedMsgsFile(inputFileContent);

    } catch (SoyMsgException sme) {
      sme.setFileOrResourceName(inputResource.toString());
      throw sme;
    }
  }


  // -----------------------------------------------------------------------------------------------
  // Soy internal methods.


  /**
   * Generates an extracted messages file (source messages to be translated) from a given message
   * bundle, and writes it to file.
   *
   * <p> Important: Do not use outside of Soy code (treat as superpackage-private).
   *
   * @param msgBundle The message bundle to write to file.
   * @param options The options for generating the output extracted messages file (depending on the
   *     message plugin being used, none or some of the options may be applicable).
   * @param outputFile The output file to write to.
   * @throws SoyMsgException If there's an error while processing the messages.
   * @throws IOException If there's an error while accessing the file.
   */
  public void writeToExtractedMsgsFile(
      SoyMsgBundle msgBundle, OutputFileOptions options, File outputFile)
      throws IOException, SoyMsgException {

    CharSequence cs = msgPlugin.generateExtractedMsgsFile(msgBundle, options);
    BaseUtils.ensureDirsExistInPath(outputFile.getPath());
    Files.write(cs, outputFile, UTF_8);
  }


  /**
   * Generates an translated messages file (source messages to be translated) from a given message
   * bundle, and writes it to file.
   *
   * <p> Important: Do not use outside of Soy code (treat as superpackage-private).
   *
   * @param msgBundle The message bundle to write to file.
   * @param options The options for generating the output translated messages file (depending on the
   *     message plugin being used, none or some of the options may be applicable).
   * @param outputFile The output file to write to.
   * @throws IOException If there's an error while accessing the file.
   * @throws SoyMsgException If there's an error while processing the messages.
   */
  public void writeToTranslatedMsgsFile(
      SoyMsgBundle msgBundle, OutputFileOptions options, File outputFile)
      throws IOException, SoyMsgException {

    if (! (msgPlugin instanceof SoyBidirectionalMsgPlugin)) {
      throw new SoyMsgException(
          "writeToTranslatedMsgsFile() only works if using a SoyBidirectionalMsgPlugin.");
    }
    SoyBidirectionalMsgPlugin msgPluginCast = (SoyBidirectionalMsgPlugin) msgPlugin;

    CharSequence cs = msgPluginCast.generateTranslatedMsgsFile(msgBundle, options);
    BaseUtils.ensureDirsExistInPath(outputFile.getPath());
    Files.write(cs, outputFile, UTF_8);
  }

}
