/*
 *  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.
 */

import org.junit.jupiter.api.Test

import static groovy.test.GroovyAssert.assertScript

final class ClosuresSpecTest {

    static void sink(Closure cl) {}

    @Test
    void testClosureSyntax() {
        sink
                // tag::closure_syntax_1[]
                { item++ }                                          // <1>
                // end::closure_syntax_1[]
        sink
                // tag::closure_syntax_1bis[]
                { -> item++ }                                       // <2>
                // end::closure_syntax_1bis[]
        sink
                // tag::closure_syntax_2[]
                { println it }                                      // <3>
                // end::closure_syntax_2[]

        sink
                // tag::closure_syntax_3[]
                { it -> println it }                                // <4>
                // end::closure_syntax_3[]

        sink
                // tag::closure_syntax_4[]
                { name -> println name }                            // <5>
                // end::closure_syntax_4[]

        sink
                // tag::closure_syntax_5[]
                { String x, int y ->                                // <6>
                    println "hey ${x} the value is ${y}"
                }
                // end::closure_syntax_5[]

        sink
                // tag::closure_syntax_6[]
                { reader ->                                         // <7>
                    def line = reader.readLine()
                    line.trim()
                }
                // end::closure_syntax_6[]
    }

    @Test
    void testAssignClosureToAVariable() {
        // tag::closure_is_an_instance_of_Closure[]
        def listener = { e -> println "Clicked on $e.source" }      // <1>
        assert listener instanceof Closure
        Closure callback = { println 'Done!' }                      // <2>
        Closure<Boolean> isTextFile = {
            File it -> it.name.endsWith('.txt')                     // <3>
        }
        // end::closure_is_an_instance_of_Closure[]
    }

    @Test
    void testCallClosure() {
        // tag::closure_call_1[]
        def code = { 123 }
        // end::closure_call_1[]
        // tag::closure_call_1_direct[]
        assert code() == 123
        // end::closure_call_1_direct[]
        // tag::closure_call_1_explicit[]
        assert code.call() == 123
        // end::closure_call_1_explicit[]

        // tag::closure_call_2[]
        def isOdd = { int i -> i%2 != 0 }                           // <1>
        assert isOdd(3) == true                                     // <2>
        assert isOdd.call(2) == false                               // <3>

        def isEven = { it%2 == 0 }                                  // <4>
        assert isEven(3) == false                                   // <5>
        assert isEven.call(2) == true                               // <6>
        // end::closure_call_2[]
    }

    @Test
    void testClosureParameters() {
        // tag::closure_param_declaration[]
        def closureWithOneArg = { str -> str.toUpperCase() }
        assert closureWithOneArg('groovy') == 'GROOVY'

        def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }
        assert closureWithOneArgAndExplicitType('groovy') == 'GROOVY'

        def closureWithTwoArgs = { a,b -> a+b }
        assert closureWithTwoArgs(1,2) == 3

        def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a+b }
        assert closureWithTwoArgsAndExplicitTypes(1,2) == 3

        def closureWithTwoArgsAndOptionalTypes = { a, int b -> a+b }
        assert closureWithTwoArgsAndOptionalTypes(1,2) == 3

        def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a+b }
        assert closureWithTwoArgAndDefaultValue(1) == 3
        // end::closure_param_declaration[]
    }

    @Test
    void testImplicitIt() {
        assertScript '''
        // tag::implicit_it[]
        def greeting = { "Hello, $it!" }
        assert greeting('Patrick') == 'Hello, Patrick!'
        // end::implicit_it[]
        '''

        assertScript '''
        // tag::implicit_it_equiv[]
        def greeting = { it -> "Hello, $it!" }
        assert greeting('Patrick') == 'Hello, Patrick!'
        // end::implicit_it_equiv[]
        '''

        assertScript '''
        // tag::closure_no_arg_def[]
        def magicNumber = { -> 42 }
        // end::closure_no_arg_def[]
        try {
            // tag::closure_no_arg_fail[]
            // this call will fail because the closure doesn't accept any argument
            magicNumber(11)
            // end::closure_no_arg_fail[]
            assert false
        } catch (MissingMethodException) {
        }
        '''
    }

    @Test
    void testClosureVargs() {
        // tag::closure_vargs[]
        def concat1 = { String... args -> args.join('') }           // <1>
        assert concat1('abc','def') == 'abcdef'                     // <2>
        def concat2 = { String[] args -> args.join('') }            // <3>
        assert concat2('abc', 'def') == 'abcdef'

        def multiConcat = { int n, String... args ->                // <4>
            args.join('')*n
        }
        assert multiConcat(2, 'abc','def') == 'abcdefabcdef'
        // end::closure_vargs[]

    }

    @Test
    void testThisObject() {
        assertScript '''
        // tag::closure_this[]
        class Enclosing {
            void run() {
                def whatIsThisObject = { getThisObject() }          // <1>
                assert whatIsThisObject() == this                   // <2>
                def whatIsThis = { this }                           // <3>
                assert whatIsThis() == this                         // <4>
            }
        }
        class EnclosedInInnerClass {
            class Inner {
                Closure cl = { this }                               // <5>
            }
            void run() {
                def inner = new Inner()
                assert inner.cl() == inner                          // <6>
            }
        }
        class NestedClosures {
            void run() {
                def nestedClosures = {
                    def cl = { this }                               // <7>
                    cl()
                }
                assert nestedClosures() == this                     // <8>
            }
        }
        // end::closure_this[]
        new Enclosing().run()
        new EnclosedInInnerClass().run()
        new NestedClosures().run()
'''
        assertScript '''
            // tag::closure_this_call[]
            class Person {
                String name
                int age
                String toString() { "$name is $age years old" }

                String dump() {
                    def cl = {
                        String msg = this.toString()               // <1>
                        println msg
                        msg
                    }
                    cl()
                }
            }
            def p = new Person(name:'Janice', age:74)
            assert p.dump() == 'Janice is 74 years old'
            // end::closure_this_call[]
        '''
    }

    @Test
    void testOwner() {
        assertScript '''
        // tag::closure_owner[]
        class Enclosing {
            void run() {
                def whatIsOwnerMethod = { getOwner() }               // <1>
                assert whatIsOwnerMethod() == this                   // <2>
                def whatIsOwner = { owner }                          // <3>
                assert whatIsOwner() == this                         // <4>
            }
        }
        class EnclosedInInnerClass {
            class Inner {
                Closure cl = { owner }                               // <5>
            }
            void run() {
                def inner = new Inner()
                assert inner.cl() == inner                           // <6>
            }
        }
        class NestedClosures {
            void run() {
                def nestedClosures = {
                    def cl = { owner }                               // <7>
                    cl()
                }
                assert nestedClosures() == nestedClosures            // <8>
            }
        }
        // end::closure_owner[]
        new Enclosing().run()
        new EnclosedInInnerClass().run()
        new NestedClosures().run()
'''
    }

    @Test
    void testClosureDelegate() {
        assertScript '''
        // tag::delegate_is_owner[]
        class Enclosing {
            void run() {
                def cl = { getDelegate() }                          // <1>
                def cl2 = { delegate }                              // <2>
                assert cl() == cl2()                                // <3>
                assert cl() == this                                 // <4>
                def enclosed = {
                    { -> delegate }.call()                          // <5>
                }
                assert enclosed() == enclosed                       // <6>
            }
        }
        // end::delegate_is_owner[]
        new Enclosing().run()
        '''

        assertScript '''
            // tag::change_delegate_classes[]
            class Person {
                String name
            }
            class Thing {
                String name
            }

            def p = new Person(name: 'Norman')
            def t = new Thing(name: 'Teapot')

            // end::change_delegate_classes[]

            // tag::change_delegate_closure[]
            def upperCasedName = { delegate.name.toUpperCase() }
            // end::change_delegate_closure[]

            // tag::change_delegate_asserts[]
            upperCasedName.delegate = p
            assert upperCasedName() == 'NORMAN'
            upperCasedName.delegate = t
            assert upperCasedName() == 'TEAPOT'
            // end::change_delegate_asserts[]

            // tag::delegate_alernative[]
            def target = p
            def upperCasedNameUsingVar = { target.name.toUpperCase() }
            assert upperCasedNameUsingVar() == 'NORMAN'
            // end::delegate_alernative[]

        '''
    }

    @Test
    void testDelegationStrategy() {
        assertScript '''
                // tag::delegation_strategy_intro[]
                class Person {
                    String name
                }
                def p = new Person(name:'Igor')
                def cl = { name.toUpperCase() }                 // <1>
                cl.delegate = p                                 // <2>
                assert cl() == 'IGOR'                           // <3>
                // end::delegation_strategy_intro[]
            '''
    }

    @Test
    void testOwnerFirst() {
        assertScript '''
            // tag::closure_owner_first[]
            class Person {
                String name
                def pretty = { "My name is $name" }             // <1>
                String toString() {
                    pretty()
                }
            }
            class Thing {
                String name                                     // <2>
            }

            def p = new Person(name: 'Sarah')
            def t = new Thing(name: 'Teapot')

            assert p.toString() == 'My name is Sarah'           // <3>
            p.pretty.delegate = t                               // <4>
            assert p.toString() == 'My name is Sarah'           // <5>
            // end::closure_owner_first[]

            // tag::closure_delegate_first[]
            p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
            assert p.toString() == 'My name is Teapot'
            // end::closure_delegate_first[]
        '''
    }

    @Test
    void testDelegateOnly() {
        assertScript '''
            // tag::delegate_only[]
            class Person {
                String name
                int age
                def fetchAge = { age }
            }
            class Thing {
                String name
            }

            def p = new Person(name:'Jessica', age:42)
            def t = new Thing(name:'Printer')
            def cl = p.fetchAge
            cl.delegate = p
            assert cl() == 42          // <1>
            cl.delegate = t
            assert cl() == 42          // <1>

            cl.resolveStrategy = Closure.DELEGATE_ONLY
            cl.delegate = p
            assert cl() == 42          // <2>
            cl.delegate = t
            try {
                cl()                   // <3>
                assert false
            } catch (MissingPropertyException ex) {
                // "age" is not defined on the delegate
            }
            // end::delegate_only[]
        '''
    }

    @Test
    void testDelegateOnlyPropertyMissing() {
        assertScript '''
            // tag::delegate_only_prop_missing[]
            class Person {
                String name
                int age
                def fetchAge = { age }
            }
            class Thing {
                String name
                def propertyMissing(String name) { -1 }
            }

            def p = new Person(name:'Jessica', age:42)
            def t = new Thing(name:'Printer')
            def cl = p.fetchAge
            cl.resolveStrategy = Closure.DELEGATE_FIRST
            cl.delegate = p
            assert cl() == 42
            cl.delegate = t
            assert cl() == -1
            // end::delegate_only_prop_missing[]
        '''
    }

    @Test
    void testGStringEager() {
        // tag::gstring_eager_intro[]
        def x = 1
        def gs = "x = ${x}"
        assert gs == 'x = 1'
        // end::gstring_eager_intro[]
        /* do not uncomment, this is used in documentation!
        // tag::gstring_eager_outro[]
        x = 2
        assert gs == 'x = 2'
        // end::gstring_eager_outro[]
        */
        x = 2
        assert gs == 'x = 1'
    }

    @Test
    void testGStringLazy() {
        // tag::gstring_lazy[]
        def x = 1
        def gs = "x = ${-> x}"
        assert gs == 'x = 1'

        x = 2
        assert gs == 'x = 2'
        // end::gstring_lazy[]
    }

    @Test
    void testGStringWithMutation() {
        assertScript '''
            // tag::gstring_mutation[]
            class Person {
                String name
                String toString() { name }          // <1>
            }
            def sam = new Person(name:'Sam')        // <2>
            def lucy = new Person(name:'Lucy')      // <3>
            def p = sam                             // <4>
            def gs = "Name: ${p}"                   // <5>
            assert gs == 'Name: Sam'                // <6>
            p = lucy                                // <7>
            assert gs == 'Name: Sam'                // <8>
            sam.name = 'Lucy'                       // <9>
            assert gs == 'Name: Lucy'               // <10>
            // end::gstring_mutation[]
        '''
    }

    @Test
    void testGStringWithoutMutation() {
        assertScript '''
            // tag::gstring_no_mutation[]
            class Person {
                String name
                String toString() { name }
            }
            def sam = new Person(name:'Sam')
            def lucy = new Person(name:'Lucy')
            def p = sam
            // Create a GString with lazy evaluation of "p"
            def gs = "Name: ${-> p}"
            assert gs == 'Name: Sam'
            p = lucy
            assert gs == 'Name: Lucy'
            // end::gstring_no_mutation[]
        '''
    }

    @Test
    void testLeftCurry() {
        // tag::left_curry[]
        def nCopies = { int n, String str -> str*n }    // <1>
        def twice = nCopies.curry(2)                    // <2>
        assert twice('bla') == 'blabla'                 // <3>
        assert twice('bla') == nCopies(2, 'bla')        // <4>
        // end::left_curry[]
    }

    @Test
    void testRightCurry() {
        // tag::right_curry[]
        def nCopies = { int n, String str -> str*n }    // <1>
        def blah = nCopies.rcurry('bla')                // <2>
        assert blah(2) == 'blabla'                      // <3>
        assert blah(2) == nCopies(2, 'bla')             // <4>
        // end::right_curry[]
    }

    @Test
    void testNCurry() {
        // tag::ncurry[]
        def volume = { double l, double w, double h -> l*w*h }      // <1>
        def fixedWidthVolume = volume.ncurry(1, 2d)                 // <2>
        assert volume(3d, 2d, 4d) == fixedWidthVolume(3d, 4d)       // <3>
        def fixedWidthAndHeight = volume.ncurry(1, 2d, 4d)          // <4>
        assert volume(3d, 2d, 4d) == fixedWidthAndHeight(3d)        // <5>
        // end::ncurry[]
    }

    @Test
    void testMemoize() {
        // tag::naive_fib[]
        def fib
        fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }
        assert fib(15) == 610 // slow!
        // end::naive_fib[]
        // tag::memoized_fib[]
        fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }.memoize()
        assert fib(25) == 75025 // fast!
        // end::memoized_fib[]
    }

    @Test
    void testComposition() {
        // tag::closure_composition[]
        def plus2  = { it + 2 }
        def times3 = { it * 3 }

        def times3plus2 = plus2 << times3
        assert times3plus2(3) == 11
        assert times3plus2(4) == plus2(times3(4))

        def plus2times3 = times3 << plus2
        assert plus2times3(3) == 15
        assert plus2times3(5) == times3(plus2(5))

        // reverse composition
        assert times3plus2(3) == (times3 >> plus2)(3)
        // end::closure_composition[]

    }

    @Test
    void testTrampoline() {
        // tag::trampoline[]
        def factorial
        factorial = { int n, def accu = 1G ->
            if (n < 2) return accu
            factorial.trampoline(n - 1, n * accu)
        }
        factorial = factorial.trampoline()

        assert factorial(1)    == 1
        assert factorial(3)    == 1 * 2 * 3
        assert factorial(1000) // == 402387260.. plus another 2560 digits
        // end::trampoline[]
    }
}
