import org.checkerframework.checker.lock.qual.*;
import org.checkerframework.checker.nullness.qual.*;
import org.checkerframework.dataflow.qual.*;

class ItselfExpressionCases {
    final Object somelock = new Object();

    private final @GuardedBy({"<self>"}) MyClass m = new MyClass();

    @Pure
    private @GuardedBy({"<self>"}) MyClass getm() {
        return m;
    }

    @Pure
    private @GuardedBy({"<self>"}) MyClass getm2(@GuardedBy("<self>") ItselfExpressionCases this) {
        // The following error is due to the precondition of the this.m field dereference not being
        // satisfied.
        // :: error: (lock.not.held)
        return m;
    }

    @Pure
    private Object getmfield() {
        // :: error: (lock.not.held)
        return getm().field;
    }

    public void arrayTest(final Object @GuardedBy("<self>") [] a1) {
        // :: error: (lock.not.held)
        Object a = a1[0];
        synchronized (a1) {
            a = a1[0];
        }
    }

    Object @GuardedBy("<self>") [] a2;

    @Pure
    public Object @GuardedBy("<self>") [] geta2() {
        return a2;
    }

    public void arrayTest() {
        // :: error: (lock.not.held)
        Object a = geta2()[0];
        synchronized (geta2()) {
            a = geta2()[0];
        }
    }

    public void testCheckPreconditions(
            final @GuardedBy("<self>") MyClass o,
            @GuardSatisfied Object gs,
            @GuardSatisfied MyClass gsMyClass) {
        // :: error: (lock.not.held)
        getm().field = new Object();
        synchronized (getm()) {
            getm().field = new Object();
        }

        // :: error: (lock.not.held)
        m.field = new Object();
        synchronized (m) {
            m.field = new Object();
        }

        // :: error: (lock.not.held)
        gs = m.field;
        synchronized (m) {
            gs = m.field;
        }

        // :: error: (lock.not.held)
        gs = getm().field;
        synchronized (getm()) {
            gs = getm().field;
        }

        // :: error: (lock.not.held)
        gsMyClass = getm();
        synchronized (getm()) {
            gsMyClass = getm();
        }

        // :: error: (lock.not.held) :: error: (contracts.precondition.not.satisfied)
        o.foo();
        synchronized (o) {
            // :: error: (contracts.precondition.not.satisfied)
            o.foo();
            synchronized (somelock) {
                o.foo();
            }
        }

        // :: error: (lock.not.held)
        o.foo2();
        synchronized (o) {
            o.foo2();
        }
    }

    class MyClass {
        Object field = new Object();

        @Holding("somelock")
        void foo(@GuardSatisfied MyClass this) {}

        void foo2(@GuardSatisfied MyClass this) {}

        void method(@GuardedBy("<self>") MyClass this) {
            // :: error: (lock.not.held) :: error: (contracts.precondition.not.satisfied)
            this.foo();
            // :: error: (lock.not.held):: error: (contracts.precondition.not.satisfied)
            foo();
            // :: error: (lock.not.held)
            synchronized (somelock) {
                // :: error: (lock.not.held)
                this.foo();
                // :: error: (lock.not.held)
                foo();
                synchronized (this) {
                    this.foo();
                    foo();
                }
            }

            // :: error: (lock.not.held)
            this.foo2();
            // :: error: (lock.not.held)
            foo2();
            synchronized (this) {
                this.foo2();
                foo2();
            }
        }
    }
}
