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

class GenericsBytecodeTest extends GenericsTestBase {

    void testClassWithoutParameterExtendsClassWithFixedParameter() {
        createClassInfo """
            class B extends ArrayList<Long> {}
        """
        assert signatures == ["class": "Ljava/util/ArrayList<Ljava/lang/Long;>;Lgroovy/lang/GroovyObject;",]
    }

    void testMultipleImplementsWithParameter() {
        createClassInfo """
            abstract class B<T> implements Runnable,List<T> {}
        """
        assert signatures == [
                "class": "<T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/lang/Runnable;Ljava/util/List<TT;>;Lgroovy/lang/GroovyObject;"
        ]
    }

    void testImplementsWithParameter() {
        createClassInfo """
            abstract class B<T> implements List<T> {}
        """
        assert signatures == [
                "class": "<T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/List<TT;>;Lgroovy/lang/GroovyObject;"
        ]
    }

    void testExtendsWithParameter() {
        createClassInfo """
            class B<T> extends ArrayList<T> {}
        """
        assert signatures == ["class": "<T:Ljava/lang/Object;>Ljava/util/ArrayList<TT;>;Lgroovy/lang/GroovyObject;"]
    }

    void testNestedExtendsWithParameter() {
        createClassInfo """
            class B<T> extends HashMap<T,List<T>> {}
        """
        assert signatures == [
                "class": "<T:Ljava/lang/Object;>Ljava/util/HashMap<TT;Ljava/util/List<TT;>;>;Lgroovy/lang/GroovyObject;"
        ]
    }

    void testBoundInterface() {
        createClassInfo """
            class B<T extends List> {}
        """
        assert signatures == ["class": "<T::Ljava/util/List;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;"]
    }

    void testNestedReuseOfParameter() {
        createClassInfo """
            class B<Y,T extends Map<String,Map<Y,Integer>>> {}
        """
        assert signatures == [
                "class": "<Y:Ljava/lang/Object;T::Ljava/util/Map<Ljava/lang/String;Ljava/util/Map<TY;Ljava/lang/Integer;>;>;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;"
        ]
    }

    void testFieldWithParameter() {
        createClassInfo """
            class B { public Collection<Integer> books }
        """
        assert signatures == [books: "Ljava/util/Collection<Ljava/lang/Integer;>;"]
    }

    void testFieldReusedParameter() {
        createClassInfo """
            class B<T> { public Collection<T> collection }
        """
        assert signatures == ["class" : "<T:Ljava/lang/Object;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;",
                              collection: "Ljava/util/Collection<TT;>;"]
    }

    void testParameterAsReturnType() {
        createClassInfo """
            class B {
                static <T> T foo() {return null}
            }
        """
        assert signatures == ["foo()Ljava/lang/Object;": "<T:Ljava/lang/Object;>()TT;"]
    }

    void testParameterAsReturnTypeAndParameter() {
        createClassInfo """
            class B {
                static <T> T foo(T t) {return null}
            }
        """
        assert signatures == ["foo(Ljava/lang/Object;)Ljava/lang/Object;": "<T:Ljava/lang/Object;>(TT;)TT;"]
    }

    void testParameterAsMethodParameter() {
        createClassInfo """
            class B<T> {
                void foo(T t){}
            }
        """
        assert signatures == [
                "class"                   : "<T:Ljava/lang/Object;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;",
                "foo(Ljava/lang/Object;)V": "(TT;)V"
        ]
    }

    void testParameterAsNestedMethodParameter() {
        createClassInfo """
            class B<T> {
                void foo(List<T> t){}
            }
        """
        assert signatures == [
                "class"                 : "<T:Ljava/lang/Object;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;",
                "foo(Ljava/util/List;)V": "(Ljava/util/List<TT;>;)V"
        ]
    }

    void testParameterAsNestedMethodParameterReturningInterface() {
        createClassInfo """
            class B<T> {
                Cloneable foo(List<T> t){}
            }
        """
        assert signatures == [
                "class"                                     : "<T:Ljava/lang/Object;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;",
                "foo(Ljava/util/List;)Ljava/lang/Cloneable;": "(Ljava/util/List<TT;>;)Ljava/lang/Cloneable;"
        ]
    }

    void testArray() {
        createClassInfo """
            class B<T> {
                T[] get(T[] arr) {return null}
            }
        """
        assert signatures == [
                "class"                                      : "<T:Ljava/lang/Object;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;",
                "get([Ljava/lang/Object;)[Ljava/lang/Object;": "([TT;)[TT;"
        ]
    }

    void testMultipleBounds() {
        createClassInfo """
            class Pair<    A extends Comparable<A> & Cloneable ,
                        B extends Cloneable & Comparable<B> >
            {
                A foo(){}
                B bar(){}
            }
        """
        assert signatures == [
                "class"                      : "<A::Ljava/lang/Comparable<TA;>;:Ljava/lang/Cloneable;B::Ljava/lang/Cloneable;:Ljava/lang/Comparable<TB;>;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;",
                "foo()Ljava/lang/Comparable;": "()TA;",
                "bar()Ljava/lang/Cloneable;" : "()TB;"
        ]
    }

    void testWildcard1() {
        createClassInfo '''
            class C {
                private Collection<?> f1
                private List<? extends Number> f2
                private Comparator<? super String> f3
                private Map<String,?> f4
            }
        '''
        assert signatures == [
            f1: 'Ljava/util/Collection<*>;',
            f2: 'Ljava/util/List<+Ljava/lang/Number;>;',
            f3: 'Ljava/util/Comparator<-Ljava/lang/String;>;',
            f4: 'Ljava/util/Map<Ljava/lang/String;*>;'
        ]
    }

    void testWildcard2() {
        createClassInfo '''
            class C<T extends Number> {
                List<? super T> dependency
            }
        '''
        assert signatures == [
            class                             : '<T:Ljava/lang/Number;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;',
            dependency                        : 'Ljava/util/List<-TT;>;',
            'getDependency()Ljava/util/List;' : '()Ljava/util/List<-TT;>;',
            'setDependency(Ljava/util/List;)V': '(Ljava/util/List<-TT;>;)V'
        ]
    }

    // GROOVY-10229
    void testWildcard3() {
        createClassInfo '''
            @groovy.transform.CompileStatic
            class C {
                Map<String,?> a() {
                }
                Map<String,List<?>> b() {
                    def c = {
                        [
                            a(), a()
                        ]
                    }
                    return null
                }
            }
        '''
        assert signatures == [
            'a()Ljava/util/Map;'                             : '()Ljava/util/Map<Ljava/lang/String;*>;',
            'b()Ljava/util/Map;'                             : '()Ljava/util/Map<Ljava/lang/String;Ljava/util/List<*>;>;',
            'doCall()Ljava/util/ArrayList;'                  : '()Ljava/util/ArrayList<Ljava/util/Map<Ljava/lang/String;+Ljava/lang/Object;>;>;',
            'doCall(Ljava/lang/Object;)Ljava/util/ArrayList;': '(Ljava/lang/Object;)Ljava/util/ArrayList<Ljava/util/Map<Ljava/lang/String;+Ljava/lang/Object;>;>;'
        ]
    }

    void testParameterAsParameterForReturnTypeAndFieldClass() {
        createClassInfo """
               class B<T> {
                   private T owner;
                   Class<T> getOwnerClass(){}
            }
        """
        assert signatures == [
                "class"                           : "<T:Ljava/lang/Object;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;",
                "owner"                           : "TT;",
                "getOwnerClass()Ljava/lang/Class;": "()Ljava/lang/Class<TT;>;"
        ]
    }

    void testInterfaceWithParameter() {
        createClassInfo """
            interface B<T> {}
        """
        assert signatures == ["class": "<T:Ljava/lang/Object;>Ljava/lang/Object;"]
    }

    void testTypeParamAsBound() {
        createClassInfo """
            class Box<A> {
                public <V extends A> void foo(V v) {
                }
            }
        """
        assert signatures == [
                "foo(Ljava/lang/Object;)V": "<V:TA;>(TV;)V", "class": "<A:Ljava/lang/Object;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;"
        ]
    }

    void "test method with generic return type defined at class level"() {
        // class Bar should compile successfully

        // the classes it references should be available as class files to check for ASM resolving
        //  so they're defined in compiled GenericsTestData and not loaded from text in the test
        createClassInfo 'class Bar extends gls.generics.GenericsTestData.Abstract<String> {}'
    }
}
