import org.checkerframework.checker.nullness.qual.*;
import org.checkerframework.dataflow.qual.Pure;

public class RequiresNonNullTest {

  @Nullable Object field1;
  @Nullable Object field2;

  @RequiresNonNull("field1")
  void method1() {
    field1.toString(); // OK, field1 is known to be non-null
    this.field1.toString(); // OK, field1 is known to be non-null
    // :: error: (dereference.of.nullable)
    field2.toString(); // error, might throw NullPointerException
  }

  @RequiresNonNull("field1")
  void method1also() {
    // ok, precondition satisfied by NNOE
    method1();
  }

  void method2() {
    field1 = new Object();
    method1(); // OK, satisfies method precondition
    field1 = null;
    // :: error: (contracts.precondition)
    method1(); // error, does not satisfy method precondition
  }

  protected @Nullable Object field;

  @RequiresNonNull("field")
  public void requiresNonNullField() {}

  public void clientFail(RequiresNonNullTest arg1) {
    // :: error: (contracts.precondition)
    arg1.requiresNonNullField();
  }

  public void clientOK(RequiresNonNullTest arg2) {
    arg2.field = new Object();
    // note that the following line works
    @NonNull Object o = arg2.field;

    arg2.requiresNonNullField(); // OK, field is known to be non-null
  }

  // TODO: forbid the field in @NNOE to be less visible than the method

  protected static @Nullable Object staticfield;

  @Pure
  @RequiresNonNull("staticfield")
  // :: warning: (purity.deterministic.void.method)
  public void reqStaticName() {
    reqStaticQualName();
  }

  @Pure
  @RequiresNonNull("RequiresNonNullTest.staticfield")
  // :: warning: (purity.deterministic.void.method)
  public void reqStaticQualName() {
    reqStaticName();
  }

  public void statClientOK(RequiresNonNullTest arg1) {
    staticfield = new Object();
    arg1.reqStaticName();

    staticfield = new Object();
    arg1.reqStaticQualName();

    RequiresNonNullTest.staticfield = new Object();
    arg1.reqStaticName();
    RequiresNonNullTest.staticfield = new Object();
    arg1.reqStaticQualName();
  }

  public void statClientFail(RequiresNonNullTest arg1) {
    // :: error: (contracts.precondition)
    arg1.reqStaticName();
    // :: error: (contracts.precondition)
    arg1.reqStaticQualName();
  }

  class NNOESubTest extends RequiresNonNullTest {
    public void subClientOK(NNOESubTest arg3) {
      arg3.field = new Object();
      arg3.requiresNonNullField();
    }

    public void subClientFail(NNOESubTest arg4) {
      // :: error: (contracts.precondition)
      arg4.requiresNonNullField();
    }

    public void subStat(NNOESubTest arg5) {
      RequiresNonNullTest.staticfield = new Object();
      arg5.reqStaticQualName();

      staticfield = new Object();
      arg5.reqStaticQualName();

      NNOESubTest.staticfield = new Object();
      arg5.reqStaticQualName();
    }
  }

  private @Nullable Object notHidden;

  class NNOEHidingTest extends RequiresNonNullTest {

    protected @Nullable String field;

    public void hidingClient1(NNOEHidingTest arg5) {
      arg5.field = "ha!";

      // TODO: The error message should say something about the hidden field.
      // :: error: (contracts.precondition)
      arg5.requiresNonNullField();

      // TODO: Add test like:
      // arg5.ensuresNonNullField();
      // arg5.requiresNonNullField();
    }

    public void hidingClient2(NNOEHidingTest arg6) {
      // :: error: (contracts.precondition)
      arg6.requiresNonNullField();
    }

    protected @Nullable Object notHidden;

    @RequiresNonNull("notHidden")
    void notHiddenTest() {
      // the field in the superclass is private -> don't complain about hiding
    }
  }

  static @Nullable Object o = "m";

  @RequiresNonNull("o")
  void test() {
    o = null;
  }

  @RequiresNonNull("thisShouldIssue1Error")
  // Test case for Issue 1051
  // https://github.com/typetools/checker-framework/issues/1051
  // :: error: (flowexpr.parse.error)
  void testIssue1051() {}
}
