// Copyright 2018 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.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
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 com.google.devtools.build.lib.rules.java.JavaConfiguration.ImportDepsCheckingLevel;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.stream.Collectors;

/** Utility for generating a call to the import_deps_checker. */
public final class ImportDepsCheckActionBuilder {

  public static ImportDepsCheckActionBuilder newBuilder() {
    return new ImportDepsCheckActionBuilder();
  }

  private Artifact jdepsArtifact;
  private Label ruleLabel;
  private NestedSet<Artifact> jarsToCheck;
  private NestedSet<Artifact> bootclasspath;
  private NestedSet<Artifact> declaredDeps;
  private NestedSet<Artifact> transitiveDeps;
  private ImportDepsCheckingLevel importDepsCheckingLevel;
  private Artifact importDepsChecker;

  private ImportDepsCheckActionBuilder() {}

  @CanIgnoreReturnValue
  public ImportDepsCheckActionBuilder checkJars(NestedSet<Artifact> jarsToCheck) {
    checkState(this.jarsToCheck == null);
    this.jarsToCheck = checkNotNull(jarsToCheck);
    return this;
  }

  @CanIgnoreReturnValue
  public ImportDepsCheckActionBuilder ruleLabel(Label ruleLabel) {
    checkState(this.ruleLabel == null);
    this.ruleLabel = checkNotNull(ruleLabel);
    return this;
  }

  @CanIgnoreReturnValue
  public ImportDepsCheckActionBuilder importDepsCheckingLevel(
      ImportDepsCheckingLevel importDepsCheckingLevel) {
    checkState(this.importDepsCheckingLevel == null);
    this.importDepsCheckingLevel = checkNotNull(importDepsCheckingLevel);
    return this;
  }

  @CanIgnoreReturnValue
  public ImportDepsCheckActionBuilder bootclasspath(NestedSet<Artifact> bootclasspath) {
    checkState(this.bootclasspath == null);
    this.bootclasspath = checkNotNull(bootclasspath);
    return this;
  }

  @CanIgnoreReturnValue
  public ImportDepsCheckActionBuilder jdepsOutputArtifact(Artifact jdepsArtifact) {
    checkState(this.jdepsArtifact == null);
    this.jdepsArtifact = checkNotNull(jdepsArtifact);
    return this;
  }

  @CanIgnoreReturnValue
  public ImportDepsCheckActionBuilder declareDeps(NestedSet<Artifact> declaredDeps) {
    checkState(this.declaredDeps == null);
    this.declaredDeps = checkNotNull(declaredDeps);
    return this;
  }

  @CanIgnoreReturnValue
  public ImportDepsCheckActionBuilder transitiveDeps(NestedSet<Artifact> transitiveDeps) {
    checkState(this.transitiveDeps == null);
    this.transitiveDeps = checkNotNull(transitiveDeps);
    return this;
  }

  @CanIgnoreReturnValue
  public ImportDepsCheckActionBuilder importDepsChecker(Artifact importDepsChecker) {
    checkState(this.importDepsChecker == null);
    this.importDepsChecker = checkNotNull(importDepsChecker);
    return this;
  }

  public void buildAndRegister(RuleContext ruleContext) {
    checkNotNull(jarsToCheck);
    checkNotNull(bootclasspath);
    checkNotNull(declaredDeps);
    checkNotNull(transitiveDeps);
    checkNotNull(importDepsCheckingLevel);
    checkNotNull(jdepsArtifact);
    checkNotNull(ruleLabel);

    SpawnAction.Builder builder = new SpawnAction.Builder();
    if (importDepsChecker == null) {
      builder.setExecutable(ruleContext.getExecutablePrerequisite("$import_deps_checker"));
    } else {
      builder
          .addTransitiveInputs(JavaRuntimeInfo.forHost(ruleContext).javaBaseInputs())
          .setJarExecutable(
              JavaCommon.getHostJavaExecutable(ruleContext),
              importDepsChecker,
              NestedSetBuilder.emptySet(Order.STABLE_ORDER));
    }
    ruleContext.registerAction(
        builder
            .useDefaultShellEnvironment()
            .addTransitiveInputs(jarsToCheck)
            .addTransitiveInputs(declaredDeps)
            .addTransitiveInputs(transitiveDeps)
            .addTransitiveInputs(bootclasspath)
            .addOutput(jdepsArtifact)
            .setMnemonic("ImportDepsChecker")
            .setProgressMessage(
                "Checking the completeness of the deps for %s",
                jarsToCheck.toList().stream()
                    .map(Artifact::prettyPrint)
                    .collect(Collectors.joining(", ")))
            .addCommandLine(buildCommandLine())
            .build(ruleContext));
  }

  private CustomCommandLine buildCommandLine() {
    return CustomCommandLine.builder()
        .addExecPaths(VectorArg.addBefore("--input").each(jarsToCheck))
        .addExecPaths(VectorArg.addBefore("--directdep").each(declaredDeps))
        .addExecPaths(VectorArg.addBefore("--classpath_entry").each(transitiveDeps))
        .addExecPaths(VectorArg.addBefore("--bootclasspath_entry").each(bootclasspath))
        .addDynamicString(convertErrorFlag(importDepsCheckingLevel))
        .addExecPath("--jdeps_output", jdepsArtifact)
        .add("--rule_label", ruleLabel.toString())
        .build();
  }

  private static String convertErrorFlag(ImportDepsCheckingLevel level) {
    switch (level) {
      case ERROR:
        return "--checking_mode=error";
      case WARNING:
        return "--checking_mode=warning";
      case OFF:
        return "--checking_mode=silence";
      default:
        throw new RuntimeException("Unhandled deps checking level: " + level);
    }
  }
}
