// Copyright 2018 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.databinding;

import static com.google.devtools.build.lib.rules.android.AndroidStarlarkData.fromNoneable;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.collect.nestedset.Depset;
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.BuiltinProvider;
import com.google.devtools.build.lib.packages.NativeInfo;
import com.google.devtools.build.lib.starlarkbuildapi.android.DataBindingV2ProviderApi;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.Sequence;
import javax.annotation.Nullable;

/**
 * A provider that exposes this enables <a
 * href="https://developer.android.com/topic/libraries/data-binding/index.html">data binding</a>
 * version 2 on its resource processing and Java compilation.
 */
public final class DataBindingV2Provider extends NativeInfo
    implements DataBindingV2ProviderApi<Artifact> {

  public static final Provider PROVIDER = new Provider();

  private final ImmutableList<Artifact> classInfos;

  private final ImmutableList<Artifact> setterStores;

  private final NestedSet<Artifact> transitiveBRFiles;

  /**
   * The label and java package of this rule and any rules that this rule exports.
   */
  @Nullable
  private final ImmutableList<LabelJavaPackagePair> labelAndJavaPackages;

  private final NestedSet<LabelJavaPackagePair> transitiveLabelAndJavaPackages;

  public DataBindingV2Provider(
      ImmutableList<Artifact> classInfos,
      ImmutableList<Artifact> setterStores,
      NestedSet<Artifact> transitiveBRFiles,
      ImmutableList<LabelJavaPackagePair> labelAndJavaPackages,
      NestedSet<LabelJavaPackagePair> transitiveLabelAndJavaPackages) {
    super(PROVIDER);
    this.classInfos = classInfos;
    this.setterStores = setterStores;
    this.transitiveBRFiles = transitiveBRFiles;
    this.labelAndJavaPackages = labelAndJavaPackages;
    this.transitiveLabelAndJavaPackages = transitiveLabelAndJavaPackages;
  }

  @Override
  public ImmutableList<Artifact> getClassInfos() {
    return classInfos;
  }

  @Override
  public ImmutableList<Artifact> getSetterStores() {
    return setterStores;
  }

  @Override
  public Depset /*<Artifact>*/ getTransitiveBRFilesForStarlark() {
    return Depset.of(Artifact.TYPE, transitiveBRFiles);
  }

  public NestedSet<Artifact> getTransitiveBRFiles() {
    return transitiveBRFiles;
  }

  @Override
  @Nullable
  public ImmutableList<LabelJavaPackagePair> getLabelAndJavaPackages() {
    return labelAndJavaPackages;
  }

  @Override
  public Depset /*<LabelJavaPackagePair>*/ getTransitiveLabelAndJavaPackagesForStarlark() {
    return Depset.of(LabelJavaPackagePair.TYPE, transitiveLabelAndJavaPackages);
  }

  public NestedSet<LabelJavaPackagePair> getTransitiveLabelAndJavaPackages() {
    return transitiveLabelAndJavaPackages;
  }

  public static DataBindingV2Provider createProvider(
      Artifact setterStoreFile,
      Artifact classInfoFile,
      Artifact brFile,
      String label,
      String javaPackage,
      // ugh these *Api types do not help one bit
      Iterable<? extends DataBindingV2ProviderApi<Artifact>> databindingV2ProvidersInDeps,
      Iterable<? extends DataBindingV2ProviderApi<Artifact>> databindingV2ProvidersInExports) {

    ImmutableList.Builder<Artifact> setterStoreFiles = ImmutableList.builder();
    if (setterStoreFile != null) {
      setterStoreFiles.add(setterStoreFile);
    }

    ImmutableList.Builder<Artifact> classInfoFiles = ImmutableList.builder();
    if (classInfoFile != null) {
      classInfoFiles.add(classInfoFile);
    }

    NestedSetBuilder<Artifact> brFiles = NestedSetBuilder.stableOrder();
    if (brFile != null) {
      brFiles.add(brFile);
    }

    NestedSetBuilder<LabelJavaPackagePair> transitiveLabelAndJavaPackages =
        NestedSetBuilder.stableOrder();
    ImmutableList.Builder<LabelJavaPackagePair> labelAndJavaPackages = ImmutableList.builder();

    if (label != null && javaPackage != null) {
      LabelJavaPackagePair labelAndJavaPackage = new LabelJavaPackagePair(label, javaPackage);
      labelAndJavaPackages.add(labelAndJavaPackage);
      transitiveLabelAndJavaPackages.add(labelAndJavaPackage);
    }

    if (databindingV2ProvidersInDeps != null) {

      for (DataBindingV2ProviderApi<Artifact> p : databindingV2ProvidersInDeps) {
        DataBindingV2Provider provider = (DataBindingV2Provider) p;
        brFiles.addTransitive(provider.getTransitiveBRFiles());
        transitiveLabelAndJavaPackages.addTransitive(provider.getTransitiveLabelAndJavaPackages());
      }
    }

    if (databindingV2ProvidersInExports != null) {

      // Add all of the information from providers from exported targets, so that targets which
      // depend on this target appear to depend on the exported targets.
      for (DataBindingV2ProviderApi<Artifact> p : databindingV2ProvidersInExports) {
        DataBindingV2Provider provider = (DataBindingV2Provider) p;
        setterStoreFiles.addAll(provider.getSetterStores());
        classInfoFiles.addAll(provider.getClassInfos());
        brFiles.addTransitive(provider.getTransitiveBRFiles());
        labelAndJavaPackages.addAll(provider.getLabelAndJavaPackages());
        transitiveLabelAndJavaPackages.addTransitive(provider.getTransitiveLabelAndJavaPackages());
      }
    }

    return new DataBindingV2Provider(
        classInfoFiles.build(),
        setterStoreFiles.build(),
        brFiles.build(),
        labelAndJavaPackages.build(),
        transitiveLabelAndJavaPackages.build());
  }

  /** The provider can construct the DataBindingV2Provider provider. */
  public static class Provider extends BuiltinProvider<DataBindingV2Provider>
      implements DataBindingV2ProviderApi.Provider<Artifact> {

    private Provider() {
      super(NAME, DataBindingV2Provider.class);
    }

    @Override
    public DataBindingV2ProviderApi<Artifact> createInfo(
        Object setterStoreFile,
        Object classInfoFile,
        Object brFile,
        Object label,
        Object javaPackage,
        Sequence<?> databindingV2ProvidersInDeps, // <DataBindingV2Provider>
        Sequence<?> databindingV2ProvidersInExports) // <DataBindingV2Provider>
        throws EvalException {

      return createProvider(
          fromNoneable(setterStoreFile, Artifact.class),
          fromNoneable(classInfoFile, Artifact.class),
          fromNoneable(brFile, Artifact.class),
          fromNoneable(label, String.class),
          fromNoneable(javaPackage, String.class),
          databindingV2ProvidersInDeps == null
              ? null
              : ImmutableList.copyOf(
                  Sequence.cast(
                      databindingV2ProvidersInDeps,
                      DataBindingV2Provider.class,
                      "databinding_v2_providers_in_deps")),
          databindingV2ProvidersInExports == null
              ? null
              : ImmutableList.copyOf(
                  Sequence.cast(
                      databindingV2ProvidersInExports,
                      DataBindingV2Provider.class,
                      "databinding_v2_providers_in_exports")));
    }
  }
}
