// Note that this file is a near duplicate in /nullness and /nullness-uninit

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

@org.checkerframework.framework.qual.DefaultQualifier(Nullable.class)
public class RawTypesBounded {

  class Bad {
    @NonNull String field;

    public Bad() {
      // :: error: (method.invocation)
      this.init(); // error
      // :: error: (method.invocation)
      init(); // error

      this.field = "field"; // valid
      // :: error: (assignment)
      this.field = null; // error
      field = "field"; // valid
      // :: error: (assignment)
      field = null; // error
    }

    void init() {
      output(this.field.length()); // valid
    }
  }

  class A {
    @NonNull String field;

    public A() {
      this.field = "field"; // valid
      field = "field"; // valid
      this.init(); // valid
      init(); // valid
    }

    public void init(@UnknownInitialization A this) {
      // :: error: (dereference.of.nullable)
      output(this.field.length());
    }

    public void initExpl2(@UnknownInitialization A this) {
      // :: error: (argument)
      output(this.field);
    }

    public void initImpl1(@UnknownInitialization A this) {
      // :: error: (dereference.of.nullable)
      output(field.length());
    }

    public void initImpl2(@UnknownInitialization A this) {
      // :: error: (argument)
      output(field);
    }
  }

  class B extends A {
    @NonNull String otherField;

    public B() {
      super();
      // :: error: (assignment)
      this.otherField = null; // error
      this.otherField = "otherField"; // valid
    }

    @Override
    public void init(@UnknownInitialization B this) {
      // :: error: (dereference.of.nullable)
      output(this.field.length()); // error (TODO: substitution)
      super.init(); // valid
    }

    public void initImpl1(@UnknownInitialization B this) {
      // :: error: (dereference.of.nullable)
      output(field.length()); // error (TODO: substitution)
    }

    public void initExpl2(@UnknownInitialization B this) {
      // :: error: (dereference.of.nullable)
      output(this.otherField.length()); // error
    }

    public void initImpl2(@UnknownInitialization B this) {
      // :: error: (dereference.of.nullable)
      output(otherField.length()); // error
    }

    void other() {
      init(); // valid
      this.init(); // valid
    }

    void otherRaw(@UnknownInitialization B this) {
      init(); // valid
      this.init(); // valid
    }
  }

  class C extends B {

    @NonNull String[] strings;

    @Override
    public void init(@UnknownInitialization C this) {
      // :: error: (dereference.of.nullable)
      output(this.strings.length); // error
      System.out.println(); // valid
    }
  }

  // To test whether the argument is @NonNull and @Initialized
  static void output(@NonNull Object o) {}

  class D extends C {
    @Override
    public void init(@UnknownInitialization D this) {
      this.field = "s";
      output(this.field.length());
    }
  }

  class MyTest {
    int i;

    MyTest(int i) {
      this.i = i;
    }

    void myTest(@UnknownInitialization MyTest this) {
      i++;
    }
  }

  class AllFieldsInitialized {
    long elapsedMillis = 0;
    long startTime = 0;

    // If all fields have an initializer, then the type of "this"
    // should still not be non-raw (there might be uninitilized subclasses)
    public AllFieldsInitialized() {
      // :: error: (method.invocation)
      nonRawMethod();
    }

    public void nonRawMethod() {}
  }

  class AFSIICell {
    AllFieldsSetInInitializer afsii;
  }

  class AllFieldsSetInInitializer {
    long elapsedMillis;
    long startTime;

    public AllFieldsSetInInitializer() {
      elapsedMillis = 0;
      // :: error: (method.invocation)
      nonRawMethod();
      startTime = 0;
      // :: error: (method.invocation)
      nonRawMethod(); // still error (subclasses...)
    }

    public AllFieldsSetInInitializer(boolean b) {
      // :: error: (method.invocation)
      nonRawMethod();
    }

    public void nonRawMethod() {}
  }

  class ConstructorInvocations {
    int v;

    public ConstructorInvocations(int v) {
      this.v = v;
    }

    public ConstructorInvocations() {
      this(0);
      // :: error: (method.invocation)
      nonRawMethod();
    }

    public void nonRawMethod() {}
  }

  class MethodAccess {
    public MethodAccess() {
      @NonNull String s = string();
    }

    public @NonNull String string(@UnknownInitialization MethodAccess this) {
      return "nonnull";
    }
  }

  void cast(@UnknownInitialization Object... args) {

    @SuppressWarnings("rawtypes")
    // :: error: (assignment)
    Object[] argsNonRaw1 = args;

    @SuppressWarnings("cast")
    Object[] argsNonRaw2 = (Object[]) args;
  }

  // default qualifier is @Nullable, so this is OK.
  class RawAfterConstructorBad {
    Object o;

    RawAfterConstructorBad() {}
  }

  class RawAfterConstructorOK1 {
    @Nullable Object o;

    RawAfterConstructorOK1() {}
  }

  class RawAfterConstructorOK2 {
    int a;

    RawAfterConstructorOK2() {}
  }
}
