// Copyright 2014 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.buildtool;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.InputFileConfiguredTarget;
import com.google.devtools.build.lib.analysis.OutputFileConfiguredTarget;
import com.google.devtools.build.lib.analysis.OutputGroupProvider;
import com.google.devtools.build.lib.analysis.TopLevelArtifactContext;
import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.CollectionUtils;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.exec.ExecutionOptions;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.skyframe.AspectValue;
import com.google.devtools.build.lib.util.io.OutErr;

import java.util.ArrayList;
import java.util.Collection;

/**
 * Handles --show_result and --experimental_show_artifacts.
 */
class BuildResultPrinter {
  private final CommandEnvironment env;

  BuildResultPrinter(CommandEnvironment env) {
    this.env = env;
  }

  /**
   * Shows the result of the build. Information includes the list of up-to-date
   * and failed targets and list of output artifacts for successful targets
   *
   * <p>This corresponds to the --show_result flag.
   */
  public void showBuildResult(
      BuildRequest request,
      BuildResult result,
      Collection<ConfiguredTarget> configuredTargets,
      Collection<AspectValue> aspects) {
    // NOTE: be careful what you print!  We don't want to create a consistency
    // problem where the summary message and the exit code disagree.  The logic
    // here is already complex.

    Collection<ConfiguredTarget> targetsToPrint = filterTargetsToPrint(configuredTargets);
    Collection<AspectValue> aspectsToPrint = filterAspectsToPrint(aspects);

    // Filter the targets we care about into two buckets:
    Collection<ConfiguredTarget> succeeded = new ArrayList<>();
    Collection<ConfiguredTarget> failed = new ArrayList<>();
    for (ConfiguredTarget target : targetsToPrint) {
      Collection<ConfiguredTarget> successfulTargets = result.getSuccessfulTargets();
      (successfulTargets.contains(target) ? succeeded : failed).add(target);
    }

    // Suppress summary if --show_result value is exceeded:
    if (succeeded.size() + failed.size() + aspectsToPrint.size()
        > request.getBuildOptions().maxResultTargets) {
      return;
    }

    OutErr outErr = request.getOutErr();

    TopLevelArtifactContext context = request.getTopLevelArtifactContext();
    for (ConfiguredTarget target : succeeded) {
      Label label = target.getLabel();
      // For up-to-date targets report generated artifacts, but only
      // if they have associated action and not middleman artifacts.
      boolean headerFlag = true;
      for (Artifact artifact :
          TopLevelArtifactHelper.getAllArtifactsToBuild(target, context).getImportantArtifacts()) {
        if (shouldPrint(artifact)) {
          if (headerFlag) {
            outErr.printErr("Target " + label + " up-to-date:\n");
            headerFlag = false;
          }
          outErr.printErrLn(formatArtifactForShowResults(artifact, request));
        }
      }
      if (headerFlag) {
        outErr.printErr("Target " + label + " up-to-date (nothing to build)\n");
      }
    }

    for (AspectValue aspect : aspectsToPrint) {
      Label label = aspect.getLabel();
      String aspectName = aspect.getConfiguredAspect().getName();
      boolean headerFlag = true;
      NestedSet<Artifact> importantArtifacts =
          TopLevelArtifactHelper.getAllArtifactsToBuild(aspect, context).getImportantArtifacts();
      for (Artifact importantArtifact : importantArtifacts) {
        if (headerFlag) {
          outErr.printErr("Aspect " + aspectName + " of " + label + " up-to-date:\n");
          headerFlag = false;
        }
        if (shouldPrint(importantArtifact)) {
          outErr.printErrLn(formatArtifactForShowResults(importantArtifact, request));
        }
      }
      if (headerFlag) {
        outErr.printErr(
            "Aspect " + aspectName + " of " + label + " up-to-date (nothing to build)\n");
      }
    }

    for (ConfiguredTarget target : failed) {
      outErr.printErr("Target " + target.getLabel() + " failed to build\n");

      // For failed compilation, it is still useful to examine temp artifacts,
      // (ie, preprocessed and assembler files).
      OutputGroupProvider topLevelProvider =
          target.getProvider(OutputGroupProvider.class);
      String productName = env.getRuntime().getProductName();
      if (topLevelProvider != null) {
        for (Artifact temp : topLevelProvider.getOutputGroup(OutputGroupProvider.TEMP_FILES)) {
          if (temp.getPath().exists()) {
            outErr.printErrLn("  See temp at "
                + OutputDirectoryLinksUtils.getPrettyPath(temp.getPath(),
                    env.getWorkspaceName(),
                    env.getWorkspace(),
                    request.getBuildOptions().getSymlinkPrefix(productName),
                    productName));
          }
        }
      }
    }
    if (!failed.isEmpty() && !request.getOptions(ExecutionOptions.class).verboseFailures) {
      outErr.printErr("Use --verbose_failures to see the command lines of failed build steps.\n");
    }
  }

  private boolean shouldPrint(Artifact artifact) {
    return !artifact.isSourceArtifact() && !artifact.isMiddlemanArtifact();
  }

  private String formatArtifactForShowResults(Artifact artifact, BuildRequest request) {
    String productName = env.getRuntime().getProductName();
    return "  " + OutputDirectoryLinksUtils.getPrettyPath(artifact.getPath(),
        env.getWorkspaceName(), env.getWorkspace(),
        request.getBuildOptions().getSymlinkPrefix(productName), productName);
  }

  /**
   * Prints a flat list of all artifacts built by the passed top-level targets.
   *
   * <p>This corresponds to the --experimental_show_artifacts flag.
   */
  public void showArtifacts(
      BuildRequest request,
      Collection<ConfiguredTarget> configuredTargets,
      Collection<AspectValue> aspects) {

    TopLevelArtifactContext context = request.getTopLevelArtifactContext();
    Collection<ConfiguredTarget> targetsToPrint = filterTargetsToPrint(configuredTargets);
    Collection<AspectValue> aspectsToPrint = filterAspectsToPrint(aspects);

    NestedSetBuilder<Artifact> artifactsBuilder = NestedSetBuilder.stableOrder();
    for (ConfiguredTarget target : targetsToPrint) {
      artifactsBuilder.addTransitive(
          TopLevelArtifactHelper.getAllArtifactsToBuild(target, context).getImportantArtifacts());
    }

    for (AspectValue aspect : aspectsToPrint) {
      artifactsBuilder.addTransitive(
          TopLevelArtifactHelper.getAllArtifactsToBuild(aspect, context).getImportantArtifacts());
    }

    OutErr outErr = request.getOutErr();
    outErr.printErrLn("Build artifacts:");

    NestedSet<Artifact> artifacts = artifactsBuilder.build();
    for (Artifact artifact : artifacts) {
      if (!artifact.isSourceArtifact()) {
        outErr.printErrLn(">>>" + artifact.getPath());
      }
    }
  }

  /**
   * Returns a list of configured targets that should participate in printing.
   *
   * <p>Hidden rules and other inserted targets are ignored.
   */
  private Collection<ConfiguredTarget> filterTargetsToPrint(
      Collection<ConfiguredTarget> configuredTargets) {
    ImmutableList.Builder<ConfiguredTarget> result = ImmutableList.builder();
    for (ConfiguredTarget target : configuredTargets) {
      // TODO(bazel-team): this is quite ugly. Add a marker provider for this check.
      if (target instanceof InputFileConfiguredTarget) {
        // Suppress display of source files (because we do no work to build them).
        continue;
      }
      if (target.getTarget() instanceof Rule) {
        Rule rule = (Rule) target.getTarget();
        if (rule.getRuleClass().contains("$")) {
          // Suppress display of hidden rules
          continue;
        }
      }
      if (target instanceof OutputFileConfiguredTarget) {
        // Suppress display of generated files (because they appear underneath
        // their generating rule), EXCEPT those ones which are not part of the
        // filesToBuild of their generating rule (e.g. .par, _deploy.jar
        // files), OR when a user explicitly requests an output file but not
        // its rule.
        TransitiveInfoCollection generatingRule =
            ((OutputFileConfiguredTarget) target).getGeneratingRule();
        if (CollectionUtils.containsAll(
            generatingRule.getProvider(FileProvider.class).getFilesToBuild(),
            target.getProvider(FileProvider.class).getFilesToBuild())
            && configuredTargets.contains(generatingRule)) {
          continue;
        }
      }

      result.add(target);
    }
    return result.build();
  }

  private Collection<AspectValue> filterAspectsToPrint(Collection<AspectValue> aspects) {
    return aspects;
  }
}
