// 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.common.collect.ImmutableList.toImmutableList;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.ExecutionRequirements;
import com.google.devtools.build.lib.actions.ParamFileInfo;
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
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.SpawnAction;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.util.StringCanonicalizer;
import javax.annotation.Nullable;

/** Helper to create Android Lint actions. */
class AndroidLintActionBuilder {
  private AndroidLintActionBuilder() {}

  private static final String MNEMONIC = "AndroidLint";
  private static final ParamFileInfo PARAM_FILE_INFO =
      ParamFileInfo.builder(ParameterFileType.UNQUOTED)
          .setCharset(UTF_8)
          .setUseAlways(true) // needed to support workers
          .build();

  /** Creates and registers Android Lint action if needed and returns action's output if created. */
  @Nullable
  static Artifact create(
      RuleContext ruleContext,
      JavaConfiguration config,
      JavaTargetAttributes attributes,
      BootClassPathInfo bootClassPathInfo,
      JavaCommon common,
      JavaSemantics semantics,
      JavaCompileOutputs<Artifact> outputs) {
    if (!config.runAndroidLint()
        || !attributes.hasSources()
        || JavaCommon.isNeverLink(ruleContext)) {
      // Note Javac doesn't run when depending on neverlink library, so we also skip Android Lint.
      return null;
    }
    if (config.limitAndroidLintToAndroidCompatible()
        && !JavaCommon.getConstraints(ruleContext).contains("android")) {
      return null;
    }
    JavaToolchainProvider toolchain = JavaToolchainProvider.from(ruleContext);
    AndroidLintTool androidLint = toolchain.getAndroidLint();
    if (androidLint == null) {
      ruleContext.ruleError(
          "android_lint_wrapper not set in java_toolchain: " + toolchain.getToolchainLabel());
      return null;
    }

    ImmutableList<Artifact> allSrcJars = attributes.getSourceJars();
    if (outputs.genSource() != null) {
      allSrcJars =
          ImmutableList.<Artifact>builder().addAll(allSrcJars).add(outputs.genSource()).build();
    }
    NestedSet<Artifact> classpath = attributes.getCompileTimeClassPath();
    if (!bootClassPathInfo.auxiliary().isEmpty()) {
      classpath =
          NestedSetBuilder.<Artifact>naiveLinkOrder()
              .addTransitive(bootClassPathInfo.auxiliary())
              .addTransitive(classpath)
              .build();
    }

    CustomCommandLine.Builder cmd = CustomCommandLine.builder();
    cmd.addExecPaths("--sources", attributes.getSourceFiles())
        .addExecPaths("--source_jars", allSrcJars)
        .addExecPaths("--bootclasspath", bootClassPathInfo.bootclasspath())
        .addExecPaths("--classpath", classpath)
        .addExecPaths("--plugins", attributes.plugins().plugins().processorClasspath())
        .addLabel("--target_label", ruleContext.getLabel());
    ImmutableList<String> javacopts =
        common.getJavacOpts().stream().map(StringCanonicalizer::intern).collect(toImmutableList());
    if (!javacopts.isEmpty()) {
      cmd.addAll("--javacopts", javacopts);
      // terminate --javacopts with `--` to support javac flags that start with `--`
      cmd.add("--");
    }
    cmd.add("--lintopts");
    cmd.addAll(androidLint.options());

    SpawnAction.Builder spawnAction = new SpawnAction.Builder();
    for (JavaPackageConfigurationProvider provider : androidLint.packageConfiguration()) {
      if (provider.matches(ruleContext.getLabel())) {
        cmd.addAll(provider.javacopts());
        spawnAction.addTransitiveInputs(provider.data());
      }
    }

    Artifact result =
        ruleContext.getPackageRelativeArtifact(
            ruleContext.getLabel().getName() + "_android_lint_output.xml",
            ruleContext.getBinOrGenfilesDirectory());
    cmd.addExecPath("--xml", result);

    NestedSetBuilder<Artifact> toolInputs = NestedSetBuilder.stableOrder();
    androidLint.tool().buildCommandLine(spawnAction.executableArguments(), toolchain, toolInputs);
    semantics.setLintProgressMessage(spawnAction);
    ruleContext.registerAction(
        spawnAction
            .addCommandLine(cmd.build(), PARAM_FILE_INFO)
            .addInputs(attributes.getSourceFiles())
            .addInputs(allSrcJars)
            .addTransitiveInputs(bootClassPathInfo.bootclasspath())
            .addTransitiveInputs(classpath)
            .addTransitiveInputs(attributes.plugins().plugins().processorClasspath())
            .addTransitiveInputs(attributes.plugins().plugins().data())
            .addTransitiveTools(toolInputs.build())
            .addOutput(result)
            .setMnemonic(MNEMONIC)
            .setExecutionInfo(getExecutionInfo(ruleContext))
            .build(ruleContext));
    return result;
  }

  /** Advertises worker support added in b/191156225. */
  private static ImmutableMap<String, String> getExecutionInfo(RuleContext ruleContext) {
    ImmutableMap<String, String> executionInfo =
        ImmutableMap.of(ExecutionRequirements.SUPPORTS_WORKERS, "1");

    return ImmutableMap.<String, String>builder()
        .putAll(ruleContext.getConfiguration().modifiedExecutionInfo(executionInfo, MNEMONIC))
        .putAll(
            TargetUtils.getExecutionInfo(
                ruleContext.getRule(), ruleContext.isAllowTagsPropagation()))
        .buildOrThrow();
  }
}
