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

final class CoercionTest extends GroovyTestCase {

    void testStringToEnumValue() {
        assertScript '''
        // tag::state_enum[]
        enum State {
            up,
            down
        }
        // end::state_enum[]

        try {
            // tag::enum_coerce_assignment[]
            State st = 'up'
            assert st == State.up
            // end::enum_coerce_assignment[]
        } catch (IllegalArgumentException err) {
            assert false:'Test should not have failed'
        }

        try {
            // tag::enum_coerce_assignment_wrong[]
            State st = 'not an enum value'
            // end::enum_coerce_assignment_wrong[]
        } catch (IllegalArgumentException err) {
            // woot!
        }

        try {
            // tag::enum_coerce_assignment_gstring[]
            def val = "up"
            State st = "${val}"
            assert st == State.up
            // end::enum_coerce_assignment_gstring[]
        } catch (IllegalArgumentException err) {
            assert false:'Test should not have failed'
        }


        // tag::enum_switch_method[]
        State switchState(State st) {
            switch (st) {
                case 'up':
                    return State.down // explicit constant
                case 'down':
                    return 'up' // implicit coercion for return types
            }
        }
        // end::enum_switch_method[]

        // tag::enum_switch_test[]
        assert switchState('up' as State) == State.down
        assert switchState(State.down) == State.up
        // end::enum_switch_test[]

        '''
    }

    void testCustomCoercion() {
        assertScript '''
import groovy.transform.ToString
import static java.lang.Math.*

@ToString(includeNames=true)
// tag::polar_class_header[]
class Polar {
    double r
    double phi
// end::polar_class_header[]
// tag::polar_class_astype[]
    def asType(Class target) {
        if (Cartesian==target) {
            return new Cartesian(x: r*cos(phi), y: r*sin(phi))
        }
    }
// end::polar_class_astype[]
// tag::polar_class_footer[]
}
// end::polar_class_footer[]

@ToString(includeNames=true)
// tag::cartesian_class[]
class Cartesian {
   double x
   double y
}
// end::cartesian_class[]

// tag::polar_astype_assert[]
def sigma = 1E-16
def polar = new Polar(r:1.0,phi:PI/2)
def cartesian = polar as Cartesian
assert abs(cartesian.x-sigma) < sigma
// end::polar_astype_assert[]
'''
    }

    void testCustomCoercionWithExternalAsType() {
        assertScript '''
import groovy.transform.ToString
import static java.lang.Math.*

@ToString(includeNames=true)
class Polar {
    double r
    double phi

}
@ToString(includeNames=true)
class Cartesian {
   double x
   double y
}

// tag::polar_metaclass_astype[]
Polar.metaClass.asType = { Class target ->
    if (Cartesian==target) {
        return new Cartesian(x: r*cos(phi), y: r*sin(phi))
    }
}
// end::polar_metaclass_astype[]

def sigma = 1E-16
def polar = new Polar(r:1.0,phi:PI/2)
def cartesian = polar as Cartesian
assert abs(cartesian.x-sigma) < sigma
'''
    }

    void testExplicitClosureCoercion() {
        assertScript '''
// tag::filter_sam_type[]
interface Predicate<T> {
    boolean accept(T obj)
}
// end::filter_sam_type[]
// tag::greeter_sam_type[]
abstract class Greeter {
    abstract String getName()
    void greet() {
        println "Hello, $name"
    }
}
// end::greeter_sam_type[]
// tag::assertions_explicit_closure_to_sam[]
Predicate filter = { it.contains 'G' } as Predicate
assert filter.accept('Groovy') == true

Greeter greeter = { 'Groovy' } as Greeter
greeter.greet()
// end::assertions_explicit_closure_to_sam[]
'''
    }

    void testImplicitClosureCoercionWithAssignment() {
        assertScript '''
interface Predicate<T> {
    boolean accept(T obj)
}

abstract class Greeter {
    abstract String getName()
    void greet() {
        println "Hello, $name"
    }
}

// tag::assertions_implicit_closure_to_sam[]
Predicate filter = { it.contains 'G' }
assert filter.accept('Groovy') == true

Greeter greeter = { 'Groovy' }
greeter.greet()
// end::assertions_implicit_closure_to_sam[]

'''
    }

    void testImplicitClosureCoercionWithAssignmentAndMethodPointer() {
        assertScript '''
interface Predicate<T> {
    boolean accept(T obj)
}

abstract class Greeter {
    abstract String getName()
    void greet() {
        println "Hello, $name"
    }
}

// tag::assertions_implicit_closure_to_sam_and_method_pointer[]

boolean doFilter(String s) { s.contains('G') }

Predicate filter = this.&doFilter
assert filter.accept('Groovy') == true

Greeter greeter = GroovySystem.&getVersion
greeter.greet()
// end::assertions_implicit_closure_to_sam_and_method_pointer[]

'''
    }

    void testClosureCoercionWithMethodCall() {
        assertScript '''
interface Predicate<T> {
    boolean accept(T obj)
}

// tag::method_accepting_filter[]
public <T> List<T> filter(List<T> source, Predicate<T> predicate) {
    source.findAll { predicate.accept(it) }
}
// end::method_accepting_filter[]

// tag::method_call_with_explicit_coercion[]
assert filter(['Java','Groovy'], { it.contains 'G'} as Predicate) == ['Groovy']
// end::method_call_with_explicit_coercion[]

// tag::method_call_with_implicit_coercion[]
assert filter(['Java','Groovy']) { it.contains 'G'} == ['Groovy']
// end::method_call_with_implicit_coercion[]
'''
    }

    void testClosureCoercionToInterface() {
        assertScript '''
// tag::foobar_interface[]
interface FooBar {
    int foo()
    void bar()
}
// end::foobar_interface[]

// tag::foobar2closure_coercion[]
def impl = { println 'ok'; 123 } as FooBar
// end::foobar2closure_coercion[]

// tag::foobarintf_assertions[]
assert impl.foo() == 123
impl.bar()
// end::foobarintf_assertions[]
        '''
    }

    void testClosureCoercionToClass() {
        assertScript '''
// tag::closure2foobarclass[]
class FooBar {
    int foo() { 1 }
    void bar() { println 'bar' }
}

def impl = { println 'ok'; 123 } as FooBar

assert impl.foo() == 123
impl.bar()
// end::closure2foobarclass[]
'''
    }

    void testCoerceMapToIterator() {
        assertScript '''
// tag::coerce_map_to_iterator[]
def map
map = [
  i: 10,
  hasNext: { map.i > 0 },
  next: { map.i-- },
]
def iter = map as Iterator
// end::coerce_map_to_iterator[]

// tag::use_coerced_iterator[]
while ( iter.hasNext() )
println iter.next()
assert map.i==0
// end::use_coerced_iterator[]
'''
        // "remove()" is an interface default method
        assertScript '''import static groovy.test.GroovyAssert.shouldFail

            def iter = [next: {->null}, hasNext: {->true}] as Iterator
            shouldFail(UnsupportedOperationException) {
                iter.remove()
            }

            iter = [next: {->null}, hasNext: {->true}, remove: {->}] as Iterator
            iter.remove()
        '''
    }

    // GROOVY-10717
    void testCoerceMapToAbstract() {
        assertScript '''import static groovy.test.GroovyAssert.shouldFail

            abstract class A {
                String b,c
            }

            def a = [getB: {->'bb'}, getX: {->'xx'}] as A
            assert a.b == 'bb' // overrides accessor
            assert a.c == null // generated accessor
            shouldFail(MissingPropertyException) {
                a.x
            }
        '''
    }

    void testCoerceThrowsNPE() {
        assertScript '''
// tag::define_x_interface[]
interface X {
    void f()
    void g(int n)
    void h(String s, int n)
}

x = [ f: {println "f called"} ] as X
// end::define_x_interface[]

// tag::call_existing_method[]
x.f() // method exists
// end::call_existing_method[]

try {
// tag::call_non_existing_method[]
    x.g() // MissingMethodException here
// end::call_non_existing_method[]
} catch (MissingMethodException e) {
    println "Caught exception"
}
try {
// tag::call_notimplemented_method[]
    x.g(5) // UnsupportedOperationException here
// end::call_notimplemented_method[]
} catch (UnsupportedOperationException e) {
    println "Caught exception"
}
'''
    }

    void testAsVsAsType() {
        assertScript '''
// tag::as_keyword[]
interface Greeter {
    void greet()
}
def greeter = { println 'Hello, Groovy!' } as Greeter // Greeter is known statically
greeter.greet()
// end::as_keyword[]

/*
// tag::clazz_greeter_header[]
Class clazz = Class.forName('Greeter')
// end::clazz_greeter_header[]

// tag::incorrect_as_usage[]
greeter = { println 'Hello, Groovy!' } as clazz
// throws:
// unable to resolve class clazz
// @ line 9, column 40.
//   greeter = { println 'Hello, Groovy!' } as clazz
// end::incorrect_as_usage[]
*/
Class clazz = Greeter
// tag::fixed_as_usage[]
greeter = { println 'Hello, Groovy!' }.asType(clazz)
greeter.greet()
// end::fixed_as_usage[]
'''
    }
}
