/*
 * Copyright 2016 Dennis Vriend
 *
 * Licensed 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 akka.stream.integration
package activemq

import akka.NotUsed
import akka.actor.ActorRef
import akka.stream.integration.PersonDomain.Person
import akka.stream.scaladsl.{ Flow, Keep }
import akka.stream.testkit.scaladsl.{ TestSink, TestSource }
import akka.stream.testkit.{ TestPublisher, TestSubscriber }
import akka.testkit.TestActor.AutoPilot
import akka.testkit.TestProbe
import JsonCamelMessageExtractor._
import JsonCamelMessageBuilder._

import scala.util.{ Failure, Success, Try }
/**
 * Test fixtures for ActiveMq
 */
trait ActiveMqTestSpec extends TestSpec {

  /**
   * Creates an acknowledging bidirectional flow whose back-end and output can be manipulated using probes; it expects
   * an implicit backendFlow such as can be acquired from withBackendFlow
   */
  def withAckBidiFlow(test: TestPublisher.Probe[AckUTup[Person]] => TestSubscriber.Probe[AckUTup[Person]] => Any)(implicit backendFlow: Flow[Person, Person, NotUsed]): Unit = {

    val inputSource = TestSource.probe[AckUTup[Person]]
    val outputSink = TestSink.probe[AckUTup[Person]]

    val partialTestFlow = ActiveMqFlow.applyMat(inputSource, outputSink)(Keep.both)

    val (inputProbe, outputProbe) = partialTestFlow.join(backendFlow).run()

    test(inputProbe)(outputProbe)
  }

  /**
   * Creates a back-end flow whose messages can be intercepted using a traditional testprobe
   */
  def withBackendFlow(test: Flow[Person, Person, NotUsed] => TestProbe => Any): Unit = {
    import akka.pattern.ask
    val flowProbe = TestProbe()
    val backendFlow = Flow[Person].mapAsync(1)(p => (flowProbe.ref ? p).mapTo[Person])
    test(backendFlow)(flowProbe)
  }

  /**
   * Creates an acknowledging bidirectional flow, connected with an ActiveMqSource and ActiveMqSink
   *
   * NOTE: Test using this fixture assume correct implementation of ActiveMqSource and ActiveMqSink!
   */
  def withActiveMqBidiFlow[S, T](sourceEndpoint: String, sinkEndpoint: String)(test: Flow[Person, Person, ActorRef] => ActorRef): Unit = {
    val ref = test(ActiveMqFlow.apply[Person, Person](sourceEndpoint, sinkEndpoint))
    terminateEndpoint(ref)
  }

  /**
   * Creates a request-response flow with an ActiveMqSource, acknowledging sink and bidi-flow to join them.
   */
  def withReqRespBidiFlow[S, T](sourceEndpoint: String)(test: Flow[Person, Person, ActorRef] => ActorRef): Any = {
    val ref = test(ActiveMqReqRespFlow[Person, Person](sourceEndpoint))
    terminateEndpoint(ref)
  }

  /**
   * Creates an AutoPilot that performs request-response according to the supplied function
   */
  implicit def function1ToAutoPilot[S, T](f: S => T): AutoPilot = new AutoPilot {
    override def run(sender: ActorRef, msg: Any): AutoPilot = msg match {
      case s: S =>
        val tryT: Try[T] = Try(f(s))
        tryT match {
          case Success(t) =>
            sender ! t
            function1ToAutoPilot(f)
          case Failure(f) =>
            fail(s"Failed to apply supplied function to received message: $s", f)
        }
      case _ =>
        fail(s"Received message is not of the required type: $msg")
    }
  }
}