/**
 * Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
 */
package akka.pattern

import scala.concurrent.Promise
import scala.concurrent.duration._
import java.util.concurrent.ThreadLocalRandom
import akka.actor.Actor
import akka.actor.ActorLogging
import akka.actor.Props
import akka.actor.Status.Failure
import akka.testkit.AkkaSpec
import akka.testkit.ImplicitSender
import java.util.concurrent.TimeoutException

object CircuitBreakerStressSpec {
  case object JobDone
  case object GetResult
  case class Result(doneCount: Int, timeoutCount: Int, failCount: Int, circCount: Int)

  class StressActor(breaker: CircuitBreaker) extends Actor with ActorLogging with PipeToSupport {
    import context.dispatcher

    private var doneCount = 0
    private var timeoutCount = 0
    private var failCount = 0
    private var circCount = 0

    private def job = {
      val promise = Promise[JobDone.type]()

      context.system.scheduler.scheduleOnce(ThreadLocalRandom.current.nextInt(300).millisecond) {
        promise.success(JobDone)
      }

      promise.future
    }

    override def receive = {
      case JobDone ⇒
        doneCount += 1
        breaker.withCircuitBreaker(job).pipeTo(self)
      case Failure(ex: CircuitBreakerOpenException) ⇒
        circCount += 1
        breaker.withCircuitBreaker(job).pipeTo(self)
      case Failure(_: TimeoutException) ⇒
        timeoutCount += 1
        breaker.withCircuitBreaker(job).pipeTo(self)
      case Failure(_) ⇒
        failCount += 1
        breaker.withCircuitBreaker(job).pipeTo(self)
      case GetResult ⇒
        sender() ! Result(doneCount, timeoutCount, failCount, circCount)
    }
  }
}

// reproducer for issue #17415
class CircuitBreakerStressSpec extends AkkaSpec with ImplicitSender {
  import CircuitBreakerStressSpec._

  muteDeadLetters(classOf[AnyRef])(system)

  "A CircuitBreaker" in {
    val breaker = CircuitBreaker(system.scheduler, 5, 200.millisecond, 200.seconds)
    val stressActors = Vector.fill(3) {
      system.actorOf(Props(classOf[StressActor], breaker))
    }
    for (_ ← 0 to 1000; a ← stressActors) {
      a ! JobDone
    }
    // let them work for a while
    Thread.sleep(3000)
    stressActors.foreach { a ⇒
      a ! GetResult
      val result = expectMsgType[Result]
      result.failCount should be(0)
    }

  }

}
