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

import groovy.test.GroovyTestCase

import static groovy.util.ProxyGenerator.INSTANCE as generator

final class ProxyGeneratorTest extends GroovyTestCase {

    void testAggregateFromBaseClass() {
        Map map = [myMethodB: {'the new B'}, myMethodX: {'the injected X'}]
        def testClass = generator.instantiateAggregateFromBaseClass(map, TestClass)
        assert testClass instanceof TestClass
        assert testClass.myMethodA() == 'the original A'
        assert testClass.myMethodB() == 'the new B'
        assert testClass.myMethodX() == 'the injected X'
    }

    void testAggregateFromAbstractBaseClass() {
        Map map = [myMethodG: {'the concrete G'}, myMethodX: {'the injected X'}]
        def testClass = generator.instantiateAggregateFromBaseClass(map, AbstractClass)
        assert testClass instanceof AbstractClass
        assert testClass.myMethodA() == 'the original A'
        assert testClass.myMethodG() == 'the concrete G'
        assert testClass.myMethodX() == 'the injected X'
    }

    void testAggregateFromInterface() {
        Map map = [myMethodC: {'the injected C'}]
        def testClass = generator.instantiateAggregateFromInterface(map, TestInterface)
        assert testClass instanceof TestInterface
        assert testClass instanceof GroovyObject
        assert testClass.myMethodC() == 'the injected C'
    }

    void testAggregate() {
        Map map = [myMethodE: {'the injected E'}, myMethodB: {'the new B'}, myMethodX: {'the injected X'}]
        def testClass = generator.instantiateAggregate(map, [TestInterface, TestOtherInterface], TestClass)
        assert testClass instanceof TestInterface
        assert testClass instanceof TestOtherInterface
        assert testClass instanceof TestClass
        assert testClass.myMethodA() == 'the original A'
        assert testClass.myMethodB() == 'the new B'
        assert testClass.myMethodX() == 'the injected X'
        assert testClass.myMethodE() == 'the injected E'
    }

    void testDelegate() {
        def delegate = new TestClass()
        Map map = [myMethodE: {'the injected E'}, myMethodB: {'the new B'}, myMethodX: {'the injected X'}]
        def testClass = generator.instantiateDelegate(map, [TestInterface, TestOtherInterface], delegate)
        assert testClass instanceof TestInterface
        assert testClass instanceof TestOtherInterface
        assert testClass.myMethodA() == 'the original A'
        assert testClass.myMethodB() == 'the new B'
        assert testClass.myMethodX() == 'the injected X'
        assert testClass.myMethodE() == 'the injected E'
    }

    // GROOVY-2705
    void testDelegateOfList() {
        def delegate = [1, 2, 3, 4, 5]
        def testClass = generator.instantiateDelegate([List], delegate)
        assert testClass instanceof List
        assert testClass.size() == 5
        assert testClass.iterator().collect() == [1, 2, 3, 4, 5]
        assert testClass[2] == 3

        testClass[3] = 99
        assert testClass[3] == 99

        testClass.removeAt(2)
        testClass.removeAt(1)
        assert testClass == [1, 99, 5]
    }

    // GROOVY-10201
    void testDelegateOfString() {
        // on JDK17+, delegate is String which implements the sealed interface ConstantDesc
        assert generator.instantiateDelegate('foo').size() == 3
    }

    void testDelegateWithBaseClass() {
        def delegate = new TestClass()
        Map map = [myMethodE: {'the injected E'}, myMethodB: {'the new B'}, myMethodX: {'the injected X'}]
        TestClass testClass = generator.instantiateDelegateWithBaseClass(map, [TestInterface, TestOtherInterface], delegate)
        assert testClass instanceof TestInterface
        assert testClass instanceof TestOtherInterface
        assert testClass.myMethodA() == 'the original A'
        assert testClass.myMethodB() == 'the new B'
        assert testClass.myMethodX() == 'the injected X'
        assert testClass.myMethodE() == 'the injected E'
    }

    void testUnknownMethodThrowsUnsupportedOperationException() {
        def map = [myMethodA: { -> 'some string' }]
        def proxy = generator.instantiateAggregateFromInterface(map, TestInterface)
        assert proxy instanceof TestInterface
        assertEquals 'some string', proxy.myMethodA()
        shouldFail(UnsupportedOperationException) {
            proxy.myMethodC()
        }
    }

    void testUnknownMethodWithBlankBody() {
        def map = [myMethodA: { -> 'some string' }]
        def gen = new ProxyGenerator(emptyMethods: true)
        def proxy = gen.instantiateAggregate(map, [TestInterface])
        assert proxy instanceof TestInterface
        assert proxy.myMethodA() == 'some string'
        proxy.myMethodC()
    }

    void testProxyWithToString() {
        def map = [toString: { -> 'hello' }]
        def gen = new ProxyGenerator()
        def proxy = gen.instantiateAggregateFromBaseClass(map, Object)
        assert proxy.toString() == 'hello'
    }

    void testProxyWithClosureChangedAfterCreation() {
        def map = [toString: { -> 'hello' }]
        def gen = new ProxyGenerator()
        def proxy = gen.instantiateAggregateFromBaseClass(map, Object)
        assert proxy.toString() == 'hello'
        map.toString = { 'world' }
        assert proxy.toString() == 'world'
    }

    void testProxyMethodUsingLongAsParameter() {
        def map = [foo: { a,b -> a*b }]
        def gen = new ProxyGenerator()
        def proxy = gen.instantiateAggregateFromBaseClass(map, TestInterfaceWithLong)
        assert proxy.foo(3,3) == 9
    }

    void testProxyMethodUsingDoubleAsParameter() {
        def map = [foo: { a,b -> a*b }]
        def gen = new ProxyGenerator()
        def proxy = gen.instantiateAggregateFromBaseClass(map, TestInterfaceWithDouble)
        assert proxy.foo(3d,3d) == 9d
    }

    void testProxyMethodUsingVariousTypesAsParameters() {
        def map = [foo: { a,b,c,d -> a*b+c-d }]
        def gen = new ProxyGenerator()
        def proxy = gen.instantiateAggregateFromBaseClass(map, TestInterfaceWithVariousTypes)
        assert proxy.foo(3, 3d, 4, 1f) == 12d
    }
}

class TestClass {
    def myMethodA() { return 'the original A' }
    def myMethodB() { return 'the original B' }
}

interface TestInterface {
    def myMethodA()
    def myMethodC()
    def myMethodD()
}

interface TestOtherInterface {
    def myMethodB()
    def myMethodE()
    def myMethodF()
}

interface TestInterfaceWithLong {
    long foo(long a, long b)
}

interface TestInterfaceWithDouble {
    double foo(double a, double b)
}

interface TestInterfaceWithVariousTypes {
    double foo(int a, double b, long c, float d)
}

abstract class AbstractClass {
    def myMethodA() { return 'the original A' }
    abstract myMethodG()
}
