// Copyright 2019 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.analysis;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant;
import com.google.devtools.build.lib.util.ClassName;
import java.util.List;

/**
 * Provides a user-friendly list of the {@link Fragment}s and {@link
 * com.google.devtools.build.lib.analysis.config.FragmentOptions} required by this target and its
 * transitive dependencies.
 *
 * <p>See {@link com.google.devtools.build.lib.analysis.config.RequiredFragmentsUtil} for details.
 */
// TODO(b/149094955): Make this more structured instead of storing raw strings.
@Immutable
public final class RequiredConfigFragmentsProvider implements TransitiveInfoProvider {

  @SerializationConstant
  public static final RequiredConfigFragmentsProvider EMPTY =
      new RequiredConfigFragmentsProvider(ImmutableSet.of());

  private final ImmutableSet<String> requiredConfigFragments;

  private RequiredConfigFragmentsProvider(ImmutableSet<String> requiredConfigFragments) {
    this.requiredConfigFragments = requiredConfigFragments;
  }

  public ImmutableSet<String> getRequiredConfigFragments() {
    return requiredConfigFragments;
  }

  /** Merges the values of two {@link RequiredConfigFragmentsProvider} instances. */
  public static RequiredConfigFragmentsProvider merge(
      RequiredConfigFragmentsProvider a, RequiredConfigFragmentsProvider b) {
    return builder().merge(a).merge(b).build();
  }

  /** Merges the values of one or more {@link RequiredConfigFragmentsProvider} instances. */
  public static RequiredConfigFragmentsProvider merge(
      List<RequiredConfigFragmentsProvider> providers) {
    checkArgument(!providers.isEmpty());
    if (providers.size() == 1) {
      return providers.get(0);
    }
    RequiredConfigFragmentsProvider.Builder merged = builder();
    for (RequiredConfigFragmentsProvider provider : providers) {
      merged.merge(provider);
    }
    return merged.build();
  }

  public static Builder builder() {
    return new Builder();
  }

  /** Builder for required config fragments. */
  public static final class Builder {
    private final ImmutableSortedSet.Builder<String> strings = ImmutableSortedSet.naturalOrder();

    private Builder() {}

    public Builder addOptionsClass(Class<? extends FragmentOptions> optionsClass) {
      strings.add(ClassName.getSimpleNameWithOuter(optionsClass));
      return this;
    }

    public Builder addOptionsClasses(Iterable<Class<? extends FragmentOptions>> optionsClasses) {
      return addClasses(optionsClasses);
    }

    public Builder addFragmentClasses(Iterable<Class<? extends Fragment>> fragmentClasses) {
      return addClasses(fragmentClasses);
    }

    private Builder addClasses(Iterable<? extends Class<?>> classes) {
      strings.addAll(Iterables.transform(classes, ClassName::getSimpleNameWithOuter));
      return this;
    }

    public Builder addDefine(String define) {
      strings.add("--define:" + define);
      return this;
    }

    public Builder addStarlarkOption(Label starlarkOption) {
      return addStarlarkOption(starlarkOption.toString());
    }

    public Builder addStarlarkOptions(Iterable<Label> starlarkOptions) {
      strings.addAll(Iterables.transform(starlarkOptions, Label::toString));
      return this;
    }

    public Builder addStarlarkOption(String starlarkOption) {
      strings.add(starlarkOption);
      return this;
    }

    public Builder merge(RequiredConfigFragmentsProvider provider) {
      strings.addAll(provider.requiredConfigFragments);
      return this;
    }

    public RequiredConfigFragmentsProvider build() {
      return new RequiredConfigFragmentsProvider(strings.build());
    }
  }
}

@@ -2464,24 +2464,112 @@
 ovider b) %7B%0A
+    if (a == EMPTY) %7B%0A      return b;%0A    %7D%0A    if (b == EMPTY) %7B%0A      return a;%0A    %7D%0A
     return b
@@ -2863,73 +2863,62 @@
     
-if (providers.size() == 1) %7B%0A      return providers.get(0);%0A    %7D
+RequiredConfigFragmentsProvider.Builder merged = null;
 %0A   
@@ -2953,35 +2953,26 @@
 ider
-.Builder merged = builder()
+ candidate = EMPTY
 ;%0A  
@@ -3044,48 +3044,317 @@
     
-merged.merge(provider);%0A    %7D%0A    return
+if (provider == EMPTY) %7B%0A        continue;%0A      %7D%0A      if (merged != null) %7B%0A        merged.merge(provider);%0A      %7D else if (candidate == EMPTY) %7B%0A        candidate = provider;%0A      %7D else %7B%0A        merged = builder().merge(candidate).merge(provider);%0A      %7D%0A    %7D%0A    return merged == null ? candidate :
  mer
@@ -5037,24 +5037,144 @@
 r build() %7B%0A
+      ImmutableSet%3CString%3E strings = this.strings.build();%0A      if (strings.isEmpty()) %7B%0A        return EMPTY;%0A      %7D%0A
       return
@@ -5217,24 +5217,16 @@
 (strings
-.build()
 );%0A    %7D

Make some performance optimizations by reusing a canonical `ReqiuiredConfigFragmentsProvider#EMPTY` instance and avoiding a copy if merging with `EMPTY`.