import java.util.List;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.framework.test.*;
import testlib.util.*;

class Basic2 {

    // basic tests to make sure everything works
    void t1(@Odd String p1, String p2) {
        String l1 = p1;
        // :: error: (assignment.type.incompatible)
        @Odd String l2 = p2;
    }

    // basic local variable flow sensitivity
    void t2(@Odd String p1, String p2, boolean b1) {
        String l1 = p1;
        if (b1) {
            l1 = p2;
        }
        // :: error: (assignment.type.incompatible)
        @Odd String l3 = l1;

        l1 = p1;
        while (b1) {
            l1 = p2;
        }
        // :: error: (assignment.type.incompatible)
        @Odd String l4 = l1;
    }

    void t2b(@Odd String p1, String p2, @Odd String p3, boolean b1) {
        String l1 = p1;

        if (b1) {
            l1 = p3;
        }
        @Odd String l3 = l1;

        while (b1) {
            l1 = p3;
        }
        @Odd String l4 = l1;
    }

    // return statement
    void t3(@Odd String p1, String p2, boolean b1) {
        String l1 = p1;
        if (b1) {
            l1 = p2;
            return;
        }
        @Odd String l3 = l1;
    }

    // simple throw statement
    void t4(@Odd String p1, String p2, boolean b1) {
        String l1 = p1;
        if (b1) {
            l1 = p2;
            throw new RuntimeException();
        }
        @Odd String l3 = l1;
    }

    class C {
        C c;
        String f1, f2, f3;
        @Odd String g1, g2, g3;
    }

    // fields
    void t5(@Odd String p1, String p2, boolean b1, C c1, C c2) {
        c1.f1 = p1;
        @Odd String l1 = c1.f1;
        c2.f2 = p2; // assignment to f2 does not change knowledge about f1
        c1.f2 = p2;
        @Odd String l2 = c1.f1;
        c2.f1 = p2;
        // :: error: (assignment.type.incompatible)
        @Odd String l3 = c1.f1;
    }

    // fields
    void t6(@Odd String p1, String p2, boolean b1, C c1, C c2) {
        c1.f1 = p1;
        c1.c.f1 = p1;
        @Odd String l2 = c1.c.f1;
        c1.f1 = p2;
        // :: error: (assignment.type.incompatible)
        @Odd String l3 = c1.f1;

        c1.f1 = p1;
        c1.c.f1 = p1;
        @Odd String l4 = c1.c.f1;
        c2.c = c2;
        // :: error: (assignment.type.incompatible)
        @Odd String l5 = c1.f1;
        // :: error: (assignment.type.incompatible)
        @Odd String l6 = c1.c.f1;
    }

    // fields
    void t6b(@Odd String p1, String p2, boolean b1, C c1, C c2) {
        if (b1) {
            c1.f1 = p1;
        }
        // :: error: (assignment.type.incompatible)
        @Odd String l1 = c1.f1;

        if (b1) {
            c1.f1 = p1;
        } else {
            c1.f1 = p1;
        }
        @Odd String l2 = c1.f1;
    }

    // method calls
    void nonpure() {}

    @Pure
    int pure() {
        return 1;
    }

    void t7(@Odd String p1, String p2, boolean b1, C c1, C c2) {
        c1.f1 = p1;
        nonpure();
        // :: error: (assignment.type.incompatible)
        @Odd String l1 = c1.f1;

        c1.f1 = p1;
        pure();
        @Odd String l2 = c1.f1;
    }

    // array accesses
    void t8(@Odd String a1[], String a2[], String p3) {
        String l1 = a1[0];

        // :: error: (assignment.type.incompatible)
        @Odd String l2 = a2[0];

        @Odd String l3 = l1;

        a1[0] = l1;
        a1[1] = l3;

        // :: error: (assignment.type.incompatible)
        a1[2] = p3;

        a2[0] = l1;
        a2[1] = l3;
        a2[2] = p3;
    }

    // self type
    void t9(@Odd Basic2 this) {
        @Odd Basic2 l1 = this;
    }

    // generics
    public <T extends String> void t10a(T p1, @Odd T p2) {
        T l1 = p1;
        p1 = l1;
        T l2 = p2;
        p2 = l2;
        // :: error: (assignment.type.incompatible)
        @Odd T l3 = p1;
        @Odd T l4 = p2;
    }

    public <T extends @Odd String> void t10b(T p1, @Odd T p2) {
        T l1 = p1;
        p1 = l1;
        T l2 = p2;
        p2 = l2;
        @Odd T l3 = p1;
        @Odd T l4 = p2;
    }

    // for-each loop
    void t11(@Odd String p1, String p2, List<String> list, List<@Odd String> oddList) {
        // :: error: (enhancedfor.type.incompatible)
        for (@Odd String i : list) {}
        for (@Odd String i : oddList) {
            @Odd String l1 = i;
        }
        for (@Odd String i : oddList) {
            // :: error: (assignment.type.incompatible)
            i = p2;
        }
        for (String i : oddList) {
            // @Odd String l3 = i;
        }
    }

    // cast without annotations
    void t12(@Odd String p1, String p2, boolean b1) {
        @Odd String l1 = (String) p1;
    }

    // final fields
    class CF {
        final String f1;
        CF c;

        void nonpure() {};

        CF(@Odd String p1) {
            f1 = p1;
            nonpure();
            @Odd String l1 = f1;
        }

        void CF_t1(@Odd String p1, CF o) {
            if (f1 == p1) {
                nonpure();
                @Odd String l1 = f1;
            }
        }
    }

    // final fields with initializer
    class A {
        final @Odd String f1 = null;
        final String f2 = f1;

        void A_t1() {
            @Odd String l1 = f2;
        }
    }
}
