/*
 * Copyright (C) 2016 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.copybara.testing;

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

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.copybara.Core;
import com.google.copybara.GeneralOptions;
import com.google.copybara.authoring.Authoring;
import com.google.copybara.config.Config;
import com.google.copybara.config.ConfigFile;
import com.google.copybara.config.MapConfigFile;
import com.google.copybara.config.SkylarkParser;
import com.google.copybara.exception.ValidationException;
import com.google.copybara.util.console.Message.MessageType;
import com.google.copybara.util.console.testing.TestingConsole;
import com.google.devtools.build.lib.syntax.Environment;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Utility class for running a simple skylark code and getting back a declared variable.
 */
public final class SkylarkTestExecutor {

  private final OptionsBuilder options;
  private final SkylarkParser skylarkParser;
  private final Map<String, byte[]> extraConfigFiles = new HashMap<>();

  public SkylarkTestExecutor(OptionsBuilder options, Class<?>... modules) {
    skylarkParser =
        new SkylarkParser(
            ImmutableSet.<Class<?>>builder()
                .add(Core.class)
                .add(Authoring.Module.class)
                .add(modules)
                .build());
    this.options = options;
  }

  public SkylarkTestExecutor addExtraConfigFile(String key, String content) {
    if (extraConfigFiles.put(key, content.getBytes(UTF_8)) != null) {
      throw new IllegalArgumentException("Already have content for: " + key);
    }
    return this;
  }

  @VisibleForTesting
  public Iterable<Class<?>> getModules(){
    return skylarkParser.getModules();
  }

  @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
  public <T> T eval(String var, String config) throws ValidationException {
    try {
      Environment env =
          skylarkParser.executeSkylark(
              createConfigFile(config), options.build(), options.general.console());
      T t = (T) env.getGlobals().get(var);
      Preconditions.checkNotNull(t, "Config %s evaluates to null '%s' var.", config, var);
      return t;
    } catch (IOException | InterruptedException e) {
      throw new RuntimeException("Should not happen: " + e.getMessage(), e);
    }
  }

  public Config loadConfig(String configContent) throws IOException, ValidationException {
    return skylarkParser.loadConfig(
        createConfigFile(configContent), options.build(), options.general.console());
  }

  public Map<String, ConfigFile<String>> getConfigMap(String configContent)
      throws IOException, ValidationException {
    return getConfigMap(createConfigFile(configContent));
  }

  public <T> Map<String, ConfigFile<T>> getConfigMap(ConfigFile<T> config)
      throws IOException, ValidationException {
    return skylarkParser.getConfigWithTransitiveImports(
            config, options.build(), options.general.console())
        .files;
  }

  private ConfigFile<String> createConfigFile(String configContent) {
    return new MapConfigFile(
        new ImmutableMap.Builder<String, byte[]>()
            .putAll(extraConfigFiles)
            .put("copy.bara.sky", configContent.getBytes(UTF_8))
            .build(),
        "copy.bara.sky");
  }

  public void evalFails(String config, String expectedMsg) {
    try {
      eval("r", "r = " + config);
      throw new RuntimeException("Eval should fail: " + config);
    } catch (ValidationException e) {
      getConsole().assertThat().onceInLog(MessageType.ERROR, "(.|\n)*" + expectedMsg + "(.|\n)*");
    }
  }

  private TestingConsole getConsole() {
    return (TestingConsole) options.build().get(GeneralOptions.class).console();
  }
}

@@ -1428,17 +1428,16 @@
 onment;%0A
-%0A
 import j
@@ -1644,16 +1644,78 @@
 utor %7B%0A%0A
+  private static final String DEFAULT_FILE = %22copy.bara.sky%22;%0A
   privat
@@ -2805,24 +2805,38 @@
 eConfigFile(
+DEFAULT_FILE, 
 config), opt
@@ -3164,24 +3164,284 @@
 %0A    %7D%0A  %7D%0A%0A
+  public Config loadConfig(String filename, String configContent)%0A      throws IOException, ValidationException %7B%0A    return skylarkParser.loadConfig(%0A        createConfigFile(filename, configContent),%0A        options.build(), options.general.console());%0A  %7D%0A%0A
   public Con
@@ -3573,32 +3573,46 @@
 reateConfigFile(
+DEFAULT_FILE, 
 configContent), 
@@ -3610,16 +3610,24 @@
 ontent),
+%0A       
  options
@@ -3834,16 +3834,30 @@
 figFile(
+DEFAULT_FILE, 
 configCo
@@ -4180,32 +4180,49 @@
 nfigFile(String 
+filename, String 
 configContent) %7B
@@ -4358,31 +4358,24 @@
    .put(
-%22copy.bara.sky%22
+filename
 , config
@@ -4429,31 +4429,24 @@
         
-%22copy.bara.sky%22
+filename
 );%0A  %7D%0A%0A

Allow setting filename in skylark tests