package ioc.specs

import org.apache.commons.lang.StringUtils
import org.apache.tapestry5.ioc.internal.services.TextTransformer
import org.apache.tapestry5.ioc.services.AspectDecorator
import org.apache.tapestry5.plastic.MethodAdvice
import org.apache.tapestry5.plastic.MethodInvocation
import spock.lang.Shared

interface Subject {

  void advised();

  void notAdvised();
}

interface ArraysSubject {

  String[] operation(String[] inputs);
}

class AspectInterceptorBuilderImplSpec extends AbstractSharedRegistrySpecification {

  @Shared
  private AspectDecorator decorator

  def setupSpec() {
    decorator = getService AspectDecorator
  }

  def "ensure that non-advised methods are not passed through the MethodAdvice object"() {
    Subject delegate = Mock()
    MethodAdvice advice = Mock()

    def builder = decorator.createBuilder Subject, delegate, "<Subject>"

    builder.adviseMethod Subject.getMethod("advised"), advice

    Subject interceptor = builder.build()

    when:

    interceptor.advised()

    then:

    1 * advice.advise(_) >> { MethodInvocation mi ->
      assert mi.method.name == "advised"
      mi.proceed()
    }
    1 * delegate.advised()
    0 * _

    when:

    interceptor.notAdvised()

    then:

    1 * delegate.notAdvised()
    0 * _
  }

  def "failure when advising a method that is not in the service interface"() {
    Subject delegate = Mock()
    MethodAdvice advice = Mock()

    def builder = decorator.createBuilder Subject, delegate, "<Subject>"

    when:

    builder.adviseMethod Runnable.getMethod("run"), advice

    then:

    IllegalArgumentException e = thrown()

    e.message == "Method public abstract void java.lang.Runnable.run() is not defined for interface interface ioc.specs.Subject."
  }

  def "multiple advice for single method is processed in order"() {
    TextTransformer delegate = Mock()
    MethodAdvice stripFirstLetter = Mock()
    MethodAdvice reverse = Mock()

    def builder = decorator.createBuilder TextTransformer, delegate, "<TextTransformer>"

    def method = TextTransformer.getMethod "transform", String

    builder.adviseMethod method, stripFirstLetter
    builder.adviseMethod method, reverse

    TextTransformer advised = builder.build()

    when:

    def result = advised.transform "Tapestry"

    then:

    result == "[yrtsepa]"

    1 * stripFirstLetter.advise(_) >> { MethodInvocation mi ->
      assert mi.getParameter(0) == "Tapestry"
      mi.setParameter 0, mi.getParameter(0).substring(1)
      mi.proceed()
    }

    1 * reverse.advise(_) >> { MethodInvocation mi ->
      assert mi.getParameter(0) == "apestry"
      mi.setParameter 0, StringUtils.reverse(mi.getParameter(0))
      mi.proceed()
    }

    1 * delegate.transform(_) >> { it }
  }

  def "arrays are allowed as method parameters and return values"() {
    ArraysSubject delegate = Mock()
    MethodAdvice advice = Mock()

    def builder = decorator.createBuilder ArraysSubject, delegate, "unused"
    builder.adviseAllMethods advice

    ArraysSubject advised = builder.build()

    when:

    def result = advised.operation(["Fred", "Barney"] as String[])

    then:

    1 * advice.advise(_) >> { MethodInvocation it ->
      String[] inputs = it.getParameter(0)

      it.setParameter 0, inputs.collect({it.toUpperCase() }) as String[]

      it.proceed()

      def index = 0

      it.setReturnValue it.getReturnValue().collect({ value -> "${index++}:$value" }) as String[]
    }

    1 * delegate.operation(_) >> { it[0] }

    result.class == ([] as String[]).class
    result.asType(List) == ["0:FRED", "1:BARNEY"]
  }

}
