import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.interning.qual.InternMethod;
import org.checkerframework.checker.interning.qual.Interned;

public @Interned class InternedClass2 {
  private final int i;

  // @UnknownInterned is the default annotation on constructor results even for @Interned classes.
  private InternedClass2(int i) {
    // Type of "this" inside a constructor of an @Interned class is @UnknownInterned.
    // :: error: (assignment)
    @Interned InternedClass2 that = this;
    this.i = i;
  }

  InternedClass2 factory(int i) {
    // :: error: (interned.object.creation) :: error: (method.invocation)
    new InternedClass2(i).someMethod(); // error, call to constructor on for @Interned class.
    (new InternedClass2(i)).intern(); // ok, call to constructor receiver to @InternMethod
    ((((new InternedClass2(i))))).intern(); // ok, call to constructor receiver to @InternMethod
    return new InternedClass2(i).intern(); // ok, call to constructor receiver to @InternMethod
  }

  void someMethod() {
    // Type of "this" inside a method (not marked @InternedMethod) is @Interned,
    // assuming the method is declared in an @Interned class.
    @Interned InternedClass2 that = this; // ok
  }

  private static Map<Integer, InternedClass2> pool = new HashMap<>();

  @InternMethod
  public InternedClass2 intern() {
    // Type of "this" inside an @InternMethod is @UnknownInterned
    // :: error: (assignment)
    @Interned InternedClass2 that = this;
    if (!pool.containsKey(this.i)) {
      // The above check proves "this" is interned.
      @SuppressWarnings("interning:assignment")
      @Interned InternedClass2 internedThis = this;
      pool.put(this.i, internedThis);
    }
    return pool.get(this.i);
  }

  @Override // ok, no override invalid receiver error is issued.
  public String toString() {
    @Interned InternedClass2 that = this; // ok
    return super.toString();
  }

  @Override
  public boolean equals(Object object) {
    if (this == object) {
      return true;
    }
    if (object == null || getClass() != object.getClass()) {
      return false;
    }

    InternedClass2 that = (InternedClass2) object;

    return i == that.i;
  }

  @Override
  public int hashCode() {
    return i;
  }

  public boolean hasNodeOfType(Class<?> type) {
    if (type == this.getClass()) {
      return true;
    }
    return false;
  }
}
