/*
 *  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.junit.jupiter.api.Test

/**
 * Unit tests for static compilation: null test optimizations.
 */
final class StaticCompileNullCompareOptimizationTest extends AbstractBytecodeTestCase {

    @Test
    void testShouldUseIfNull1() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(Object o) {
                o != null
            }
        ''')
        assert bytecode.hasStrictSequence(['IFNULL'])
    }

    @Test
    void testShouldUseIfNull2() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(Object o) {
                null != o
            }
        ''')
        assert bytecode.hasStrictSequence(['IFNULL'])
    }

    @Test
    void testShouldUseIfNonNull1() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(Object o) {
                o == null
            }
        ''')
        assert bytecode.hasStrictSequence(['IFNONNULL'])
    }

    @Test
    void testShouldUseIfNonNull2() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(Object o) {
                null == o
            }
        ''')
        assert bytecode.hasStrictSequence(['IFNONNULL'])
    }

    @Test
    void testPrimitiveWithNullShouldBeOptimized1() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(int x) {
                null == x
            }
        ''')
        assert !bytecode.hasSequence(['ICONST_0', 'POP'])
        assert bytecode.hasSequence(['RETURN'])
    }

    @Test
    void testPrimitiveWithNullShouldBeOptimized2() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(int x) {
                x == null
            }
        ''')
        assert !bytecode.hasSequence(['ICONST_0', 'POP'])
        assert bytecode.hasSequence(['RETURN'])
    }

    @Test
    void testOptimizeGroovyTruthForPrimitiveBoolean1() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(boolean x) {
                if (x) {
                }
            }
        ''')
        assert bytecode.hasSequence([
            'ILOAD 1',
            'IFEQ L1',
            'L1',
            'RETURN'
        ])
    }

    @Test
    void testOptimizeGroovyTruthForPrimitiveBoolean2() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(boolean x) {
                if (!x) {
                }
            }
        ''')
        assert bytecode.hasSequence([
            'ILOAD 1',
            'IFNE L1',
            'ICONST_1',
            'GOTO L2',
            'L1',
            'ICONST_0',
            'L2',
            'IFEQ L3',
            'L3',
            'RETURN'
        ])
    }

    @Test
    void testOptimizeGroovyTruthForPrimitiveBoolean3() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(boolean x) {
                if (!!x) {
                }
            }
        ''')
        assert bytecode.hasSequence([
            'ILOAD 1',
            'IFEQ L1',
            'L1',
            'RETURN'
        ])
    }

    @Test
    void testOptimizeGroovyTruthForNonPrimitiveBoolean() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(Boolean x) {
                if (x) {
                }
            }
        ''')
        assert bytecode.hasSequence([
            'ALOAD 1',
            'DUP',
            'IFNONNULL',
            'POP',
            'ICONST_0',
            'GOTO',
            'L1',
            'INVOKEVIRTUAL',
            'L2',
            'IFEQ'
        ])
    }

    @Test
    void testOptimizeGroovyTruthForPrimitiveNumberType() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(int x) {
                if (x) {
                }
            }
        ''')
        assert bytecode.hasSequence([
            'ILOAD 1',
            'IFEQ L1',
            'ICONST_1',
            'GOTO L2',
            'L1',
            'ICONST_0',
            'L2',
            'IFEQ L3',
            'L3',
            'RETURN'
        ])
    }

    @Test
    void testNoGroovyTruthOptimizationForObject() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(Object x) {
                if (x) {
                }
            }
        ''')
        assert bytecode.hasSequence([
            'ALOAD 1',
            'DUP',
            'IFNONNULL L1',
            'POP',
            'ICONST_0',
            'GOTO L2',
            'L1',
            'INVOKEDYNAMIC cast(Ljava/lang/Object;)Z'
        ])
    }

    @Test
    void testNoGroovyTruthOptimizationForString() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            void m(String x) {
                if (x) {
                }
            }
        ''')
        assert bytecode.hasSequence([
            'ALOAD 1',
            'DUP',
            'IFNONNULL L1',
            'POP',
            'ICONST_0',
            'GOTO L2',
            'L1',
            'INVOKEDYNAMIC cast(Ljava/lang/String;)Z'
        ])
    }

    @Test
    void testGroovyTruthOptimizationForFinalClass() {
        def bytecode = compile(method:'m', '''
            final class A {
            }
            @groovy.transform.CompileStatic
            void m(A x) {
                if (x) {
                }
            }
        ''')
        assert bytecode.hasSequence([
            'ALOAD 1',
            'DUP',
            'IFNONNULL L1',
            'POP',
            'ICONST_0',
            'GOTO L2',
            'POP',
            'ICONST_1'
        ])
        assert !bytecode.hasSequence(['INVOKEDYNAMIC cast(LA;)Z'])
    }

    @Test
    void testGroovyTruthOptimizationForPrivateInnerClass() {
        def bytecode = compile(method:'m', '''
            class A {
                private static class B {
                }
                @groovy.transform.CompileStatic
                void m(B x) {
                    if (x) {
                    }
                }
            }
        ''')
        assert bytecode.hasSequence([
            'ALOAD 1',
            'DUP',
            'IFNONNULL L1',
            'POP',
            'ICONST_0',
            'GOTO L2',
            'POP',
            'ICONST_1'
        ])
        assert !bytecode.hasSequence(['INVOKEDYNAMIC cast(LA$B;)Z'])
    }

    @Test
    void testNoGroovyTruthOptimizationForPublicInnerClass() {
        def bytecode = compile(method:'m', '''
            class A {
                public static class B {
                }
                @groovy.transform.CompileStatic
                void m(B x) {
                    if (x) {
                    }
                }
            }
        ''')
        assert bytecode.hasSequence([
            'ALOAD 1',
            'DUP',
            'IFNONNULL L1',
            'POP',
            'ICONST_0',
            'GOTO L2',
            'L1',
            'INVOKEDYNAMIC cast(LA$B;)Z'
        ])
    }

    // GROOVY-10711
    @Test
    void testNoGroovyTruthOptimizationIfProvidesAsBoolean() {
        def bytecode = compile(method:'m', '''
            @groovy.transform.CompileStatic
            @groovy.transform.Immutable
            class C {
                boolean asBoolean() {
                }
            }

            @groovy.transform.CompileStatic
            void m(C x) {
                if (!x) {
                }
            }
        ''')
        assert bytecode.hasSequence([
            'ALOAD 1',
            'DUP',
            'IFNONNULL L1',
            'POP',
            'ICONST_0',
            'GOTO L2',
            'L1',
            'INVOKEDYNAMIC cast(LC;)Z'
        ])
    }

    @Test
    void testCompare() {
        assertScript '''
            class Pogo {
            }
            @groovy.transform.CompileStatic
            class C {
                static test() {
                    Pogo pogo = null
                    def check = { -> if (pogo) { 1 } else { 0 } }
                    assert check() == 0
                }
            }

            C.test()
        '''
    }
}
