// Copyright 2015 The Bazel Authors. All rights reserved.
//
// 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.devtools.build.lib.syntax;

import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
import com.google.devtools.build.lib.util.Preconditions;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Global constants and support for global namespaces of runtime functions.
 */
public final class Runtime {

  private Runtime() {}

  @SkylarkSignature(name = "True", returnType = Boolean.class,
      doc = "Literal for the boolean true.")
  private static final Boolean TRUE = true;

  @SkylarkSignature(name = "False", returnType = Boolean.class,
      doc = "Literal for the boolean false.")
  private static final Boolean FALSE = false;

  /** There should be only one instance of this type to allow "== None" tests. */
  @SkylarkModule(
    name = "NoneType",
    documented = false,
    doc = "Unit type, containing the unique value None."
  )
  @Immutable
  public static final class NoneType implements SkylarkValue {
    private NoneType() {}

    @Override
    public String toString() {
      return "None";
    }

    @Override
    public boolean isImmutable() {
      return true;
    }

    @Override
    public void write(Appendable buffer, char quotationMark) {
      Printer.append(buffer, "None");
    }
  }

  /** Marker for unbound variables in cases where neither Java null nor Skylark None is suitable. */
  @Immutable
  public static final class UnboundMarker implements SkylarkValue {
    private UnboundMarker() {}

    @Override
    public String toString() {
      return "<unbound>";
    }

    @Override
    public boolean isImmutable() {
      return true;
    }

    @Override
    public void write(Appendable buffer, char quotationMark) {
      Printer.append(buffer, "<unbound>");
    }
  }

  @SkylarkSignature(name = "<unbound>", returnType = UnboundMarker.class, documented = false,
      doc = "Marker for unbound values in cases where neither Skylark None nor Java null can do.")
  public static final UnboundMarker UNBOUND = new UnboundMarker();

  @SkylarkSignature(name = "None", returnType = NoneType.class,
      doc = "Literal for the None value.")
  public static final NoneType NONE = new NoneType();

  @SkylarkSignature(name = "PACKAGE_NAME", returnType = String.class,
      doc = "The name of the package the rule or build extension is called from. "
          + "For example, in the BUILD file <code>some/package/BUILD</code>, its value "
          + "will be <code>some/package</code>. "
          + "This variable is special, because its value comes from outside of the extension "
          + "module (it comes from the BUILD file), so it can only be accessed in functions "
          + "(transitively) called from BUILD files. For example:<br>"
          + "<pre class=language-python>def extension():\n"
          + "  return PACKAGE_NAME</pre>"
          + "In this case calling <code>extension()</code> works from the BUILD file (if the "
          + "function is loaded), but not as a top level function call in the extension module.")
  public static final String PKG_NAME = "PACKAGE_NAME";

  @SkylarkSignature(name = "REPOSITORY_NAME", returnType = String.class,
      doc = "The name of the repository the rule or build extension is called from. "
          + "For example, in packages that are called into existence by the WORKSPACE stanza "
          + "<code>local_repository(name='local', path=...)</code> it will be set to "
          + "<code>@local</code>. In packages in the main repository, it will be empty. "
          + "It can only be accessed in functions (transitively) called from BUILD files.")
  public static final String REPOSITORY_NAME = "REPOSITORY_NAME";

  /**
   * Set up a given environment for supported class methods.
   */
  static Environment setupConstants(Environment env) {
    // In Python 2.x, True and False are global values and can be redefined by the user.
    // In Python 3.x, they are keywords. We implement them as values, for the sake of
    // simplicity. We define them as Boolean objects.
    return env.setup("False", FALSE).setup("True", TRUE).setup("None", NONE);
  }


  /** Global registry of functions associated to a class or namespace */
  private static final Map<Class<?>, Map<String, BaseFunction>> functions = new HashMap<>();

  /**
   * Registers a function with namespace to this global environment.
   */
  public static void registerFunction(Class<?> nameSpace, BaseFunction function) {
    Preconditions.checkNotNull(nameSpace);
    Preconditions.checkArgument(nameSpace.equals(getCanonicalRepresentation(nameSpace)));
    Preconditions.checkArgument(
        getCanonicalRepresentation(function.getObjectType()).equals(nameSpace));
    if (!functions.containsKey(nameSpace)) {
      functions.put(nameSpace, new HashMap<String, BaseFunction>());
    }
    functions.get(nameSpace).put(function.getName(), function);
  }

  /**
   * Returns the canonical representation of the given class, i.e. the super class for which any
   * functions were registered.
   *
   * <p>Currently, this is only necessary for mapping the different subclasses of {@link
   * java.util.Map} to the interface.
   */
  // TODO(bazel-team): make everything a SkylarkValue, and remove this function.
  public static Class<?> getCanonicalRepresentation(Class<?> clazz) {
    if (String.class.isAssignableFrom(clazz)) {
      return MethodLibrary.StringModule.class;
    }
    return EvalUtils.getSkylarkType(clazz);
  }


  static Map<String, BaseFunction> getNamespaceFunctions(Class<?> nameSpace) {
    return functions.get(getCanonicalRepresentation(nameSpace));
  }

  /**
   * Registers global fields with SkylarkSignature into the specified Environment.
   * @param env the Environment into which to register fields.
   * @param moduleClass the Class object containing globals.
   */
  public static void registerModuleGlobals(Environment env, Class<?> moduleClass) {
    try {
      if (moduleClass.isAnnotationPresent(SkylarkModule.class)) {
        env.setup(
            moduleClass.getAnnotation(SkylarkModule.class).name(),
            moduleClass.getConstructor().newInstance());
      }
      for (Field field : moduleClass.getDeclaredFields()) {
        if (field.isAnnotationPresent(SkylarkSignature.class)) {
          // Fields in Skylark modules are sometimes private.
          // Nevertheless they have to be annotated with SkylarkSignature.
          field.setAccessible(true);
          SkylarkSignature annotation = field.getAnnotation(SkylarkSignature.class);
          Object value = field.get(null);
          // Ignore function factories and non-global functions
          if (!(value instanceof BuiltinFunction.Factory
              || (value instanceof BaseFunction
                  && !annotation.objectType().equals(Object.class)))) {
            env.setup(annotation.name(), value);
          }
        }
      }
    } catch (ReflectiveOperationException e) {
      throw new AssertionError(e);
    }
  }

  /**
   * Returns the function of the namespace of the given name or null of it does not exists.
   */
  public static BaseFunction getFunction(Class<?> nameSpace, String name) {
    Map<String, BaseFunction> nameSpaceFunctions = getNamespaceFunctions(nameSpace);
    return nameSpaceFunctions != null ? nameSpaceFunctions.get(name) : null;
  }

  /**
   * Returns the function names registered with the namespace.
   */
  public static Set<String> getFunctionNames(Class<?> nameSpace) {
    Map<String, BaseFunction> nameSpaceFunctions = getNamespaceFunctions(nameSpace);
    return nameSpaceFunctions != null ? nameSpaceFunctions.keySet() : ImmutableSet.<String>of();
  }

  static void setupMethodEnvironment(
      Environment env, Iterable<BaseFunction> functions) {
    for (BaseFunction function : functions) {
      env.setup(function.getName(), function);
    }
  }
}
