/*
 *  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 org.codehaus.groovy.classgen.asm.sc

import org.codehaus.groovy.classgen.asm.AbstractBytecodeTestCase
import org.codehaus.groovy.runtime.MethodClosure

import static org.codehaus.groovy.control.CompilerConfiguration.DEFAULT as config

class StaticCompilationTest extends AbstractBytecodeTestCase {

    void testEmptyMethod() {
        def bytecode = compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m() {}
        ''')
        assert bytecode.hasStrictSequence(
                ['public m()V', 'L0', 'LINENUMBER 3 L0', 'RETURN']
        )
    }

    void testPrimitiveReturn1() {
        def bytecode = compile(method: 'm', '''
            @groovy.transform.CompileStatic
            int m() { 1 }
        ''')
        assert bytecode.hasStrictSequence(
                ['ICONST_1', 'IRETURN']
        )
    }

    void testPrimitiveReturn2() {
        def bytecode = compile(method: 'm', '''
            @groovy.transform.CompileStatic
            long m() { 1L }
        ''')
        assert bytecode.hasStrictSequence(
                ['LCONST_1', 'LRETURN']
        )
    }

    void testPrimitiveReturn3() {
        def bytecode = compile(method: 'm', '''
            @groovy.transform.CompileStatic
            short m() { 1 }
        ''')
        assert bytecode.hasStrictSequence(
                ['ICONST_1', 'I2S', 'IRETURN']
        )
    }

    void testPrimitiveReturn4() {
        def bytecode = compile(method: 'm', '''
            @groovy.transform.CompileStatic
            byte m() { 1 }
        ''')
        assert bytecode.hasStrictSequence(
                ['ICONST_1', 'I2B', 'IRETURN']
        )
    }

    void testIdentityReturns() {
        def bytecode = compile(method: 'm', '''
            @groovy.transform.CompileStatic
            int m(int i) { i }
        ''')
        assert bytecode.hasStrictSequence(
                ['ILOAD', 'IRETURN']
        )

        bytecode = compile(method: 'm', '''
            @groovy.transform.CompileStatic
            long m(long l) { l }
        ''')
        assert bytecode.hasStrictSequence(
                ['LLOAD', 'LRETURN']
        )

        bytecode = compile(method: 'm', '''
            @groovy.transform.CompileStatic
            short m(short l) { l }
        ''')
        assert bytecode.hasStrictSequence(
                ['ILOAD', 'IRETURN']
        )

        bytecode = compile(method: 'm', '''
            @groovy.transform.CompileStatic
            float m(float l) { l }
        ''')
        assert bytecode.hasStrictSequence(
                ['FLOAD', 'FRETURN']
        )

        bytecode = compile(method: 'm', '''
            @groovy.transform.CompileStatic
            double m(double l) { l }
        ''')
        assert bytecode.hasStrictSequence(
                ['DLOAD', 'DRETURN']
        )

        bytecode = compile(method: 'm', '''
            @groovy.transform.CompileStatic
            Object m(Object l) { l }
        ''')
        assert bytecode.hasStrictSequence(
                ['ALOAD', 'ARETURN']
        )
    }

    void testSingleAssignment() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m() {
                int a = 1
            }
        ''').hasSequence([
                "ICONST_1",
                "ISTORE",
                "RETURN"
        ])
    }

    void testReturnSingleAssignment() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            int m() {
                int a = 1
            }
        ''').hasSequence([
                "ICONST_1",
                "ISTORE",
                "ILOAD",
                "IRETURN"
        ])
    }

    void testIntLeftShift() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m() {
                int a = 1
                int b = a << 32
            }
        ''').hasStrictSequence([
                "ILOAD",
                "BIPUSH 32",
                "ISHL"
        ])
    }

    void testLongLeftShift() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m() {
                long a = 1L
                long b = a << 32
            }
        ''').hasStrictSequence([
                "LLOAD",
                "BIPUSH 32",
                "LSHL"
        ])
    }

    void testArrayGet() {
        if (config.indyEnabled) return;
        // this test is done with indy in another tests case
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m(int[] arr) {
                arr[0]
            }
        ''').hasStrictSequence([
                "ALOAD 1",
                "ICONST_0",
                "INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArrayGet ([II)I"
        ])
    }

    void testArraySet() {
        if (config.indyEnabled) return;
        // this test is done with indy in another tests case
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m(int[] arr) {
                arr[0] = 0
            }
        ''').hasStrictSequence([
                "ICONST_0",
                "ISTORE 2",
                "ALOAD 1",
                "ICONST_0",
                "ILOAD 2",
                "INVOKESTATIC org/codehaus/groovy/runtime/BytecodeInterface8.intArraySet ([III)V"
        ])
    }

/*    void testPlusPlus() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m() {
                int i = 0
                i++
            }
        ''').hasStrictSequence([
                "IINC",
        ])
    }

    void testMinusMinus() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m() {
                int i = 0
                i--
            }
        ''').hasStrictSequence([
                "IINC",
        ])
    }

    void testPlusEquals() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            int m() {
                int i = 0
                i += 13
                return i
            }
        ''').hasStrictSequence([
                "ILOAD",
                "ILOAD",
                "IADD",
                "ISTORE"
        ])
    }

    void testPlusEqualsFromArgs() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m(int i, int j) {
                i += j
            }
        ''').hasStrictSequence([
                "ILOAD",
                "ILOAD",
                "IADD",
                "ISTORE"
        ])
    }*/

    void testFlow() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            String m(String str) {
                def obj = 1
                obj = str
                obj.toUpperCase()
            }
            m 'Cedric'
        ''').hasStrictSequence([
                "ICONST",
                "INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;",
                "ASTORE",
                "L1",
                "ALOAD 2",
                "POP",
                "L2",
                "LINENUMBER",
                "ALOAD 1",
                "ASTORE 3",
                "ALOAD 3",
                "ASTORE 2",
                "ALOAD 3",
                "POP",
                "L3",
                "LINENUMBER",
                "ALOAD 2",
                "CHECKCAST java/lang/String",
                "INVOKEVIRTUAL java/lang/String.toUpperCase ()Ljava/lang/String;",
                "ARETURN",
                "L4"
        ])
    }

    void testInstanceOf() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m(Object str) {
                if (str instanceof String) {
                    str.toUpperCase()
                }
            }
            m 'Cedric'
        ''').hasStrictSequence([
                "ALOAD",
                "CHECKCAST java/lang/String",
                "INVOKEVIRTUAL java/lang/String.toUpperCase ()Ljava/lang/String;"
        ])
    }

    void testShouldGenerateDirectConstructorCall() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            class Foo {
                String msg
                Foo(int x, String y) { msg = y*x }
                static Foo foo() {
                    Foo result = [2,'Bar']
                }
            }
        ''').hasStrictSequence([
                'ICONST_2',
                'LDC "Bar"',
                'INVOKESPECIAL Foo.<init> (ILjava/lang/String;)V'
        ])
    }

    void testShouldGenerateDirectArrayConstruct() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m() {
                int[] arr = [123,456]
            }
        ''').hasStrictSequence([
                'ICONST_2',
                'NEWARRAY T_INT',
                'DUP',
                'ICONST_0',
                'BIPUSH 123',
                'IASTORE'
        ])
    }

    void testShouldGenerateDirectBooleanArrayConstruct() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m() {
                boolean[] arr = [123,false]
            }
        ''').hasStrictSequence([
                'ICONST_2',
                'NEWARRAY T_BOOLEAN',
                'DUP',
                'ICONST_0',
                'BIPUSH 123',
                'INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;',
                'INVOKESTATIC org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox (Ljava/lang/Object;)Z',
                'BASTORE'
        ])
    }

    void testShouldTriggerDirectCallToOuterClassGetter() {
        assert compile(method: 'fromInner', classNamePattern: '.*Inner.*', '''
            class Holder {
                String value
            }

            @groovy.transform.CompileStatic
            class Outer {
                String outerProperty = 'outer'
                private class Inner {
                    String fromInner() {
                        Holder holder = new Holder()
                        holder.value = outerProperty
                        holder.value
                    }
                }

                String blah() {
                    new Inner().fromInner()
                }
            }

            def o = new Outer()
            assert o.blah() == 'outer'
        ''').hasStrictSequence([
                'GETFIELD Outer$Inner.this$0',
                'INVOKEVIRTUAL Outer.getOuterProperty',
                'DUP',
                'ASTORE',
                'ALOAD',
                'ALOAD',
                'INVOKEVIRTUAL Holder.setValue'
        ])
    }

    void testShouldOptimizeBytecodeByAvoidingCreationOfMopMethods() {
        def shell = new GroovyShell()
        def clazz = shell.evaluate '''
            import groovy.transform.TypeCheckingMode
            import groovy.transform.CompileStatic

            @CompileStatic
            class A {
                def doSomething() { 'A' }
            }

            @CompileStatic
            class B extends A {
                def doSomething() { 'B' + super.doSomething() }
            }

            B
        '''
        assert clazz instanceof Class
        assert clazz.name == 'B'
        def mopMethods = clazz.declaredMethods.findAll { it.name =~ /(super|this)\$/ }
        assert mopMethods.empty
    }

    void testShouldNotOptimizeBytecodeForMopMethodsBecauseOfSkip() {
        def shell = new GroovyShell()
        def clazz = shell.evaluate '''
            import groovy.transform.TypeCheckingMode
            import groovy.transform.CompileStatic

            @CompileStatic
            class A {
                def doSomething() { 'A' }
            }

            @CompileStatic
            class B extends A {
                @CompileStatic(TypeCheckingMode.SKIP)
                def doSomething() { 'B' + super.doSomething() }
            }

            B
        '''
        assert clazz instanceof Class
        assert clazz.name == 'B'
        def mopMethods = clazz.declaredMethods.findAll { it.name =~ /(super|this)\$/ }
        assert !mopMethods.empty
    }

    // GROOVY-7124
    void testUseInvokeVirtualPreferredOverInvokeInterface() {
        assert compile(method: 'foo', classNamePattern: 'B', '''
            interface A { void m() }
            class B implements A {
                void m() {}
                @groovy.transform.CompileStatic
                void foo() {
                    m()
                }
            }
        ''').hasStrictSequence(['INVOKEVIRTUAL B.m'])
    }

    void testShouldNotTryToCastToSupposedDelegateType() {
        assertScript '''
            @groovy.transform.CompileStatic
            class ClassCastOhNoes {
               def foo(def o) {
                   def cl = {
                       delegate.getClass()
                   }
                   cl.delegate = o
                   cl()
               }
            }
            new ClassCastOhNoes().foo(100)
        '''
    }

    void testShouldOptimizeCharInit() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m() {
                char c = 'x'
            }
        ''').hasStrictSequence([
                'LINENUMBER 4',
                'BIPUSH 120',
                'ISTORE'
        ])
    }

    void testShouldOptimizeCharComparison() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m() {
                char c1 = 'x'
                char c2 = 'x'
                boolean b = c1==c2
            }
        ''').hasSequence([
                'LINENUMBER 4',
                'BIPUSH 120',
                'ISTORE',
                'BIPUSH 120',
                'ISTORE',
                'ILOAD',
                'ILOAD',
                'IF_ICMPNE',
        ])

        // make sure the code passes
        assertScript '''
            @groovy.transform.CompileStatic
            void m() {
                char c1 = 'x'
                char c2 = 'x'
                boolean b = c1==c2
                assert b
                char c3 = 'z'
                b = c1==c3
                assert !b
            }
            m()
        '''
    }

    void testForEachLoopOptimization() {
        def loop_init = [
                'ALOAD', // load array (might be more complex than just ALOAD in general case)
                'DUP',
                'ASTORE', // store array to local var
                'ARRAYLENGTH', // get array len
                'ISTORE', // store it to local var
                'ICONST_0',
                'ISTORE', // initialize loop index
                'ILOAD', // load loop index
                'ILOAD', // load array length
                'IF_ICMPGE' // if greater or equal, end of loop
        ]

        // int[]
        def intExample = '''
            @groovy.transform.CompileStatic
            void m(int[] arr) {
                for (int i: arr) {
                   println(i)
                }
            }
            m([1,2] as int[])
        '''
        assert compile(method:'m', intExample).hasSequence([*loop_init,'IALOAD','ISTORE'])

        // short[]
        def shortExample = '''
            @groovy.transform.CompileStatic
            void m(short[] arr) {
                for (short i: arr) {
                   println(i)
                }
            }
            m([1,2] as short[])
        '''
        assert compile(method:'m', shortExample).hasSequence([*loop_init,'SALOAD','ISTORE'])

        // byte[]
        def byteExample = '''
            @groovy.transform.CompileStatic
            void m(byte[] arr) {
                for (byte i: arr) {
                   println(i)
                }
            }
            m([1,2] as byte[])
        '''
        assert compile(method:'m', byteExample).hasSequence([*loop_init,'BALOAD','ISTORE'])

        // long[]
        def longExample = '''
            @groovy.transform.CompileStatic
            void m(long[] arr) {
                for (long i: arr) {
                   println(i)
                }
            }
            m([1,2] as long[])
        '''
        assert compile(method:'m', longExample).hasSequence([*loop_init,'LALOAD','LSTORE'])

        // char[]
        def charExample = '''
            @groovy.transform.CompileStatic
            void m(char[] arr) {
                for (char i: arr) {
                   println(i)
                }
            }
            m('foo'.toCharArray())
        '''
        assert compile(method:'m', charExample).hasSequence([*loop_init,'CALOAD','ISTORE'])

        // boolean[]
        def boolExample = '''
            @groovy.transform.CompileStatic
            void m(boolean[] arr) {
                for (boolean i: arr) {
                   println(i)
                }
            }
            m([true,false] as boolean[])
        '''
        assert compile(method:'m', boolExample).hasSequence([*loop_init,'BALOAD','ISTORE'])

        // float[]
        def floatExample = '''
            @groovy.transform.CompileStatic
            void m(float[] arr) {
                for (float i: arr) {
                   println(i)
                }
            }
            m([1.5f,2.0f] as float[])
        '''
        assert compile(method:'m', floatExample).hasSequence([*loop_init,'FALOAD','FSTORE'])

        // double[]
        def doubleExample = '''
            @groovy.transform.CompileStatic
            void m(double[] arr) {
                for (double i: arr) {
                   println(i)
                }
            }
            m([1.1,2.2] as double[])
        '''
        assert compile(method:'m', doubleExample).hasSequence([*loop_init,'DALOAD','DSTORE'])

        // Any[]
        def anyExample = '''
            @groovy.transform.CompileStatic
            void m(String[] arr) {
                for (String i: arr) {
                   println(i)
                }
            }
            m(['a','b'] as String[])
        '''
        assert compile(method:'m', anyExample).hasSequence([*loop_init,'AALOAD','ASTORE'])

        // now check that everything runs fine
        [byteExample,shortExample, intExample, charExample, boolExample, longExample, floatExample, doubleExample, anyExample].each { script ->
            assertScript(script)
        }
    }

    void testCompareWithCharOptimization() {
        String code = '''
            @groovy.transform.CompileStatic
            boolean m(char[] arr) {
                char c = arr[0]
                ' '==c
            }
            assert m(' abc '.toCharArray())
        '''
        assert compile(method:'m',code).hasSequence(['BIPUSH','ILOAD','IF_ICMPNE'])
        assertScript(code)

        code = '''
            @groovy.transform.CompileStatic
            boolean m(char[] arr) {
                char c = arr[0]
                c==' '
            }
            assert m(' abc '.toCharArray())
        '''
        assert compile(method:'m',code).hasSequence(['ILOAD','BIPUSH','IF_ICMPNE'])
        assertScript(code)
    }

    void testShouldRemoveUnnecessaryCast() {
        assert compile(method: 'm', '''
            @groovy.transform.CompileStatic
            void m() {
                char c = (char) 'x'
            }
        ''').hasStrictSequence([
                'LINENUMBER 4',
                'BIPUSH 120',
                // No checkcast, but the idea is to check that further optimization was done
                // because the RHS is no longer a CastExpression but a ConstantExpression
                'ISTORE'
        ])
    }

    void testInstanceMethodReference() {
        // dynamic case should be a method closure
        assert String::toUpperCase instanceof MethodClosure

        // static case should be compiled into functional interface
        String code = '''
            import groovy.transform.CompileStatic
            import java.util.stream.Collectors

            @CompileStatic
            void m() {
                assert ['foo'].stream().map(String::toUpperCase).collect(Collectors.toList()) == ['FOO']
            }
            m()
        '''
        assert compile(method:'m', code).hasSequence([
                'INVOKEDYNAMIC apply()Ljava/util/function/Function;',
                /* handle kind 0x6 : INVOKESTATIC */
                'java/lang/invoke/LambdaMetafactory.metafactory',
                /* handle kind 0x5 : INVOKEVIRTUAL */
                'java/lang/String.toUpperCase()Ljava/lang/String;'
        ])
        assertScript(code)
    }
}
