// Copyright 2016 Google Inc. All Rights Reserved.
package com.google.copybara;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.copybara.config.Config;
import com.google.copybara.config.ConfigValidationException;
import com.google.copybara.config.YamlParser;
import com.google.copybara.config.skylark.SkylarkParser;
import com.google.copybara.folder.FolderDestination;
import com.google.copybara.folder.FolderDestinationOptions;
import com.google.copybara.git.GerritDestination;
import com.google.copybara.git.GerritOptions;
import com.google.copybara.git.GitDestination;
import com.google.copybara.git.GitOptions;
import com.google.copybara.git.GitOrigin;
import com.google.copybara.transform.MoveFiles;
import com.google.copybara.transform.Replace;
import com.google.copybara.transform.Reverse;
import com.google.copybara.transform.Sequence;
import com.google.copybara.transform.TransformOptions;
import com.google.copybara.transform.ValidationException;
import com.google.copybara.util.console.Console;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import javax.annotation.Nullable;
import org.yaml.snakeyaml.TypeDescription;

/**
 * Copybara tool main class.
 *
 * <p>Executes Copybara workflows independently from the environment that they are invoked from
 * (command-line, service).
 */
public class Copybara {

  protected static final ImmutableSet<Class<?>> BASIC_MODULES = ImmutableSet.<Class<?>>of(
      FolderDestination.Module.class);

  private final SkylarkParser skylarkParser;

  /**
   * Delete this method once imported and fixed
   */
  @Deprecated
  public Copybara() {
    skylarkParser = null;
  }

  public Copybara(SkylarkParser skylarkParser) {
    this.skylarkParser = Preconditions.checkNotNull(skylarkParser);
  }

  protected List<Option> getAllOptions() {
    return ImmutableList.of(
        new FolderDestinationOptions(),
        new GitOptions(),
        new GerritOptions(),
        new TransformOptions(),
        new WorkflowOptions());
  }

  protected Iterable<TypeDescription> getYamlTypeDescriptions() {
    return ImmutableList.of(
        // Transformations
        YamlParser.docTypeDescription(Replace.Yaml.class),
        YamlParser.docTypeDescription(Reverse.Yaml.class),
        YamlParser.docTypeDescription(MoveFiles.Yaml.class),
        YamlParser.docTypeDescription(Sequence.Yaml.class),
        // Origins
        YamlParser.docTypeDescription(GitOrigin.Yaml.class),
        // Destinations
        YamlParser.docTypeDescription(GerritDestination.Yaml.class),
        YamlParser.docTypeDescription(GitDestination.Yaml.class),
        YamlParser.docTypeDescription(FolderDestination.Yaml.class));
  }

  /**
   * Returns a short String representing the version of the binary
   */
  protected String getVersion() {
    return "Unknown version";
  }

  /**
   * Returns a String (can be multiline) representing all the information about who and when the
   * Copybara was built.
   */
  protected String getBinaryInfo() {
    return "Unknown version";
  }

  public void run(Options options, String configContents, String workflowName,
      Path baseWorkdir, @Nullable String sourceRef)
      throws RepoException, ValidationException, IOException, EnvironmentException {
    options.get(WorkflowOptions.class).setWorkflowName(workflowName);
    boolean skylark = options.get(GeneralOptions.class).isSkylark();
    Config config = parseConfig(skylark, configContents, options);
    config.getActiveWorkflow().run(baseWorkdir, sourceRef);
  }

  public void validate(Options options, String configContent, String yamlConfigContent,
      String workflowName)
      throws RepoException, ValidationException, IOException, EnvironmentException {
    options.get(WorkflowOptions.class).setWorkflowName(workflowName);
    boolean skylark = options.get(GeneralOptions.class).isSkylark();
    Config config = parseConfig(skylark, configContent, options);
    Config yamlConfig = parseConfig(/*skylark=*/false, yamlConfigContent, options);
    if (!config.equals(yamlConfig)) {
      Console console = options.get(GeneralOptions.class).console();
      console.error("Skylark config file and Yaml one are not equivalent:\n"
          + "YAML: " + yamlConfig + "\n"
          + "BZL : " + config);
      throw new ValidationException("Error validating configuration");
    }
  }

  private Config parseConfig(boolean skylark, String configContents, Options options)
      throws IOException, ConfigValidationException, EnvironmentException {
    if (skylark) {
      return skylarkParser.loadConfig(configContents, options);
    }
    return loadYamlConfig(configContents, options);
  }

  private Config loadYamlConfig(String configContents, Options options)
      throws IOException, ConfigValidationException, EnvironmentException {
    return new YamlParser(getYamlTypeDescriptions()).parseConfig(configContents, options);
  }
}

@@ -3432,39 +3432,53 @@
 wName);%0A    
-boolean skylark
+GeneralOptions generalOptions
  = options.g
@@ -3493,32 +3493,70 @@
 alOptions.class)
+;%0A    boolean skylark = generalOptions
 .isSkylark();%0A  
@@ -3616,24 +3616,695 @@
 , options);%0A
+    Console console = generalOptions.console();%0A    console.progress(%22Validating configuration%22);%0A    List%3CString%3E validationMessages = validateConfig(config);%0A    if (!validationMessages.isEmpty()) %7B%0A      console.error(%22Configuration is invalid:%22);%0A      for (String validationMessage : validationMessages) %7B%0A        console.error(validationMessage);%0A      %7D%0A      throw new ValidationException(%22Error validating configuration: Configuration is invalid.%22);%0A    %7D%0A    // TODO(danielromero): Add test to check that the workflow is not run, once we finish migration%0A    if (generalOptions.isValidate()) %7B%0A      console.info(%22Configuration is valid.%22);%0A      return;%0A    %7D%0A
     config.g
@@ -5169,25 +5169,487 @@
 guration
-%22);%0A    %7D
+: %22%0A          + %22Skylark config file and Yaml one are not equivalent%22);%0A    %7D%0A    validateConfig(config);%0A  %7D%0A%0A  /**%0A   * Returns a list of validation error messages, if any, for the given configuration.%0A   */%0A  protected List%3CString%3E validateConfig(Config config) throws ValidationException %7B%0A    // TODO(danielromero): Move here SkylarkParser validations once Config has all the workflows.%0A    // checkCondition(!workflows.isEmpty(), ...)%0A    return ImmutableList.of();
 %0A  %7D%0A%0A  

Introduce a validateConfig() method, to perform global validations.