/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package gls.annotations.closures

import gls.CompilableTestSupport

import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.reflect.Modifier

final class AnnotationClosureTest extends CompilableTestSupport {

    def answer = new Object() {
        def answer() { 42 }
    }

    void testGep3InClosure() {
        shouldCompile '''
            @interface Bar{Class value();}
            class Foo {
                @Bar({ sleep 1 })
                def baz() {}
            }
        '''
    }

    void testAllowedAsValueForAnnotationElementOfTypeClass() {
        shouldCompile '''
            import gls.annotations.closures.AnnWithClassElement

            @AnnWithClassElement(elem = { 1 + 2 })
            class Foo {}
        '''
    }

    // TODO: two compile errors instead of one, odd error message

    void testNotAllowedAsValueForAnnotationElementOfOtherType() {
        shouldNotCompile '''
            import gls.annotations.closures.AnnWithStringElement

            @AnnWithStringElement(elem = { 1 + 2 })
            class Foo {}
        '''
    }

    void testIsCompiledToPublicClass() {
        def closureClass = ClassWithAnnClosure.getAnnotation(AnnWithClassElement).elem()
        assert Modifier.isPublic(closureClass.modifiers)
    }

    void testDefaultValueIsCompiledToPublicClass() {
        def closureClass = ClosureAsDefaultValue.getAnnotation(AnnWithDefaultValue).elem()
        assert Modifier.isPublic(closureClass.modifiers)
    }

    void testCanBeUsedAsDefaultValue() {
        def closureClass = ClosureAsDefaultValue.getAnnotation(AnnWithDefaultValue).elem()
        def closure = closureClass.newInstance(null, null)

        assert closure.call() == 3
    }

    void testCanBeNested() {
        def closureClass = NestedClosure.getAnnotation(AnnWithClassElement).elem()
        def closure = closureClass.newInstance(null, null)

        assert closure.call(9) == 9
    }

    void testWorksOnInnerClass() {
        def closureClass = ClassWithAnnClosure.InnerClassWithAnnClosure.getAnnotation(AnnWithClassElement).elem()
        def closure = closureClass.newInstance(null, null)

        assert closure.call() == 3
    }

    void testWorksOnNestedClass() {
        def closureClass = ClassWithAnnClosure.NestedClassWithAnnClosure.getAnnotation(AnnWithClassElement).elem()
        def closure = closureClass.newInstance(null, null)

        assert closure.call() == 3
    }

    void testWorksOnNestedAnnotation() {
        def closureClass = NestedAnnotation.getAnnotation(AnnWithNestedAnn).elem().elem()
        def closure = closureClass.newInstance(null, null)

        assert closure.call() == 3
    }

    void testWorksOnNestedAnnotationWithDefaultValue() {
        def closureClass = NestedAnnotationWithDefault.getAnnotation(AnnWithNestedAnnWithDefault).elem().elem()
        def closure = closureClass.newInstance(null, null)

        assert closure.call() == 3
    }

    void testWorksOnAnnotationWithArray() {
        def closureClasses = ClassWithAnnArrayClosure.getAnnotation(AnnWithClassArrayElement).elem()
        assert closureClasses?.size() == 2
        assert closureClasses[0].newInstance(null, null)() == 3
        assert closureClasses[1].newInstance(null, null)() == 5
    }

    void testMayContainGString() {
        def closureClass = ClosureWithGString.getAnnotation(AnnWithClassElement).elem()
        def closure = closureClass.newInstance(null, null)

        assert closure.call([1, 2, 3]) == "list has 3 elements"
    }

    void testDoesNoHarmOnAnnotationWithSourceRetention() {
        shouldCompile '''
            import java.lang.annotation.*

            @Retention(RetentionPolicy.SOURCE)
            @interface AnnWithSourceRetention {
                Class elem()
            }

            @AnnWithSourceRetention(elem = { 1 + 2 })
            class Foo {}
        '''
    }

    void testDoesNoHarmOnAnnotationWithClassRetention() {
        shouldCompile '''
            import java.lang.annotation.*

            @Retention(RetentionPolicy.CLASS)
            @interface AnnWithClassRetention {
                Class elem()
            }

            @AnnWithClassRetention(elem = { 1 + 2 })
            class Foo {}
        '''
    }

    // GROOVY-7033
    void testAnnotationOnAnonymousInnerClassMethod() {
        assertScript '''
            import java.lang.annotation.*

            @Target(ElementType.METHOD)
            @Retention(RetentionPolicy.RUNTIME)
            @interface MethodAnno { Class<?> value() }

            @Target(ElementType.TYPE_USE)
            @Retention(RetentionPolicy.RUNTIME)
            @interface TypeUseAnno { Class<?> value() }

            def aic = new Object() {
                @MethodAnno(value = { -> })
                @TypeUseAnno(value = { -> })
                String toString() {}
            }

            def toString = aic.class.getMethod('toString')
            Annotation ann = toString.annotations[0]
            assert ann.annotationType().name == 'MethodAnno'
            ann = toString.annotatedReturnType.annotations[0]
            assert ann.annotationType().name == 'TypeUseAnno'
        '''
    }
}

@Retention(RetentionPolicy.RUNTIME)
@interface AnnWithClassElement {
    Class elem()
}

@Retention(RetentionPolicy.RUNTIME)
@interface AnnWithClassArrayElement {
    Class[] elem()
}

@Retention(RetentionPolicy.RUNTIME)
@interface AnnWithStringElement {
    String elem()
}

@AnnWithClassArrayElement(elem = [{ 1 + 2 }, { 2 + 3 }])
class ClassWithAnnArrayClosure {
}

@AnnWithClassElement(elem = { 1 + 2 })
class ClassWithAnnClosure {
    @AnnWithClassElement(elem = { 1 + 2 })
    class InnerClassWithAnnClosure {}

    @AnnWithClassElement(elem = { 1 + 2 })
    static class NestedClassWithAnnClosure {}
}

@JavaAnnotationWithClassElement(elem = { 1 + 2 })
class ClassWithJavaAnnClosure {}

@AnnWithClassElement(elem = { def nested = { it }; nested(it) })
class NestedClosure {}

@AnnWithDefaultValue
class ClosureAsDefaultValue {}

@Retention(RetentionPolicy.RUNTIME)
@interface AnnWithDefaultValue {
    Class elem() default { 1 + 2 }
}

@Retention(RetentionPolicy.RUNTIME)
@interface AnnWithNestedAnn {
    AnnWithClassElement elem()
}

@AnnWithNestedAnn(elem = @AnnWithClassElement(elem = { 1 + 2 }))
class NestedAnnotation {}

@Retention(RetentionPolicy.RUNTIME)
@interface AnnWithNestedAnnWithDefault {
    AnnWithDefaultValue elem()
}

@AnnWithNestedAnnWithDefault(elem = @AnnWithDefaultValue())
class NestedAnnotationWithDefault {}

@AnnWithClassElement(elem = { list -> "list has ${list.size()} elements" })
class ClosureWithGString {}
