package ioc.specs

import org.apache.tapestry5.func.F
import org.apache.tapestry5.ioc.annotations.AnnotationUseContext
import org.apache.tapestry5.ioc.services.TypeCoercer
import org.apache.tapestry5.ioc.util.TimeInterval
import org.apache.tapestry5.plastic.PlasticUtils
import spock.lang.Unroll

class TypeCoercerSpec extends AbstractSharedRegistrySpecification {

  TypeCoercer coercer = getService TypeCoercer

  static String BIG_DECIMAL_VALUE = "12345656748352435842385234598234958234574358723485.35843534285293857298457234587"

  static String BIG_INTEGER_VALUE = "12384584574874385743";

  static byte byte1 = 65, byte2 = 66

  static short short1 = 22, short2 = 7

  static int int1 = 200, int2 = 300

  static long long1 = 100l, long2 = 200l

  static float float1 = 22.1f, float2 = -123.5

  static double double1 = 123d, double2 = 999.9d

  static char char1 = "1".charAt(0), char2 = "2".charAt(0)


  @Unroll
  def "coerce #inputTypeName #input to #typeName should be #expected"() {

    expect:

    coercer.coerce(input, type) == expected

    where:


    input                             | type                 | expected
    "-15"                             | Double               | -15d
    "2.27"                            | Double               | 2.27d
    227l                              | int                  | 227
    -37                               | Number               | -37
    new StringBuilder("12345")        | int                  | 12345
    "52"                              | Integer              | 52
    this                              | String               | toString()
    55l                               | Integer              | 55
    256l                              | Boolean              | true
    ""                                | Boolean              | false
    " "                               | Boolean              | false
    "x"                               | Boolean              | true
    " z "                             | Boolean              | true
    "false"                           | Boolean              | false
    "FAlse"                           | Boolean              | false
    null                              | Boolean              | false
    256d                              | Integer              | 256
    22.7                              | Integer              | 22
    0                                 | Boolean              | false
    32838l                            | Boolean              | true
    127                               | Byte                 | new Byte("127")
    58d                               | Short                | new Short("58")
    33                                | Long                 | 33l
    22                                | Float                | 22f
    1234                              | Double               | 1234d
    31.14f                            | Double               | 31.14f.doubleValue()
    []                                | Boolean              | false
    ["a"]                             | Boolean              | true
    [null]                            | Boolean              | true
    new BigDecimal(BIG_DECIMAL_VALUE) | Double               | 1.2345656748352436E49
    BIG_INTEGER_VALUE                 | BigInteger           | new BigInteger(BIG_INTEGER_VALUE)
    -12345678L                        | BigInteger           | new BigInteger("-12345678")
    "bravo"                           | List                 | ["bravo"]
    null                              | Iterator             | null
    null                              | List                 | null
    null                              | Collection           | null
    null                              | String               | null
    ["a", 123] as Object[]            | List                 | ["a", 123]
    ["a", "b"] as String[]            | List                 | ["a", "b"]
    [true, false] as boolean[]        | List                 | [true, false]
    [byte1, byte2] as byte[]          | List                 | [byte1, byte2]
    [char1, char2] as char[]          | List                 | [char1, char2]
    [short1, short2] as short[]       | List                 | [short1, short2]
    [int1, int2] as int[]             | List                 | [int1, int2]
    [long1, long2] as long[]          | List                 | [long1, long2]
    [float1, float2] as float[]       | List                 | [float1, float2]
    [double1, double2] as double[]    | List                 | [double1, double2]
    "foo/bar/baz.txt"                 | File                 | new File("foo/bar/baz.txt")
    new TimeInterval("2 h")           | Long                 | 2 * 60 * 60 * 1000l
    "2 h"                             | TimeInterval         | new TimeInterval("120 m")
    F.flow()                          | Boolean              | false
    F.flow(1, 2, 3)                   | Boolean              | true
    F.flow(1, 2, 3)                   | List                 | [1, 2, 3]
    "mixin"                           | AnnotationUseContext | AnnotationUseContext.MIXIN
    123                               | Object[]             | [123] as Object[]
    [1, 2, 3]                         | Object[]             | [1, 2, 3] as Object[]


    inputTypeName = PlasticUtils.toTypeName(input.getClass())
    typeName = PlasticUtils.toTypeName(type)
  }

  @Unroll
  def "explain #fromName to #toName should be #expected"() {

    expect:

    coercer.explain(from, to) == expected

    where:

    from         | to         | expected
    StringBuffer | Integer    | "Object --> String, String --> Long, Long --> Integer"
    void         | Map        | "null --> null"
    void         | Boolean    | "null --> Boolean"
    Object[]     | Boolean    | "Object[] --> Boolean"
    String[]     | List       | "Object[] --> java.util.List"
    Float        | Double     | "Float --> Double"
    Double       | BigDecimal | "Object --> String, String --> java.math.BigDecimal"
    int          | Integer    | ""
    Integer      | Integer    | ""

    fromName = PlasticUtils.toTypeName(from)
    toName = PlasticUtils.toTypeName(to)
  }

  def "no coercion yields the input value unchanged"() {
    def input = new Integer(-37)

    expect:

    coercer.coerce(input, Integer).is(input)
  }

  def "coercions for primitive types and wrappers are the same"() {
    when:

    def c1 = coercer.getCoercion(int, Integer)
    def c2 = coercer.getCoercion(Integer, Integer)

    then:

    c1.is c2
  }

  def "exception when no coercion is found"() {
    when:

    coercer.coerce("", Map)

    then:

    RuntimeException e = thrown()

    e.message.contains "Could not find a coercion from type java.lang.String to type java.util.Map"
  }

  def "exception when a coercion fails"() {
    when:

    coercer.coerce([:], Float)

    then:

    RuntimeException e = thrown()

    e.message.contains "Coercion of {} to type java.lang.Float (via Object --> String, String --> Double, Double --> Float) failed"
    e.cause instanceof NumberFormatException
  }

  def "computed coercions are cached"() {
    def c1 = coercer.getCoercion(StringBuilder, Integer)
    def c2 = coercer.getCoercion(StringBuilder, Integer)

    expect:

    c1.is(c2)

    when:

    coercer.clearCache()
    def c3 = coercer.getCoercion(StringBuilder, Integer)

    then:

    !c1.is(c3)
  }
}
