/*
 *  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 static java.lang.Boolean.FALSE as F
import static java.text.DateFormat.MEDIUM as M
import static java.util.regex.Pattern.*
import static java.text.DateFormat.MEDIUM
import static junit.framework.Assert.format
import static junit.framework.Assert.assertEquals
import static groovy.StaticImportTarget.x
import static groovy.StaticImportTarget.z // do not remove
import static java.lang.Math.*
import static java.util.Calendar.getInstance as now
import static groovy.API.*
import static groovy.StaticImportChild.*
import static groovy.Outer1.*
import static groovy.Outer2.Inner2
import static groovy.Outer2.Inner2 as InnerAlias2
import static java.util.jar.Attributes.*
import static java.util.jar.Attributes.Name as AttrName
import static groovy.Container5087.*
import static Foo4964.*

import org.codehaus.groovy.runtime.DefaultGroovyMethods as DGM

final class StaticImportTest extends groovy.test.GroovyTestCase {

    void testFieldWithAliasInExpression() {
        assert !F
    }

    void testMethodAndField() {
        assert cos(2 * PI) == 1.0
    }

    static myStaticMethod() {
        cos(2 * PI)
    }

    void testMethodAndFieldInStaticContext() {
        assert myStaticMethod() == 1.0
    }

    void testMethodAndFieldInClosure() {
        def closure = { cos(2 * PI) }
        assert closure() == 1.0
    }

    void testFieldAsObjectExpression() {
        assert PI.equals(Math.PI)
    }

    void testFieldAsArgumentList() {
        assert ('' + PI.toString()).contains('3.14')
    }

    void testFieldAliasing() {
        assert MEDIUM == M
    }

    // GROOVY-1809
    void testMethodAliasing() {
        // making this not possible on one line?
        def now = now().time
        assert now.class == Date
    }

    void testWildCardAliasing() {
        assert MULTILINE == java.util.regex.Pattern.MULTILINE
    }

    private format(a, b, c, ignored) { format(a, b, c) }

    void testMethodDefCanUseStaticallyImportedMethodWithSameNameButDiffArgs() {
        assert format('different', 'abc', 'aBc', 3) == 'different expected:<abc> but was:<aBc>'
    }

    void testAssertEqualsFromJUnit() {
        double[] values = [3.9999, 4.0001, 0.00021, 0.00019]
        assertEquals(values[0], values[1], values[2])
        shouldFail(junit.framework.AssertionFailedError) {
            assertEquals(values[0], values[1], values[3])
        }
    }

    void testStaticImportFromGroovy() {
        def nonstaticval = new StaticImportTarget().y('he', 3)
        def staticval = x('he', 3)
        assert nonstaticval == staticval
    }

    void testStaticImportWithVarArgs() {
        assert noArrayMethod('one', 1) == 'noArrayMethod(one, 1)'
        assert API.arrayMethod('two', 1, 2, 3) == 'arrayMethod(two, 1, 2, 3)'
        assert arrayMethod('three', 1, 2, 3) == 'arrayMethod(three, 1, 2, 3)'
    }

    void testStaticImportFromParentClass() {
        assert cmethod() == 'hello from child'
        assert pmethod() == 'hello from parent'
        assert cfield == 21
        assert pfield == 42
    }

    void testStaticImportAndDefaultValue() {
        assertScript '''
            import static Foo.*
            import static Bar.*

            class Bar {
                static void bar() {
                    assert foo(10,1000) == 1010
                    assert foo(10) == 110
                }
            }

            class Foo {
                static int foo(int x, int y = 100) {x+y}
            }

            Bar.bar()
        '''
    }

    void testStaticImportProperty() {
        for (imports in ['import static Foo.getX; import static Foo.setX', 'import static Foo.*']) {
            assertScript """$imports
                class Foo {
                    static x = 'foo'
                }
                assert x == 'foo'
                x = 'bar'
                assert getX() == 'bar'
                setX('baz')
                assert 'baz' == x
            """
            assertScript """$imports
                class Foo {
                    static x = 'foo'
                    static getX() { x + '_get' }
                }
                assert x == 'foo_get'
                x = 'bar'
                assert getX() == 'bar_get'
                setX('baz')
                assert 'baz_get' == x
            """
            assertScript """$imports
                class Foo {
                    static x = 'foo'
                    static void setX(newx) { x = newx + '_set' }
                }
                assert x == 'foo'
                x = 'bar'
                assert getX() == 'bar_set'
                setX('baz')
                assert 'baz_set' == x
            """
            assertScript """$imports
                class Foo {
                    static x = 'foo'
                    static getX() { x + '_get' }
                    static void setX(newx) { x = newx + '_set' }
                }
                assert x == 'foo_get'
                x = 'bar'
                assert getX() == 'bar_set_get'
                setX('baz')
                assert 'baz_set_get' == x
            """
        }
    }

    void testStaticImportPropertyBooleanAlternative() {
        for (imports in ['import static Foo.isX; import static Foo.setX', 'import static Foo.x', 'import static Foo.*']) {
            assertScript """$imports
                class Foo {
                    static x
                    static boolean isX() { !!x }
                }
                assert !x
                x = true
                assert isX()
                setX(false)
                assert !x
            """
            assertScript """$imports
                class Foo {
                    static x
                    static boolean isX() { !!x }
                    static void setX(newx) { x = newx }
                }
                assert !x
                x = false
                assert !isX()
                setX(true)
                assert x
            """
        }
    }

    void testStaticImportPropertyWithPublicField() {
        for (imports in ['import static Foo.x', 'import static Foo.*']) {
            assertScript """$imports
                class Foo {
                    public static x = 'foo'
                }
                assert x == 'foo'
                x = 'bar'
                assert x == 'bar'
                x = 'baz'
                assert x == 'baz'
            """
            assertScript """$imports
                class Foo {
                    public static x = 'foo'
                    static getX() { x + '_get' }
                }
                assert x == 'foo_get'
                x = 'bar'
                assert getX() == 'bar_get'
                x = 'baz'
                assert x == 'baz_get'
            """
            assertScript """$imports
                class Foo {
                    public static x = 'foo'
                    static void setX(newx) { x = newx + '_set' }
                }
                assert x == 'foo'
                x = 'bar'
                assert x == 'bar_set'
                setX('baz')
                assert x == 'baz_set'
            """
            assertScript """$imports
                class Foo {
                    public static x = 'foo'
                    static getX() { x + '_get' }
                    static void setX(newx) { x = newx + '_set' }
                }
                assert x == 'foo_get'
                x = 'bar'
                assert getX() == 'bar_set_get'
                setX('baz')
                assert x == 'baz_set_get'
            """
        }
        assertScript '''
            import static Foo.getX; import static Foo.setX
            class Foo { public static x = 'foo'; static getX() { x + '_get' }; static void setX(newx) { x = newx + '_set' } }
            assert getX() == 'foo_get'; setX('bar'); assert getX() == 'bar_set_get'; setX('baz'); assert 'baz_set_get' == getX()
        '''
    }

    void testStaticImportPropertyWithAliases() {
        for (imports in ['import static Foo.getX as getZ; import static Foo.setX as setZ', 'import static Foo.x as z']) {
            assertScript """$imports
                class Foo {
                    static x = 'foo'
                }
                assert z == 'foo'
                z = 'bar'
                assert getZ() == 'bar'
                setZ('baz')
                assert z == 'baz'
            """
            assertScript """$imports
                class Foo {
                    static x = 'foo'
                    static getX() { x + '_get' }
                }
                assert z == 'foo_get'
                z = 'bar'
                assert getZ() == 'bar_get'
                setZ('baz')
                assert z == 'baz_get'
            """
            assertScript """$imports
                class Foo {
                    static x = 'foo'
                    static void setX(newx) { x = newx + '_set' }
                }
                assert z == 'foo'
                z = 'bar'
                assert getZ() == 'bar_set'
                setZ('baz')
                assert z == 'baz_set'
            """
            assertScript """$imports
                class Foo {
                    static x = 'foo'
                    static getX() { x + '_get' }
                    static void setX(newx) { x = newx + '_set' }
                }
                assert z == 'foo_get'
                z = 'bar'
                assert getZ() == 'bar_set_get'
                setZ('baz')
                assert z == 'baz_set_get'
            """
        }

        assertScript '''
            import groovy.transform.CompileStatic
            import static Pogo.setP as store
            class Pogo {
                static p
            }
            assert Pogo.p == null
            @CompileStatic test() {
                store('')
                assert Pogo.p == ''
            }
            test()
        '''
    }

    // GROOVY-9382, GROOVY-10133
    void testStaticImportPropertyWithChoices() {
        assertScript '''
            import static Foo.isX
            import static Foo.getX
            class Foo {
                static boolean isX() { true }
                static boolean getX() { false }
            }
            assert x
        '''

        def err = shouldFail '''
            import static Foo.isX
            class Foo { static isX() {} }

            x
        '''
        assert err =~ /No such property: x for class/

        err = shouldFail '''
            import static Foo.isX as isY
            class Foo { static isX() {} }

            y
        '''
        assert err =~ /No such property: y for class/
    }

    // GROOVY-8389, GROOVY-10329
    void testStaticImportPropertyWithClosure() {
        for (imports in ['import static Foo.bar; import static Foo.baz', 'import static Foo.*']) {
            for (tag in ['@groovy.transform.CompileDynamic', '@groovy.transform.CompileStatic']) {
                assertScript """$imports
                    class Foo {
                        static Closure<String> bar = { -> 'property' }
                        static Closure<String> baz = { -> 'property' }
                    }
                    String bar() {
                        'method'
                    }
                    $tag
                    String test() {
                        bar() + ':' + baz()
                    }
                    String result = test()
                    assert result == 'method:property'
                """
            }
        }
    }

    // GROOVY-8389
    void testStaticImportMethodVsLocalMethod() {
        assertScript '''
            import static Foo.bar
            class Foo {
                static bar = 'foo'
            }
            def bar() {
                'bar'
            }
            @groovy.transform.CompileStatic
            def test() {
                bar()
            }
            assert test() == 'bar'
        '''

        assertScript '''
            import static Foo.bar
            class Foo {
                static bar = 'foo'
            }
            static bar() {
                'bar'
            }
            @groovy.transform.CompileStatic
            def test() {
                bar()
            }
            assert test() == 'bar'
        '''

        assertScript '''
            import static Foo.baz
            import static Foo.bar
            class Foo {
                static bar() { 'foobar' }
                static baz() { 'foobaz' }
            }
            def bar() {
                'bar'
            }
            @groovy.transform.CompileStatic
            def test() {
                "${bar()}${baz()}"
            }
            assert test() == 'barfoobaz'
        '''

        assertScript '''
            import static Foo.baz
            import static Foo.bar
            class Foo {
                static bar() { 'foobar' }
                static baz() { 'foobaz' }
            }
            static bar() {
                'bar'
            }
            @groovy.transform.CompileStatic
            def test() {
                "${bar()}${baz()}"
            }
            assert test() == 'barfoobaz'
        '''
    }

    void testConstructorArgsAliasing() {
        // not recommended style to use statics in constructors but supported
        assertScript '''
            class Foo {
                static x
            }
            import static Foo.x as z
            new Foo(z:'hi')
            assert z == 'hi'
        '''
    }

    void testMethodCallWithThisTargetIsNotResolvedToStaticallyImportedMethod() {
        // not using shouldFail() to avoid closure scope
        try {
            this.z()
            fail()
        } catch (MissingMethodException expected) {}
    }

    void testMethodCallWithSuperTargetIsNotResolvedToStaticallyImportedMethod() {
        // not using shouldFail() to avoid closure scope
        try {
            super.z()
            fail()
        } catch (MissingMethodException expected) {}
    }

    // GROOVY-3945, GROOVY-10329, GROOVY-11056
    void testStaticImportOfClosureProperty() {
        for (imports in ['import static groovy.StaticImportTarget.cl', 'import static groovy.StaticImportTarget.*']) {
            assertScript """$imports
                String result = cl()
                assert result == 'StaticImportTarget#static closure called'
            """
            assertScript """$imports
                def fn = { -> cl() }
                String result = fn()
                assert result == 'StaticImportTarget#static closure called'
            """
        }

        assertScript """import static groovy.StaticImportTarget.cl as fn
            String result = fn()
            assert result == 'StaticImportTarget#static closure called'
        """
    }

    // GROOVY-7490, GROOVY-10329, GROOVY-11056
    void testStaticImportOfCallableProperty() {
        for (imports in ['import static Pogo.callable_property', 'import static Pogo.*']) {
            for (use in ['@groovy.transform.CompileStatic caller()', 'def caller()', 'def caller =']) {
                assertScript """$imports
                    class WithCall {
                        String call(String input) {
                            return input
                        }
                    }
                    class Pogo {
                        static final WithCall callable_property = new WithCall()
                    }
                    $use {
                        callable_property('works')
                    }
                    String result = caller()
                    assert result == 'works'
                """
            }
        }
    }

    // GROOVY-4145
    void testStaticPropertyImportedImplementedAsGetter() {
        assertScript '''
            import static Groovy4145.foo4145
            class Groovy4145 {
                static getFoo4145() {
                    return 3
                }
            }
            assert foo4145 == 3
        '''
    }

    // GROOVY-4228
    void testMethodCallExpressionInStaticContextWithInstanceVariableShouldFail() {
        def err = shouldFail '''
            class B {
                def c = new Object()
                static main(args) {
                    c.foo()
                }
            }
        '''
        assert err =~ /Apparent variable 'c' was found in a static scope but doesn't refer to a local variable, static field or class/
    }

    // GROOVY-10396
    void testStaticImportVersusThisOrSuperMethod1() {
        assertScript '''
            import static groovy.Extension10396.*

            static void test() {
                println 'x'
            }
            strings.clear()
            test()

            assert 'x' in strings
        '''
    }

    // GROOVY-10396
    void testStaticImportVersusThisOrSuperMethod2() {
        assertScript '''
            import static groovy.Extension10396.*

            def obj = new Object() { // outer class extends Script
                String toString() {
                    println 'AIC::x'
                    super.toString()
                }
            }
            strings.clear()
            obj.toString()

            assert 'x' !in strings
        '''
    }

    // GROOVY-10396
    void testStaticImportVersusThisOrSuperMethod3() {
        assertScript '''
            import static groovy.Extension10396.*

            static void println(String s) { // static overload
            }
            static void test() {
                println 'x'
            }
            strings.clear()
            test()

            assert 'x' !in strings
        '''
    }

    void testStaticStarImportOfStaticInnerClass() {
        assert Inner1.class.name == 'groovy.Outer1$Inner1'
    }

    void testStaticImportOfStaticInnerClass() {
        assert Inner2.class.name == 'groovy.Outer2$Inner2'
    }

    void testStaticImportOfStaticInnerClassWithAlias() {
        assert InnerAlias2.class.name == 'groovy.Outer2$Inner2'
    }

    void testStaticImportOfStaticInnerClassExistingExternalJar() {
        assert Name.class.name == 'java.util.jar.Attributes$Name'
    }

    void testStaticImportOfStaticInnerClassExistingExternalJarWithAlias() {
        assert AttrName.class.name == 'java.util.jar.Attributes$Name'
    }

    void testExplicitStaticMethodCallHasPrecedenceOverStaticImport() {
        Bar4964.run()
    }

    void testMapIndexInLeftExpressionOfEquals() {
        holder = 'foo'
        def map = [:]
        map[holder] = 'bar'
        assert map.containsKey('foo')
        assert map.foo == 'bar'
    }

    void testConstructorParamInLeftExpressionOfEquals() {
        holder = [:]
        new HolderWrapper(holder).foo = 'baz'
        assert holder.foo == 'baz'
    }

    void testMethodParamInLeftExpressionOfEquals() {
        holder = [[a:1, b:2], [c:3]]
        DGM.find(holder) { it.size() == 2 }.a = 4
        assert holder[0].a == 4
        dgmFind(holder) { it.size() == 1 }.c = 7
        assert holder[1].c == 7
    }

    private <T> T dgmFind(Collection<T> col, Closure clos) {
        DGM.find(col, clos)
    }
}

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

class API {
    static noArrayMethod(String s, int value) {
        "noArrayMethod(${s}, ${value})"
    }

    static arrayMethod(String s, int[] values) {
        "arrayMethod(${s}, " + values.toList().join(", ") + ")"
    }
}

class StaticImportParent {
    static pfield = 42
    static pmethod() { 'hello from parent' }
}

class StaticImportChild extends StaticImportParent {
    static cfield = 21
    static cmethod() { 'hello from child' }
}

class Outer1 {
    static class Inner1 {}
}

class Outer2 {
    static class Inner2 {}
}

class Foo4964 {
    static doIt() { [k: 'foo'] }
}

class Bar4964 {
    static doIt() { [k: 'bar'] }

    /*
     The following `run` method is invoked by `testExplicitStaticMethodCallHasPrecedenceOverStaticImport`
     As the method name shows, "ExplicitStaticMethodCall Has Precedence Over StaticImport", but the original test code does not conform to the intention
     */
    static run() {
//        The original test code is commented as follows:
//        assert doIt().k == 'foo'
//        assert doIt() == [k: 'foo']

        assert doIt().k == 'bar'
        assert doIt() == [k: 'bar']

        assert Bar4964.doIt() == [k: 'bar']
        assert Bar4964.doIt().k == 'bar'
    }
}

class Container5087 {
    static holder
}

class HolderWrapper {
    def holder

    HolderWrapper(holder) {
        this.holder = holder
    }

    void setProperty(String name, Object value) {
        holder[name] = value
    }
}

class Extension10396 {
    static final List<String> strings = []
    static void println(String s) {
        strings << s
    }
}
