// 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.cpp;

import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
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.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.starlarkbuildapi.cpp.LibraryToLinkApi;
import javax.annotation.Nullable;
import net.starlark.java.eval.Dict;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Printer;
import net.starlark.java.eval.Sequence;
import net.starlark.java.eval.StarlarkList;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.StarlarkThread;

/** Encapsulates information for linking a library. */
// The AutoValue implementation of this class already has a sizeable number of fields, meaning that
// instances have a surprising memory cost.
@Immutable
public abstract class LibraryToLink implements LibraryToLinkApi<Artifact, LtoBackendArtifacts> {

  public static final Depset.ElementType TYPE = Depset.ElementType.of(LibraryToLink.class);

  public static ImmutableList<Artifact> getDynamicLibrariesForRuntime(
      boolean linkingStatically, Iterable<LibraryToLink> libraries) {
    ImmutableList.Builder<Artifact> dynamicLibrariesForRuntimeBuilder = ImmutableList.builder();
    for (LibraryToLink libraryToLink : libraries) {
      Artifact artifact = libraryToLink.getDynamicLibraryForRuntimeOrNull(linkingStatically);
      if (artifact != null) {
        dynamicLibrariesForRuntimeBuilder.add(artifact);
      }
    }
    return dynamicLibrariesForRuntimeBuilder.build();
  }

  public static ImmutableList<Artifact> getDynamicLibrariesForLinking(
      NestedSet<LibraryToLink> libraries) {
    ImmutableList.Builder<Artifact> dynamicLibrariesForLinkingBuilder = ImmutableList.builder();
    for (LibraryToLink libraryToLink : libraries.toList()) {
      if (libraryToLink.getInterfaceLibrary() != null) {
        dynamicLibrariesForLinkingBuilder.add(libraryToLink.getInterfaceLibrary());
      } else if (libraryToLink.getDynamicLibrary() != null) {
        dynamicLibrariesForLinkingBuilder.add(libraryToLink.getDynamicLibrary());
      }
    }
    return dynamicLibrariesForLinkingBuilder.build();
  }

  private LibraryToLink() {}

  public abstract String getLibraryIdentifier();

  @Nullable
  public abstract ImmutableList<Artifact> getObjectFiles();

  @Nullable
  public abstract ImmutableMap<Artifact, LtoBackendArtifacts> getSharedNonLtoBackends();

  @Nullable
  public abstract LtoCompilationContext getLtoCompilationContext();

  @Nullable
  public abstract ImmutableList<Artifact> getPicObjectFiles();

  @Nullable
  public abstract ImmutableMap<Artifact, LtoBackendArtifacts> getPicSharedNonLtoBackends();

  @Nullable
  public abstract LtoCompilationContext getPicLtoCompilationContext();

  public abstract AutoLibraryToLink.Builder toBuilder();

  @Override
  public final boolean isImmutable() {
    return true; // immutable and Starlark-hashable
  }

  @Nullable
  public final Artifact getDynamicLibraryForRuntimeOrNull(boolean linkingStatically) {
    if (getDynamicLibrary() == null) {
      return null;
    }
    if (linkingStatically && (getStaticLibrary() != null || getPicStaticLibrary() != null)) {
      return null;
    }
    return getDynamicLibrary();
  }

  @Override
  public final Sequence<Artifact> getObjectFilesForStarlark() {
    ImmutableList<Artifact> objectFiles = getObjectFiles();
    return objectFiles == null ? StarlarkList.empty() : StarlarkList.immutableCopyOf(objectFiles);
  }

  @Override
  public final Sequence<Artifact> getLtoBitcodeFilesForStarlark() {
    LtoCompilationContext ctx = getLtoCompilationContext();
    return ctx == null ? StarlarkList.empty() : StarlarkList.immutableCopyOf(ctx.getBitcodeFiles());
  }

  @Override
  public final boolean getMustKeepDebugForStarlark(StarlarkThread thread) throws EvalException {
    CcModule.checkPrivateStarlarkificationAllowlist(thread);
    return getMustKeepDebug();
  }

  @Nullable
  @Override
  public final Dict<Artifact, LtoBackendArtifacts> getSharedNonLtoBackendsForStarlark(
      StarlarkThread thread) throws EvalException {
    CcModule.checkPrivateStarlarkificationAllowlist(thread);
    ImmutableMap<Artifact, LtoBackendArtifacts> backends = getSharedNonLtoBackends();
    return backends != null ? Dict.immutableCopyOf(backends) : null;
  }

  @Override
  public final Sequence<Artifact> getPicObjectFilesForStarlark() {
    ImmutableList<Artifact> objectFiles = getPicObjectFiles();
    return objectFiles == null ? StarlarkList.empty() : StarlarkList.immutableCopyOf(objectFiles);
  }

  @Override
  public final Sequence<Artifact> getPicLtoBitcodeFilesForStarlark() {
    LtoCompilationContext ctx = getPicLtoCompilationContext();
    return ctx == null ? StarlarkList.empty() : StarlarkList.immutableCopyOf(ctx.getBitcodeFiles());
  }

  @Nullable
  @Override
  public final Dict<Artifact, LtoBackendArtifacts> getPicSharedNonLtoBackendsForStarlark(
      StarlarkThread thread) throws EvalException {
    CcModule.checkPrivateStarlarkificationAllowlist(thread);
    ImmutableMap<Artifact, LtoBackendArtifacts> backends = getPicSharedNonLtoBackends();
    return backends != null ? Dict.immutableCopyOf(backends) : null;
  }

  LinkerInputs.LibraryToLink getStaticLibraryToLink() {
    return LinkerInputs.newInputLibrary(
        Preconditions.checkNotNull(getStaticLibrary(), this),
        getAlwayslink()
            ? ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY
            : ArtifactCategory.STATIC_LIBRARY,
        getLibraryIdentifier(),
        getObjectFiles(),
        getLtoCompilationContext(),
        getSharedNonLtoBackends(),
        getMustKeepDebug(),
        getDisableWholeArchive());
  }

  LinkerInputs.LibraryToLink getPicStaticLibraryToLink() {
    return LinkerInputs.newInputLibrary(
        Preconditions.checkNotNull(getPicStaticLibrary(), this),
        getAlwayslink()
            ? ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY
            : ArtifactCategory.STATIC_LIBRARY,
        getLibraryIdentifier(),
        getPicObjectFiles(),
        getPicLtoCompilationContext(),
        getPicSharedNonLtoBackends(),
        getMustKeepDebug(),
        getDisableWholeArchive());
  }

  LinkerInputs.LibraryToLink getDynamicLibraryToLink() {
    Artifact dynamicLibrary = Preconditions.checkNotNull(getDynamicLibrary(), this);
    if (getResolvedSymlinkDynamicLibrary() != null) {
      return LinkerInputs.solibLibraryToLink(
          dynamicLibrary, getResolvedSymlinkDynamicLibrary(), getLibraryIdentifier());
    }
    return LinkerInputs.newInputLibrary(
        dynamicLibrary,
        ArtifactCategory.DYNAMIC_LIBRARY,
        getLibraryIdentifier(),
        /*objectFiles=*/ ImmutableSet.of(),
        LtoCompilationContext.EMPTY,
        /*sharedNonLtoBackends=*/ ImmutableMap.of(),
        getMustKeepDebug(),
        getDisableWholeArchive());
  }

  LinkerInputs.LibraryToLink getInterfaceLibraryToLink() {
    Artifact interfaceLibrary = Preconditions.checkNotNull(getInterfaceLibrary(), this);
    if (getResolvedSymlinkInterfaceLibrary() != null) {
      return LinkerInputs.solibLibraryToLink(
          interfaceLibrary, getResolvedSymlinkInterfaceLibrary(), getLibraryIdentifier());
    }
    return LinkerInputs.newInputLibrary(
        interfaceLibrary,
        ArtifactCategory.INTERFACE_LIBRARY,
        getLibraryIdentifier(),
        /*objectFiles=*/ ImmutableSet.of(),
        LtoCompilationContext.EMPTY,
        /*sharedNonLtoBackends=*/ ImmutableMap.of(),
        getMustKeepDebug(),
        getDisableWholeArchive());
  }

  // TODO(plf): This is just needed for Go, do not expose to Starlark and try to remove it. This was
  // introduced to let a linker input declare that it needs debug info in the executable.
  // Specifically, this was introduced for linking Go into a C++ binary when using the gccgo
  // compiler.
  abstract boolean getMustKeepDebug();

  abstract boolean getDisableWholeArchive();

  @Override
  public final void debugPrint(Printer printer, StarlarkSemantics semantics) {
    printer.append("<LibraryToLink(");
    printer.append(
        Joiner.on(", ")
            .skipNulls()
            .join(
                mapEntry("object", getObjectFiles()),
                mapEntry("pic_objects", getPicObjectFiles()),
                mapEntry("static_library", getStaticLibrary()),
                mapEntry("pic_static_library", getPicStaticLibrary()),
                mapEntry("dynamic_library", getDynamicLibrary()),
                mapEntry("resolved_symlink_dynamic_library", getResolvedSymlinkDynamicLibrary()),
                mapEntry("interface_library", getInterfaceLibrary()),
                mapEntry(
                    "resolved_symlink_interface_library", getResolvedSymlinkInterfaceLibrary()),
                mapEntry("alwayslink", getAlwayslink())));
    printer.append(")>");
  }

  @Nullable
  private static String mapEntry(String keyName, @Nullable Object value) {
    return value == null ? null : keyName + "=" + value;
  }

  public static AutoLibraryToLink.Builder builder() {
    return new AutoValue_LibraryToLink_AutoLibraryToLink.Builder()
        .setMustKeepDebug(false)
        .setAlwayslink(false)
        .setDisableWholeArchive(false);
  }

  /** Builder for {@link LibraryToLink}. */
  public interface Builder {

    AutoLibraryToLink.Builder setLibraryIdentifier(String libraryIdentifier);

    AutoLibraryToLink.Builder setStaticLibrary(Artifact staticLibrary);

    AutoLibraryToLink.Builder setObjectFiles(ImmutableList<Artifact> objectFiles);

    AutoLibraryToLink.Builder setLtoCompilationContext(LtoCompilationContext ltoCompilationContext);

    AutoLibraryToLink.Builder setSharedNonLtoBackends(
        ImmutableMap<Artifact, LtoBackendArtifacts> sharedNonLtoBackends);

    AutoLibraryToLink.Builder setPicStaticLibrary(Artifact picStaticLibrary);

    AutoLibraryToLink.Builder setPicObjectFiles(ImmutableList<Artifact> picObjectFiles);

    AutoLibraryToLink.Builder setPicLtoCompilationContext(
        LtoCompilationContext picLtoCompilationContext);

    AutoLibraryToLink.Builder setPicSharedNonLtoBackends(
        ImmutableMap<Artifact, LtoBackendArtifacts> picSharedNonLtoBackends);

    AutoLibraryToLink.Builder setDynamicLibrary(Artifact dynamicLibrary);

    AutoLibraryToLink.Builder setResolvedSymlinkDynamicLibrary(
        Artifact resolvedSymlinkDynamicLibrary);

    AutoLibraryToLink.Builder setInterfaceLibrary(Artifact interfaceLibrary);

    AutoLibraryToLink.Builder setResolvedSymlinkInterfaceLibrary(
        Artifact resolvedSymlinkInterfaceLibrary);

    AutoLibraryToLink.Builder setAlwayslink(boolean alwayslink);

    AutoLibraryToLink.Builder setMustKeepDebug(boolean mustKeepDebug);

    AutoLibraryToLink.Builder setDisableWholeArchive(boolean disableWholeArchive);

    LibraryToLink build();
  }

  /** {@link AutoValue}-backed implementation. */
  @AutoValue
  abstract static class AutoLibraryToLink extends LibraryToLink {

    @Nullable
    @Override // Remove @StarlarkMethod.
    public abstract Artifact getStaticLibrary();

    @Nullable
    @Override // Remove @StarlarkMethod.
    public abstract Artifact getPicStaticLibrary();

    @Nullable
    @Override // Remove @StarlarkMethod.
    public abstract Artifact getDynamicLibrary();

    @Nullable
    @Override // Remove @StarlarkMethod.
    public abstract Artifact getResolvedSymlinkDynamicLibrary();

    @Nullable
    @Override // Remove @StarlarkMethod.
    public abstract Artifact getInterfaceLibrary();

    @Nullable
    @Override // Remove @StarlarkMethod.
    public abstract Artifact getResolvedSymlinkInterfaceLibrary();

    @Override // Remove @StarlarkMethod.
    public abstract boolean getAlwayslink();

    @Memoized
    @Override
    LinkerInputs.LibraryToLink getStaticLibraryToLink() {
      return super.getStaticLibraryToLink();
    }

    @Memoized
    @Override
    LinkerInputs.LibraryToLink getPicStaticLibraryToLink() {
      return super.getPicStaticLibraryToLink();
    }

    @Memoized
    @Override
    LinkerInputs.LibraryToLink getDynamicLibraryToLink() {
      return super.getDynamicLibraryToLink();
    }

    @Memoized
    @Override
    LinkerInputs.LibraryToLink getInterfaceLibraryToLink() {
      return super.getInterfaceLibraryToLink();
    }

    @AutoValue.Builder
    public abstract static class Builder implements LibraryToLink.Builder {

      Builder() {}

      abstract AutoLibraryToLink autoBuild();

      @Override
      public final LibraryToLink build() {
        LibraryToLink result = autoBuild();
        Preconditions.checkNotNull(result.getLibraryIdentifier(), result);
        Preconditions.checkState(
            result.getResolvedSymlinkDynamicLibrary() == null || result.getDynamicLibrary() != null,
            result);
        Preconditions.checkState(
            result.getResolvedSymlinkInterfaceLibrary() == null
                || result.getResolvedSymlinkInterfaceLibrary() != null,
            result);
        Preconditions.checkState(
            result.getStaticLibrary() != null
                || result.getPicStaticLibrary() != null
                || result.getDynamicLibrary() != null
                || result.getInterfaceLibrary() != null,
            result);

        return result;
      }
    }
  }
}
