// Copyright 2020 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.rules.java;

import static com.google.devtools.build.lib.collect.nestedset.Order.STABLE_ORDER;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.AliasProvider;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import javax.annotation.Nullable;

/** An executable tool that is part of {@code java_toolchain}. */
@AutoValue
public abstract class JavaToolchainTool {

  /** The executable, possibly a {@code _deploy.jar}. */
  public abstract FilesToRunProvider tool();

  /** Additional inputs required by the tool, e.g. a Class Data Sharing archive. */
  public abstract NestedSet<Artifact> data();

  /**
   * JVM flags to invoke the tool with, or empty if it is not a {@code _deploy.jar}. Location
   * expansion is performed on these flags using the inputs in {@link #data}.
   */
  public abstract NestedSet<String> jvmOpts();

  @Nullable
  static JavaToolchainTool fromRuleContext(
      RuleContext ruleContext,
      String toolAttribute,
      String dataAttribute,
      String jvmOptsAttribute) {
    FilesToRunProvider tool = ruleContext.getExecutablePrerequisite(toolAttribute);
    if (tool == null) {
      return null;
    }
    NestedSetBuilder<Artifact> dataArtifacts = NestedSetBuilder.stableOrder();
    ImmutableMap.Builder<Label, ImmutableCollection<Artifact>> locations = ImmutableMap.builder();
    for (TransitiveInfoCollection data : ruleContext.getPrerequisites(dataAttribute)) {
      NestedSet<Artifact> files = data.getProvider(FileProvider.class).getFilesToBuild();
      dataArtifacts.addTransitive(files);
      locations.put(AliasProvider.getDependencyLabel(data), files.toList());
    }
    NestedSet<String> jvmOpts =
        NestedSetBuilder.wrap(
            Order.STABLE_ORDER,
            ruleContext
                .getExpander()
                .withExecLocations(locations.buildOrThrow())
                .list(jvmOptsAttribute));
    return create(tool, dataArtifacts.build(), jvmOpts);
  }

  @Nullable
  static JavaToolchainTool fromFilesToRunProvider(@Nullable FilesToRunProvider executable) {
    if (executable == null) {
      return null;
    }
    return create(
        executable,
        NestedSetBuilder.emptySet(STABLE_ORDER),
        NestedSetBuilder.emptySet(STABLE_ORDER));
  }

  private static JavaToolchainTool create(
      FilesToRunProvider tool, NestedSet<Artifact> data, NestedSet<String> jvmOpts) {
    return new AutoValue_JavaToolchainTool(tool, data, jvmOpts);
  }

  /**
   * Builds the executable command line for the tool and adds its inputs to the given input builder.
   *
   * <p>For a Java command, the executable command line will include {@code java -jar deploy.jar} as
   * well as any JVM flags.
   *
   * @param command the executable command line builder for the tool
   * @param toolchain {@code java_toolchain} for the action being constructed
   * @param inputs inputs for the action being constructed
   */
  void buildCommandLine(
      CustomCommandLine.Builder command,
      JavaToolchainProvider toolchain,
      NestedSetBuilder<Artifact> inputs) {
    inputs.addTransitive(data());
    Artifact executable = tool().getExecutable();
    if (!executable.getExtension().equals("jar")) {
      command.addExecPath(executable);
      inputs.addTransitive(tool().getFilesToRun());
    } else {
      inputs.add(executable).addTransitive(toolchain.getJavaRuntime().javaBaseInputs());
      command
          .addPath(toolchain.getJavaRuntime().javaBinaryExecPathFragment())
          .addAll(toolchain.getJvmOptions())
          .addAll(jvmOpts())
          .add("-jar")
          .addPath(executable.getExecPath());
    }
  }

  /**
   * Builds the executable command line for the tool and adds its inputs to the given builder, see
   * also {@link #buildCommandLine(CustomCommandLine.Builder, JavaToolchainProvider,
   * NestedSetBuilder)}.
   */
  CustomCommandLine.Builder buildCommandLine(
      JavaToolchainProvider toolchain, NestedSetBuilder<Artifact> inputs) {
    CustomCommandLine.Builder command = CustomCommandLine.builder();
    buildCommandLine(command, toolchain, inputs);
    return command;
  }
}
