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

public class FlowNullness {

  public void testIf() {

    String str = "foo";
    @NonNull String a;
    // :: warning: (nulltest.redundant)
    if (str != null) {
      a = str;
    }

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;
  }

  public void testIfNoBlock() {

    String str = "foo";
    @NonNull String a;
    // :: warning: (nulltest.redundant)
    if (str != null) {
      a = str;
    }

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;
  }

  public void testElse() {

    String str = "foo";
    @NonNull String a;
    // :: warning: (nulltest.redundant)
    if (str == null) {
      testAssert();
    } else {
      a = str;
    }

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;
  }

  public void testElseNoBlock() {

    String str = "foo";
    @NonNull String a;
    // :: warning: (nulltest.redundant)
    if (str == null) {
      testAssert();
    } else {
      a = str;
    }

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;
  }

  public void testReturnIf() {

    String str = "foo";
    // :: warning: (nulltest.redundant)
    if (str == null) {
      testAssert();
      return;
    }

    @NonNull String a = str;

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;
  }

  public void testReturnElse() {

    String str = "foo";
    // :: warning: (nulltest.redundant)
    if (str != null) {
      testAssert();
    } else {
      return;
    }

    @NonNull String a = str;

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;
  }

  public void testThrowIf() {

    String str = "foo";
    // :: warning: (nulltest.redundant)
    if (str == null) {
      testAssert();
      throw new RuntimeException("foo");
    }

    @NonNull String a = str;

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;
  }

  public void testThrowElse() {

    String str = "foo";
    // :: warning: (nulltest.redundant)
    if (str != null) {
      testAssert();
    } else {
      throw new RuntimeException("foo");
    }

    @NonNull String a = str;

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;
  }

  public void testAssert() {

    String str = "foo";
    // :: warning: (nulltest.redundant)
    assert str != null;

    @NonNull String a = str;

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;
  }

  public void testWhile() {

    String str = "foo";
    // :: warning: (nulltest.redundant)
    while (str != null) {
      @NonNull String a = str;
      break;
    }

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;
  }

  public void testIfInstanceOf() {

    String str = "foo";
    @NonNull String a;
    if (str instanceof String) {
      a = str;
    }

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;
  }

  public void testNew() {

    String str = "foo";
    @NonNull String a = str;

    str = null;
    // :: error: (assignment)
    @NonNull String b = str;

    String s2 = new String();
    s2.toString();
  }

  public void testExit() {

    String str = "foo";
    // :: warning: (nulltest.redundant)
    if (str == null) {
      System.exit(0);
    }

    @NonNull String a = str;
  }

  void testMore() {
    String str = null + " foo";
    @NonNull String a = str;
  }

  void orderOfEvaluation() {
    class MyClass {
      @org.checkerframework.dataflow.qual.Pure
      public boolean equals(@Nullable Object o) {
        return o != null;
      }

      void test(@Nullable Object a, @Nullable Object b) {}
    }
    MyClass m = new MyClass();
    m.equals(m = null);

    MyClass n = new MyClass();
    // :: error: (dereference.of.nullable)
    n.test(n = null, n.toString()); // error

    MyClass o = null;
    // :: error: (dereference.of.nullable)
    o.equals(o == new MyClass()); // error
  }

  void instanceOf(@Nullable Object o) {
    if (o instanceof String) {
      // cannot be null here
      o.toString();
      return;
    }
    // :: error: (dereference.of.nullable)
    o.toString(); // error
  }

  public static void checkConditional1(@Nullable Object a) {
    if (a == null) {
    } else {
      a.getClass(); // not an error
    }
  }

  public static void checkConditional2(@Nullable Object a) {
    if (a == null) {
    } else if (a instanceof String) {
    } else {
      a.getClass(); // not an error
    }
  }

  public static String spf(String format, @NonNull Object[] args) {
    int current_arg = 0;
    Object arg = args[current_arg];
    if (false) {
      return arg.toString(); // not an error
    }
    if (arg instanceof long[]) {
      return "foo";
    } else {
      return arg.toString(); // still not an error
    }
  }

  void empty_makes_no_change() {
    String o1 = "not null!";
    if (false) {
      // empty branch
    } else {
      o1 = "still not null!";
    }
    System.out.println(o1.toString());
  }

  @org.checkerframework.dataflow.qual.Pure
  public boolean equals(@Nullable Object o) {
    if (!(o instanceof Integer)) {
      return false;
    }
    @NonNull Object nno = o;
    @NonNull Integer nni = (Integer) o;
    return true;
  }

  void while_set_and_test(@Nullable String s) {
    String line;
    // imagine "s" is "reader.readLine()" (but avoid use of libraries in unit tests)
    while ((line = s) != null) {
      line.trim();
    }
  }

  void equality_test(@Nullable String s) {
    @NonNull String n = "m";
    if (s == n) {
      s.toString();
    }
  }

  @Nullable Object returnNullable() {
    return null;
  }

  void testNullableCall() {
    if (returnNullable() != null) {
      // :: error: (dereference.of.nullable)
      returnNullable().toString(); // error
    }
  }

  void nonNullArg(@NonNull Object arg) {
    // empty body
  }

  void testNonNullArg(@Nullable Object arg) {
    // :: error: (argument)
    nonNullArg(arg); // error
    nonNullArg(arg); // no error
  }

  void test() {
    String[] s = null;
    // :: error: (dereference.of.nullable)
    for (int i = 0; i < s.length; ++i) { // error
      String m = s[i]; // fine.. s cannot be null
    }
  }

  private double @MonotonicNonNull []
      intersect; // = null; TODO: do we want to allow assignments of null to MonotonicNonNull?

  public void add_modified(double[] a, int count) {
    // System.out.println ("common: " + ArraysMDE.toString (a));
    // :: warning: (nulltest.redundant)
    if (a == null) {
      return;
    } else if (intersect == null) {
      intersect = a;
      return;
    }

    double[] tmp = new double[intersect.length];
  }
}
