import org.checkerframework.framework.qual.AnnotatedFor;
import testlib.util.SubQual;
import testlib.util.SuperQual;

class AnnotatedForTest {
    /*
         Test a mix of @SuppressWarnings with @AnnotatedFor. @SuppressWarnings should win, but only within the kinds of warnings it promises to suppress. It should win because
         it is a specific intent of suppressing warnings, whereas NOT suppressing warnings using AnnotatedFor is a default behavior, and SW is a user-specified behavior.
    */

    // Test unannotated class initializer - no warnings should be issued
    @SuperQual Object o1 = annotatedMethod(new Object());
    @SubQual Object o2 = annotatedMethod(new Object());
    Object o3 = unannotatedMethod(o2);
    Object o4 = unannotatedMethod(o1);

    static @SuperQual Object so1;
    static @SubQual Object so2;
    static Object so3, so4;

    // Test unannotated static initializer block - no warnings should be issued
    static {
        so1 = staticAnnotatedMethod(new Object());
        so2 = staticAnnotatedMethod(new Object());
        so3 = staticUnannotatedMethod(so2);
        so4 = staticUnannotatedMethod(so1);
    }

    @SuperQual Object o5;
    @SubQual Object o6;
    Object o7, o8;

    // Test unannotated nonstatic initializer block - no warnings should be issued
    {
        o5 = annotatedMethod(new Object());
        o6 = annotatedMethod(new Object());
        o7 = unannotatedMethod(o6);
        o8 = unannotatedMethod(o5);
    }

    // This method is @AnnotatedFor("subtyping") so it can cause errors to be issued by calling
    // other methods.
    @AnnotatedFor("subtyping")
    void method1() {
        // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since
        // @SuperQual is annotated with @DefaultQualifierInHierarchy.
        @SuperQual Object o1 = annotatedMethod(new Object());
        // :: error: (assignment.type.incompatible)
        @SubQual Object o2 = annotatedMethod(new Object());

        // When calling unannotatedMethod, we expect the conservative defaults.
        Object o3 = unannotatedMethod(o2);
        // :: error: (argument.type.incompatible)
        Object o4 = unannotatedMethod(o1);

        // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor
        // annotation.
        Object o5 = unannotatedMethod(o2);
        // :: error: (argument.type.incompatible)
        Object o6 = unannotatedMethod(o1);

        // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({})
        Object o7 = unannotatedMethod(o2);
        // :: error: (argument.type.incompatible)
        Object o8 = unannotatedMethod(o1);
    }

    @SuppressWarnings("all")
    @AnnotatedFor(
            "subtyping") // Same as method1, but the @SuppressWarnings overrides the @AnnotatedFor.
    void method2() {
        // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since
        // @SuperQual is annotated with @DefaultQualifierInHierarchy.
        @SuperQual Object o1 = annotatedMethod(new Object());
        @SubQual Object o2 = annotatedMethod(new Object());

        // When calling unannotatedMethod, we expect the conservative defaults.
        Object o3 = unannotatedMethod(o2);
        Object o4 = unannotatedMethod(o1);

        // Testing that @AnnotatedFor({}) behaves the same way as not putting an @AnnotatedFor
        // annotation.
        Object o5 = unannotatedMethod(o2);
        Object o6 = unannotatedMethod(o1);

        // Testing that @AnnotatedFor(a different typesystem) behaves the same way @AnnotatedFor({})
        Object o7 = unannotatedMethod(o2);
        Object o8 = unannotatedMethod(o1);
    }

    @SuppressWarnings("nullness")
    @AnnotatedFor("subtyping") // Similar to method1. The @SuppressWarnings does not override the
    // @AnnotatedFor because it suppressing warnings for a different typesystem.
    void method3() {
        // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since
        // @SuperQual is annotated with @DefaultQualifierInHierarchy.
        @SuperQual Object o1 = annotatedMethod(new Object());
        // :: error: (assignment.type.incompatible)
        @SubQual Object o2 = annotatedMethod(new Object());
    }

    @AnnotatedFor("subtyping")
    Object annotatedMethod(Object p) {
        return new Object();
    }

    Object unannotatedMethod(Object p) {
        return new Object();
    }

    @AnnotatedFor("subtyping")
    static Object staticAnnotatedMethod(Object p) {
        return new Object();
    }

    static Object staticUnannotatedMethod(Object p) {
        return new Object();
    }

    @AnnotatedFor({})
    Object unannotatedMethod2(Object p) {
        return new Object();
    }

    @AnnotatedFor("nullness")
    Object annotatedForADifferentTypeSystemMethod(Object p) {
        return new Object();
    }

    // Annotated for more than one type system
    @AnnotatedFor({"nullness", "subtyping"})
    void method4() {
        // :: error: (assignment.type.incompatible)
        @SubQual Object o2 = new @SuperQual Object();
    }

    // Different way of writing the checker name
    @AnnotatedFor("SubtypingChecker")
    void method5() {
        // :: error: (assignment.type.incompatible)
        @SubQual Object o2 = new @SuperQual Object();
    }

    // Different way of writing the checker name
    @AnnotatedFor("org.checkerframework.common.subtyping.SubtypingChecker")
    void method6() {
        // :: error: (assignment.type.incompatible)
        @SubQual Object o2 = new @SuperQual Object();
    }

    // Every method in this class should issue warnings for subtyping even if it's not marked with
    // @AnnotatedFor, unless it's marked with @SuppressWarnings.
    @AnnotatedFor("subtyping")
    class annotatedClass {
        // Test annotated class initializer
        // When calling annotatedMethod, we expect the usual (non-conservative) defaults, since
        // @SuperQual is annotated with @DefaultQualifierInHierarchy.
        @SuperQual Object o1 = annotatedMethod(new Object());
        // :: error: (assignment.type.incompatible)
        @SubQual Object o2 = annotatedMethod(new Object());

        // When calling unannotatedMethod, we expect the conservative defaults.
        Object o3 = unannotatedMethod(o2);
        // :: error: (argument.type.incompatible)
        Object o4 = unannotatedMethod(o1);

        @SuperQual Object o5;
        @SubQual Object o6;
        Object o7, o8;

        // Test annotated nonstatic initializer block
        {
            o5 = annotatedMethod(new Object());
            // :: error: (assignment.type.incompatible)
            o6 = annotatedMethod(new Object());
            o7 = unannotatedMethod(o6);
            // :: error: (argument.type.incompatible)
            o8 = unannotatedMethod(o5);
        }

        void method1() {
            // :: error: (assignment.type.incompatible)
            @SubQual Object o2 = new @SuperQual Object();
        }

        @SuppressWarnings("all")
        void method2() {
            @SubQual Object o2 = new @SuperQual Object();
        }
    }

    @AnnotatedFor("subtyping")
    static class staticAnnotatedForClass {
        static @SuperQual Object so1;
        static @SubQual Object so2;
        static Object so3, so4;

        // Test annotated static initializer block
        static {
            so1 = staticAnnotatedMethod(new Object());
            // :: error: (assignment.type.incompatible)
            so2 = staticAnnotatedMethod(new Object());
            so3 = staticUnannotatedMethod(so2);
            // :: error: (argument.type.incompatible)
            so4 = staticUnannotatedMethod(so1);
        }
    }

    @SuppressWarnings("all") // @SuppressWarnings("all") overrides @AnnotatedFor("subtyping")
    @AnnotatedFor("subtyping")
    class annotatedAndWarningsSuppressedClass {
        // Test annotated class initializer whose warnings are suppressed.
        @SuperQual Object o1 = annotatedMethod(new Object());
        @SubQual Object o2 = annotatedMethod(new Object());
        Object o3 = unannotatedMethod(o2);
        Object o4 = unannotatedMethod(o1);

        @SuperQual Object o5;
        @SubQual Object o6;
        Object o7, o8;

        // Test annotated nonstatic initializer block whose warnings are suppressed.
        {
            o5 = annotatedMethod(new Object());
            o6 = annotatedMethod(new Object());
            o7 = unannotatedMethod(o6);
            o8 = unannotatedMethod(o5);
        }

        void method1() {
            @SubQual Object o2 = new @SuperQual Object();
        }
    }

    @SuppressWarnings("all")
    @AnnotatedFor("subtyping")
    static class staticAnnotatedAndWarningsSuppressedClass {
        static @SuperQual Object so1;
        static @SubQual Object so2;
        static Object so3, so4;

        // Test annotated static initializer block whose warnings are suppressed.
        static {
            so1 = staticAnnotatedMethod(new Object());
            so2 = staticAnnotatedMethod(new Object());
            so3 = staticUnannotatedMethod(so2);
            so4 = staticUnannotatedMethod(so1);
        }
    }
}
