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

/*
 * Miscellaneous tests based on problems found when checking Daikon.
 */
public class DaikonTests {

  // Based on a problem found in PPtSlice.
  class Bug1 {
    @Nullable Object field;

    public void cond1() {
      if (this.hashCode() > 6 && Bug1Other.field != null) {
        // spurious dereference error
        Bug1Other.field.toString();
      }
    }

    public void cond1(Bug1 p) {
      if (this.hashCode() > 6 && p.field != null) {
        // works
        p.field.toString();
      }
    }

    public void cond2() {
      if (Bug1Other.field != null && this.hashCode() > 6) {
        // works
        Bug1Other.field.toString();
      }
    }
  }

  // Based on problem found in PptCombined.
  // Not yet able to reproduce the problem :-(

  class Bug2Data {
    Bug2Data(Bug2Super o) {}
  }

  class Bug2Super {
    public @MonotonicNonNull Bug2Data field;
  }

  class Bug2 extends Bug2Super {
    private void m() {
      field = new Bug2Data(this);
      field.hashCode();
    }
  }

  // Based on problem found in FloatEqual.
  class Bug3 {
    @EnsuresNonNullIf(expression = "derived", result = true)
    public boolean isDerived() {
      return (derived != null);
    }

    @Nullable Object derived;

    void good1(Bug3 v1) {
      if (!v1.isDerived() || !(5 > 9)) {
        return;
      }
      v1.derived.hashCode();
    }

    // TODO: this is currently not supported
    //        void good2(Bug3 v1) {
    //            if (!(v1.isDerived() && (5 > 9)))
    //                return;
    //            v1.derived.hashCode();
    //        }

    void good3(Bug3 v1) {
      if (!v1.isDerived() || !(v1 instanceof Bug3)) {
        return;
      }
      Object o = (Object) v1.derived;
      o.hashCode();
    }
  }

  // Based on problem found in PrintInvariants.
  // Not yet able to reproduce the problem :-(

  class Bug4 {
    @MonotonicNonNull Object field;

    void m(Bug4 p) {
      if (false && p.field != null) {
        p.field.hashCode();
      }
    }
  }

  // Based on problem found in chicory.Runtime:
  class Bug5 {
    @Nullable Object clazz;

    @EnsuresNonNull("clazz")
    void init() {
      clazz = new Object();
    }

    void test(Bug5 b) {
      if (b.clazz == null) {
        b.init();
      }

      // The problem is:
      // In the "then" branch, we have in "nnExpr" that "clazz" is non-null.
      // In the "else" branch, we have in "annos" that the variable is non-null.
      // However, as these are facts in two different representations, the merge keeps
      // neither!
      //
      // no error message expected
      b.clazz.hashCode();
    }
  }

  // From LimitedSizeSet.  The following initialization of the values array
  // has caused a NullPointerException.
  class Bug6<T> {
    protected @Nullable T @Nullable [] values;

    public Bug6() {
      // :: warning: [unchecked] unchecked cast
      @Nullable T[] new_values_array = (@Nullable T[]) new @Nullable Object[4];
      values = new_values_array;
    }
  }
}

class Bug1Other {
  static @Nullable Object field;
}
