// Test case for issue 2432:
// https://github.com/typetools/checker-framework/issues/2432

import java.util.List;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;

public class Issue2432 {

  void jdkAnnotation(List<@PolyNull Object> nl, @Nullable Object no, @PolyNull Object po) {
    // :: error: (argument)
    nl.add(null);
    // :: error: (argument)
    nl.add(no);
    // OK
    nl.add(po);
  }

  // receiver's poly annotations in declaration are different from the ones in invocation
  void polyReceiverType(
      TypeArgClass<@PolyNull Object> tc,
      @NonNull Object nno,
      @Nullable Object no,
      @PolyNull Object po) {
    // :: error: (argument)
    Object object = tc.echo(no);
    // :: error: (assignment)
    nno = tc.echo(po);
    // No error. Return value remains @PolyNull.
    // Note po's @PolyNull is unsubstitutable (from parameter but not from declaration)
    po = tc.echo(po);
  }

  // the following two methods tests pesudo assignment of arguments with poly annotation
  void polyAssignment(TypeArgClass<@NonNull Object> nnc, @PolyNull Object po) {
    // :: error: (argument)
    nnc.echo(po);
  }

  void polyAssignment2(TypeArgClass<@Nullable Object> nc, @PolyNull Object po) {
    // No error
    nc.echo(po);
    // :: error: (assignment)
    po = nc.echo(po);
  }

  // a "foo function" with 2 poly annotations, where one of them appears in type argument
  // purpose: test invocation without using explicit receiver
  @PolyNull Object foo2PolyTypeArg(
      TypeArgClass<@PolyNull Object> pc, @PolyNull Object po, @NonNull Object nno) {
    return pc.add(nno, po);
  }

  // lub tests without receiver
  // lub combination: (@Nullable, @Nullable) = @Nullable
  void lubWithTypeArgNoReceiver1(TypeArgClass<@Nullable Object> nc, @Nullable Object no) {
    @NonNull Object nno = new Object();
    // No error
    foo2PolyTypeArg(nc, no, nno);
  }

  // lub combination: (@NonNull, @NonNull) = @NonNull
  void lubWithTypeArgNoReceiver2(TypeArgClass<@NonNull Object> nnc, @NonNull Object nno) {
    // No error
    foo2PolyTypeArg(nnc, nno, nno);
  }

  // lub combination: (@Nullable, @NonNull) = @Nullable
  void lubWithTypeArgNoReceiver3(TypeArgClass<@Nullable Object> nc, @NonNull Object nno) {
    // No error
    foo2PolyTypeArg(nc, nno, nno);
  }

  // lub combination: (@NonNull, @Nullable) = @Nullable
  void lubWithTypeArgNoReceiver4(TypeArgClass<@NonNull Object> nnc, @Nullable Object no) {
    // :: error: (argument)
    foo2PolyTypeArg(nnc, no, new Object());
  }

  // lub test with receiver
  // T dummy in tripleAdd is to ensure poly annotations from declaration is handled separately
  void lubWithReceiver(
      TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) {
    // :: error: (argument)
    pc.tripleAdd(no, nno, no);
    // No error
    pc.tripleAdd(no, nno, nno);
  }

  // ensure poly annotations from declaration is handled separately from poly from other context
  void declarationPolyInParameter(
      TypeArgClass<@PolyNull Object> pc, @Nullable Object no, @NonNull Object nno) {
    // No error
    pc.echo(nno, no);

    // the invocation is valid, while the assignment is not
    // :: error: (assignment)
    @NonNull Object nonnull = pc.echo(nno, no);
  }

  private class TypeArgClass<T> {
    @PolyNull Object add(@PolyNull Object obj, T dummy) {
      return obj;
    }

    @PolyNull Object tripleAdd(@PolyNull Object o1, @PolyNull Object o2, T dummy) {
      return o1;
    }

    T echo(T obj) {
      return obj;
    }

    T echo(T obj, @PolyNull Object dummy) {
      return obj;
    }
  }
}
