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

import org.codehaus.groovy.runtime.ReflectionMethodInvoker
import org.junit.jupiter.api.Test

import static groovy.test.GroovyAssert.assertScript
import static groovy.test.GroovyAssert.shouldFail

final class GroovyInterceptableTest {

    @Test
    void testMethodIntercept1() {
        def g = new GI()
        assert g.someInt() == 2806
        assert g.someUnexistingMethod() == 1
        assert g.toString() == "invokeMethodToString"
    }

    @Test
    void testMethodIntercept2() {
        def g = new GI()
        assert g.foo == 89
        g.foo = 90
        assert g.foo == 90
        // Should this be 1 or 90?
        assert g.getFoo() == 1
    }

    // GROOVY-3015
    @Test
    void testMethodIntercept3() {
        String shared = '''\
            import org.codehaus.groovy.runtime.InvokerHelper
            import org.codehaus.groovy.runtime.StringBufferWriter
            import static groovy.test.GroovyTestCase.assertEquals

            class Traceable implements GroovyInterceptable {
                private static int indent = 1
                Writer writer = new PrintWriter(System.out)
                Object invokeMethod(String name, Object args) {
                    writer.write('\\n' + ('  ' * indent) + 'Enter ' + name)
                    indent += 1
                    def result = InvokerHelper.getMetaClass(this).invokeMethod(this, name, args)
                    indent -= 1
                    writer.write('\\n' + ('  ' * indent) + 'Leave ' + name)
                    return result
                }
            }

            class Whatever extends Traceable {
                int inner() { return 1 }
                int outer() { return inner() }
                int shouldTraceOuterAndInnerMethod() { return outer() }
                def shouldTraceOuterAndInnerClosure = { -> return outer() }
            }

            def log = new StringBuffer()
            def obj = new Whatever(writer: new StringBufferWriter(log))
        '''

        assertScript shared + '''
            obj.shouldTraceOuterAndInnerMethod()

            assertEquals """
            |  Enter shouldTraceOuterAndInnerMethod
            |    Enter outer
            |      Enter inner
            |      Leave inner
            |    Leave outer
            |  Leave shouldTraceOuterAndInnerMethod""".stripMargin(), log.toString()
        '''

        assertScript shared + '''
            obj.shouldTraceOuterAndInnerClosure()

            assertEquals """
            |  Enter shouldTraceOuterAndInnerClosure
            |    Enter outer
            |      Enter inner
            |      Leave inner
            |    Leave outer
            |  Leave shouldTraceOuterAndInnerClosure""".stripMargin(), log.toString()
        '''
    }

    @Test
    void testMissingMethod1() {
        def obj = new GI2()
        shouldFail { obj.notAMethod() }
        assert 'missing' == obj.result
    }

    @Test
    void testMissingMethod2() {
        def obj = new GI2()
        shouldFail { obj.method() }
        assert 'missing' == obj.result
    }
}

//------------------------------------------------------------------------------

class GI implements GroovyInterceptable {
    def foo = 89
    int someInt() { 2806 }
    @Override
    String toString() { "originalToString" }
    @Override
    Object invokeMethod(String name, Object args) {
        if ("toString" == name)
            return "invokeMethodToString"
        else if ("someInt" == name)
            return ReflectionMethodInvoker.invoke(this, name, args)
        else
            return 1
    }
}

class GI2 implements GroovyInterceptable {
    def result = ""
    @Override
    def invokeMethod(String name, args) {
        def metaMethod = Foo.metaClass.getMetaMethod(name, args)
        if (metaMethod != null) return metaMethod.invoke(this, args)
        result += "missing"
        throw new MissingMethodException(name, Foo.class, args)
    }
    def method() {
        notAMethod()
    }
}
