// Test case for Issue 1633:
// https://github.com/typetools/checker-framework/issues/1633

import java.util.function.Supplier;
import org.checkerframework.checker.nullness.qual.*;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.Covariant;

public class Issue1633 {

  // supplyNullable is a supplier that may return null.
  // supplyNonNull is a supplier that does not return null.

  void foo1(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
    // :: error: (argument)
    @Nullable String str = o.orElseGetUnannotated(supplyNullable);
  }

  void foo2(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
    @Nullable String str1 = o.orElseGetNullable(supplyNullable);
  }

  void foo2nw(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
    @Nullable String str1 = o.orElseGetNullableNoWildcard(supplyNullable);
  }

  void foo3(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
    // :: error: (argument)
    @Nullable String str2 = o.orElseGetNonNull(supplyNullable);
  }

  void foo4(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
    @Nullable String str3 = o.orElseGetPolyNull(supplyNullable);
  }

  void foo4nw(Optional1633<String> o, Supplier<@Nullable String> supplyNullable) {
    @Nullable String str3 = o.orElseGetPolyNullNoWildcard(supplyNullable);
  }

  void foo41(Optional1633<String> o) {
    // This is a false postive because inference doesn't work with poly qualifiers.
    // :: error: (return)
    @Nullable String str3 = o.orElseGetPolyNull(() -> null);
  }

  void foo41nw(Optional1633<String> o) {
    // This is a false postive because inference doesn't work with poly qualifiers.
    // :: error: (return)
    @Nullable String str3 = o.orElseGetPolyNullNoWildcard(() -> null);
  }

  void foo5(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
    @NonNull String str = o.orElseGetUnannotated(supplyNonNull);
  }

  void foo6(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
    // :: error: (assignment)
    @NonNull String str1 = o.orElseGetNullable(supplyNonNull);
  }

  void foo6nw(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
    // :: error: (assignment)
    @NonNull String str1 = o.orElseGetNullableNoWildcard(supplyNonNull);
  }

  void foo7(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
    @NonNull String str2 = o.orElseGetNonNull(supplyNonNull);
  }

  void foo8(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
    @NonNull String str3 = o.orElseGetPolyNull(supplyNonNull);
  }

  void foo8nw(Optional1633<String> o, Supplier<@NonNull String> supplyNonNull) {
    @NonNull String str3 = o.orElseGetPolyNullNoWildcard(supplyNonNull);
  }
}

// From the JDK
@Covariant(0)
final
@NonNull class Optional1633<T extends @Nullable Object> {

  /** If non-null, the value; if null, indicates no value is present. */
  private final @Nullable T value = null;

  // TODO: there are conceptually two versions of this method:
  //   public @Nullable T orElseGet(Supplier<? extends @Nullable T> other) {
  //   public @NonNull T orElseGet(Supplier<? extends @NonNull T> other) {
  // Issue #1633 says that this annotation doesn't help at all:
  //   public @PolyNull T orElseGet(Supplier<? extends @PolyNull T> other) {
  // but it does seem to work in this test case.
  public T orElseGetUnannotated(Supplier<? extends T> other) {
    return value != null ? value : other.get();
  }

  public @Nullable T orElseGetNullable(Supplier<@Nullable ? extends @Nullable T> other) {
    return value != null ? value : other.get();
  }

  public @Nullable T orElseGetNullableNoWildcard(Supplier<? extends @Nullable T> other) {
    // The commented-out line fails to typecheck.
    // return value != null ? value : other.get();
    if (value != null) {
      return value;
    } else {
      return other.get();
    }
  }

  public @NonNull T orElseGetNonNull(Supplier<@NonNull ? extends @NonNull T> other) {
    return value != null ? value : other.get();
  }

  public @PolyNull T orElseGetPolyNull(Supplier<@PolyNull ? extends @PolyNull T> other) {
    return value != null ? value : other.get();
  }

  public @PolyNull T orElseGetPolyNullNoWildcard(Supplier<? extends @PolyNull T> other) {
    // The commented-out line fails to typecheck.
    // return value != null ? value : other.get();
    if (value != null) {
      return value;
    } else {
      return other.get();
    }
  }
}
