// Copyright 2015 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.android;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
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.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.BuildType;
import java.util.Optional;
import javax.annotation.Nullable;

/**
 * Represents a container for the {@link ResourceContainer}s for a given library. This is
 * abstraction simplifies the process of managing and exporting the direct and transitive resource
 * dependencies of an android rule, as well as providing type safety.
 *
 * <p>The transitive and direct dependencies are not guaranteed to be disjoint. If a library is
 * included in both the transitive and direct dependencies, it will appear twice. This requires
 * consumers to manage duplicated resources gracefully.
 */
@Immutable
public final class ResourceDependencies {
  /**
   * Contains all the transitive resources that are not generated by the direct ancestors of the
   * current rule.
   *
   * @deprecated We are migrating towards storing each type of Artifact in a different NestedSet.
   *     This should allow greater efficiency since we don't need to unroll this NestedSet to get a
   *     particular input. TODO (b/67996945): Complete this migration.
   */
  @Deprecated
  private final NestedSet<ResourceContainer> transitiveResourceContainers;

  /**
   * Contains all the direct dependencies of the current target. Since a given direct dependency can
   * act as a "forwarding" library, collecting all the direct resource from it's dependencies and
   * providing them as "direct" dependencies to maintain merge order, this uses a NestedSet to
   * properly maintain ordering and ease of merging.
   *
   * @deprecated Similarly to {@link #transitiveResourceContainers}, we are moving away from storing
   *     ResourceContainer objects here. TODO (b/67996945): Complete this migration.
   */
  @Deprecated
  private final NestedSet<ResourceContainer> directResourceContainers;

  /**
   * Transitive resource files for this target.
   *
   * <p>We keep them separate from the {@code transitiveAssets} so that we can filter them.
   */
  private final NestedSet<Artifact> transitiveResources;

  /** Transitive asset files for this target. */
  private final NestedSet<Artifact> transitiveAssets;

  private final NestedSet<Artifact> transitiveManifests;

  private final NestedSet<Artifact> transitiveAapt2RTxt;

  private final NestedSet<Artifact> transitiveSymbolsBin;

  private final NestedSet<Artifact> transitiveCompiledSymbols;

  private final NestedSet<Artifact> transitiveStaticLib;

  private final NestedSet<Artifact> transitiveRTxt;

  /** Whether the resources of the current rule should be treated as neverlink. */
  private final boolean neverlink;

  public static ResourceDependencies fromRuleDeps(RuleContext ruleContext, boolean neverlink) {
    NestedSetBuilder<ResourceContainer> transitiveDependencies = NestedSetBuilder.naiveLinkOrder();
    NestedSetBuilder<ResourceContainer> directDependencies = NestedSetBuilder.naiveLinkOrder();
    NestedSetBuilder<Artifact> transitiveResources = NestedSetBuilder.naiveLinkOrder();
    NestedSetBuilder<Artifact> transitiveAssets = NestedSetBuilder.naiveLinkOrder();
    NestedSetBuilder<Artifact> transitiveManifests = NestedSetBuilder.naiveLinkOrder();
    NestedSetBuilder<Artifact> transitiveAapt2RTxt = NestedSetBuilder.naiveLinkOrder();
    NestedSetBuilder<Artifact> transitiveSymbolsBin = NestedSetBuilder.naiveLinkOrder();
    NestedSetBuilder<Artifact> transitiveCompiledSymbols = NestedSetBuilder.naiveLinkOrder();
    NestedSetBuilder<Artifact> transitiveStaticLib = NestedSetBuilder.naiveLinkOrder();
    NestedSetBuilder<Artifact> transitiveRTxt = NestedSetBuilder.naiveLinkOrder();

    AttributeMap attributes = ruleContext.attributes();
    for (String attr : AndroidCommon.TRANSITIVE_ATTRIBUTES) {
      if (!attributes.has(attr, BuildType.LABEL_LIST) && !attributes.has(attr, BuildType.LABEL)) {
        continue;
      }
      for (AndroidResourcesProvider resources :
          ruleContext.getPrerequisites(attr, Mode.TARGET, AndroidResourcesProvider.class)) {
        transitiveDependencies.addTransitive(resources.getTransitiveAndroidResources());
        directDependencies.addTransitive(resources.getDirectAndroidResources());
        transitiveResources.addTransitive(resources.getTransitiveResources());
        transitiveAssets.addTransitive(resources.getTransitiveAssets());
        transitiveManifests.addTransitive(resources.getTransitiveManifests());
        transitiveAapt2RTxt.addTransitive(resources.getTransitiveAapt2RTxt());
        transitiveSymbolsBin.addTransitive(resources.getTransitiveSymbolsBin());
        transitiveCompiledSymbols.addTransitive(resources.getTransitiveCompiledSymbols());
        transitiveStaticLib.addTransitive(resources.getTransitiveStaticLib());
        transitiveRTxt.addTransitive(resources.getTransitiveRTxt());
      }
    }

    return new ResourceDependencies(
        neverlink,
        transitiveDependencies.build(),
        directDependencies.build(),
        transitiveResources.build(),
        transitiveAssets.build(),
        transitiveManifests.build(),
        transitiveAapt2RTxt.build(),
        transitiveSymbolsBin.build(),
        transitiveCompiledSymbols.build(),
        transitiveStaticLib.build(),
        transitiveRTxt.build());
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .add("transitiveResourceContainers", transitiveResourceContainers)
        .add("directResourceContainers", directResourceContainers)
        .add("transitiveResources", transitiveResources)
        .add("transitiveAssets", transitiveAssets)
        .add("transitiveManifests", transitiveManifests)
        .add("transitiveAapt2RTxt", transitiveAapt2RTxt)
        .add("transitiveSymbolsBin", transitiveSymbolsBin)
        .add("transitiveCompiledSymbols", transitiveCompiledSymbols)
        .add("transitiveStaticLib", transitiveStaticLib)
        .add("transitiveRTxt", transitiveRTxt)
        .add("neverlink", neverlink)
        .toString();
  }

  /**
   * Creates an empty ResourceDependencies instance. This is used when an AndroidResources rule is
   * the only resource dependency. The most common case is the AndroidTest rule.
   */
  public static ResourceDependencies empty() {
    return new ResourceDependencies(
        false,
        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER),
        NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER));
  }

  private ResourceDependencies(
      boolean neverlink,
      NestedSet<ResourceContainer> transitiveResourceContainers,
      NestedSet<ResourceContainer> directResourceContainers,
      NestedSet<Artifact> transitiveResources,
      NestedSet<Artifact> transitiveAssets,
      NestedSet<Artifact> transitiveManifests,
      NestedSet<Artifact> transitiveAapt2RTxt,
      NestedSet<Artifact> transitiveSymbolsBin,
      NestedSet<Artifact> transitiveCompiledSymbols,
      NestedSet<Artifact> transitiveStaticLib,
      NestedSet<Artifact> transitiveRTxt) {
    this.neverlink = neverlink;
    this.transitiveResourceContainers = transitiveResourceContainers;
    this.directResourceContainers = directResourceContainers;
    this.transitiveResources = transitiveResources;
    this.transitiveAssets = transitiveAssets;
    this.transitiveManifests = transitiveManifests;
    this.transitiveAapt2RTxt = transitiveAapt2RTxt;
    this.transitiveSymbolsBin = transitiveSymbolsBin;
    this.transitiveCompiledSymbols = transitiveCompiledSymbols;
    this.transitiveStaticLib = transitiveStaticLib;
    this.transitiveRTxt = transitiveRTxt;
  }

  /** Returns a copy of this instance with filtered resources. The original object is unchanged. */
  public ResourceDependencies filter(ResourceFilter resourceFilter) {
    Optional<NestedSet<Artifact>> filteredResources =
        resourceFilter.maybeFilterDependencies(transitiveResources);

    if (!filteredResources.isPresent()) {
      // No filtering was done.
      return this;
    }

    // Note that this doesn't filter any of the dependent artifacts. This
    // means that if any resource changes, the corresponding actions will get
    // re-executed
    return withResources(
        resourceFilter.filterDependencyContainers(transitiveResourceContainers),
        resourceFilter.filterDependencyContainers(directResourceContainers),
        filteredResources.get());
  }

  @VisibleForTesting
  ResourceDependencies withResources(
      NestedSet<ResourceContainer> transitiveResourceContainers,
      NestedSet<ResourceContainer> directResourceContainers,
      NestedSet<Artifact> transitiveResources) {
    return new ResourceDependencies(
        neverlink,
        transitiveResourceContainers,
        directResourceContainers,
        transitiveResources,
        transitiveAssets,
        transitiveManifests,
        transitiveAapt2RTxt,
        transitiveSymbolsBin,
        transitiveCompiledSymbols,
        transitiveStaticLib,
        transitiveRTxt);
  }

  /**
   * Creates a new AndroidResourcesProvider with the supplied ResourceContainer as the direct dep.
   *
   * <p>When a library produces a new resource container the AndroidResourcesProvider should use
   * that container as a the direct dependency for that library. This makes the consuming rule to
   * identify the new container and merge appropriately. The previous direct dependencies are then
   * added to the transitive dependencies.
   *
   * @param label The label of the library exporting this provider.
   * @param newDirectResource The new direct dependency for AndroidResourcesProvider
   * @return A provider with the current resources and label.
   */
  public AndroidResourcesProvider toProvider(
      Label label, ResourceContainer newDirectResource) {
    if (neverlink) {
      return ResourceDependencies.empty().toProvider(label);
    }
    return AndroidResourcesProvider.create(
        label,
        NestedSetBuilder.<ResourceContainer>naiveLinkOrder()
            .addTransitive(transitiveResourceContainers)
            .addTransitive(directResourceContainers)
            .build(),
        NestedSetBuilder.<ResourceContainer>naiveLinkOrder().add(newDirectResource).build(),
        NestedSetBuilder.<Artifact>naiveLinkOrder()
            .addTransitive(transitiveResources)
            .addAll(newDirectResource.getResources())
            .build(),
        NestedSetBuilder.<Artifact>naiveLinkOrder()
            .addTransitive(transitiveAssets)
            .addAll(newDirectResource.getAssets())
            .build(),
        withDirectAndTransitive(newDirectResource.getManifest(), transitiveManifests),
        withDirectAndTransitive(newDirectResource.getAapt2RTxt(), transitiveAapt2RTxt),
        withDirectAndTransitive(newDirectResource.getSymbols(), transitiveSymbolsBin),
        withDirectAndTransitive(newDirectResource.getCompiledSymbols(), transitiveCompiledSymbols),
        withDirectAndTransitive(newDirectResource.getStaticLibrary(), transitiveStaticLib),
        withDirectAndTransitive(newDirectResource.getRTxt(), transitiveRTxt));
  }

  private static NestedSet<Artifact> withDirectAndTransitive(
      @Nullable Artifact direct, NestedSet<Artifact> transitive) {
    NestedSetBuilder<Artifact> builder = NestedSetBuilder.naiveLinkOrder();
    builder.addTransitive(transitive);
    if (direct != null) {
      builder.add(direct);
    }

    return builder.build();
  }

  /**
   * Create a new AndroidResourcesProvider from the dependencies of this library.
   *
   * <p>When a library doesn't export resources it should simply forward the current transitive and
   * direct resources to the consuming rule. This allows the consuming rule to make decisions about
   * the resource merging as if this library didn't exist.
   *
   * @param label The label of the library exporting this provider.
   * @return A provider with the current resources and label.
   */
  public AndroidResourcesProvider toProvider(Label label) {
    if (neverlink) {
      return ResourceDependencies.empty().toProvider(label);
    }
    return AndroidResourcesProvider.create(
        label,
        transitiveResourceContainers,
        directResourceContainers,
        transitiveResources,
        transitiveAssets,
        transitiveManifests,
        transitiveAapt2RTxt,
        transitiveSymbolsBin,
        transitiveCompiledSymbols,
        transitiveStaticLib,
        transitiveRTxt);
  }

  /**
   * Provides an NestedSet of the direct and transitive resources.
   *
   * @deprecated Rather than accessing the ResourceContainers, use other methods in this class to
   *     get the specific Artifacts you need instead.
   */
  @Deprecated
  public NestedSet<ResourceContainer> getResourceContainers() {
    return NestedSetBuilder.<ResourceContainer>naiveLinkOrder()
        .addTransitive(directResourceContainers)
        .addTransitive(transitiveResourceContainers)
        .build();
  }

  /**
   * @deprecated Rather than accessing the ResourceContainers, use other methods in this class to
   *     get the specific Artifacts you need instead.
   */
  @Deprecated
  public NestedSet<ResourceContainer> getTransitiveResourceContainers() {
    return transitiveResourceContainers;
  }

  /**
   * @deprecated Rather than accessing the ResourceContainers, use other methods in this class to
   *     get the specific Artifacts you need instead.
   */
  @Deprecated
  public NestedSet<ResourceContainer> getDirectResourceContainers() {
    return directResourceContainers;
  }

  public NestedSet<Artifact> getTransitiveResources() {
    return transitiveResources;
  }

  public NestedSet<Artifact> getTransitiveAssets() {
    return transitiveAssets;
  }

  public NestedSet<Artifact> getTransitiveManifests() {
    return transitiveManifests;
  }

  public NestedSet<Artifact> getTransitiveAapt2RTxt() {
    return transitiveAapt2RTxt;
  }

  public NestedSet<Artifact> getTransitiveSymbolsBin() {
    return transitiveSymbolsBin;
  }

  /**
   * @return The transitive closure of compiled symbols. Compiled symbols are zip files containing
   *     the compiled resource output of aapt2 compile
   */
  public NestedSet<Artifact> getTransitiveCompiledSymbols() {
    return transitiveCompiledSymbols;
  }

  public NestedSet<Artifact> getTransitiveStaticLib() {
    return transitiveStaticLib;
  }

  public NestedSet<Artifact> getTransitiveRTxt() {
    return transitiveRTxt;
  }
}
