// Copyright 2017 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.testutil;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.Package;
import com.google.devtools.build.lib.packages.PackageLoadingListener;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.skyframe.PrecomputedValue.Injected;
import com.google.devtools.build.lib.skyframe.packages.BazelPackageLoader;
import com.google.devtools.build.lib.skyframe.packages.PackageLoader;
import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyFunctionName;
import java.util.OptionalLong;
import net.starlark.java.eval.StarlarkSemantics;

/**
 * A {@link PackageLoadingListener} for use in tests that a check with {@link BazelPackageLoader}
 * for each loaded package, for the sake of getting pretty nice test coverage.
 */
public class BazelPackageLoadingListenerForTesting implements PackageLoadingListener {
  private final ConfiguredRuleClassProvider ruleClassProvider;
  private final BlazeDirectories directories;
  private final ImmutableList<Injected> extraPrecomputedValues;
  private final ImmutableMap<SkyFunctionName, SkyFunction> extraSkyFunctions;

  public BazelPackageLoadingListenerForTesting(
      ConfiguredRuleClassProvider ruleClassProvider,
      BlazeDirectories directories,
      ImmutableList<Injected> extraPrecomputedValues,
      ImmutableMap<SkyFunctionName, SkyFunction> extraSkyFunctions) {
    this.ruleClassProvider = ruleClassProvider;
    this.directories = directories;
    this.extraPrecomputedValues = extraPrecomputedValues;
    this.extraSkyFunctions = extraSkyFunctions;
  }

  @Override
  public void onLoadingCompleteAndSuccessful(
      Package pkg,
      StarlarkSemantics starlarkSemantics,
      long loadTimeNanos,
      OptionalLong packageOverhead) {
    sanityCheckBazelPackageLoader(pkg, ruleClassProvider, starlarkSemantics);
  }

  private PackageLoader makeFreshPackageLoader(
      ConfiguredRuleClassProvider ruleClassProvider, StarlarkSemantics starlarkSemantics) {
    return BazelPackageLoader.builder(
            Root.fromPath(directories.getWorkspace()),
            directories.getInstallBase(),
            directories.getOutputBase())
        .setStarlarkSemantics(starlarkSemantics)
        .setRuleClassProvider(ruleClassProvider)
        .addExtraPrecomputedValues(extraPrecomputedValues)
        .addExtraSkyFunctions(extraSkyFunctions)
        .build();
  }

  private void sanityCheckBazelPackageLoader(
      Package pkg,
      ConfiguredRuleClassProvider ruleClassProvider,
      StarlarkSemantics starlarkSemantics) {
    PackageIdentifier pkgId = pkg.getPackageIdentifier();
    Package newlyLoadedPkg;
    try (PackageLoader packageLoader =
        makeFreshPackageLoader(ruleClassProvider, starlarkSemantics)) {
      newlyLoadedPkg = packageLoader.loadPackage(pkg.getPackageIdentifier());
    } catch (InterruptedException e) {
      return;
    } catch (NoSuchPackageException e) {
      throw new IllegalStateException(e);
    }
    ImmutableSet<Label> targetsInPkg =
        ImmutableSet.copyOf(Iterables.transform(pkg.getTargets().values(), Target::getLabel));
    ImmutableSet<Label> targetsInNewlyLoadedPkg =
        ImmutableSet.copyOf(
            Iterables.transform(newlyLoadedPkg.getTargets().values(), Target::getLabel));
    if (!targetsInPkg.equals(targetsInNewlyLoadedPkg)) {
      Sets.SetView<Label> unsatisfied = Sets.difference(targetsInPkg, targetsInNewlyLoadedPkg);
      Sets.SetView<Label> unexpected = Sets.difference(targetsInNewlyLoadedPkg, targetsInPkg);
      throw new IllegalStateException(
          String.format(
              "The Package for %s had a different set of targets (<targetsInPkg> - "
                  + "<targetsInNewlyLoadedPkg> = %s, <targetsInNewlyLoadedPkg> - <targetsInPkg> = "
                  + "%s) when loaded normally during execution of the current test than it did "
                  + "when loaded via BazelPackageLoader (done automatically by the "
                  + "BazelPackageLoadingListenerForTesting hook). This either means: (i) Skyframe "
                  + "package loading semantics have diverged from "
                  + "BazelPackageLoader semantics (ii) The test in question is doing something "
                  + "that confuses BazelPackageLoadingListenerForTesting.",
              pkgId, unsatisfied, unexpected));
    }
  }
}
