/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * license agreements; and to You under the Apache License, version 2.0:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * This file is part of the Apache Pekko project, which was derived from Akka.
 */

/*
 * Copyright (C) 2015-2022 Lightbend Inc. <https://www.lightbend.com>
 */

package docs.stream

import scala.concurrent.Await
import org.apache.pekko.stream.Supervision
import org.apache.pekko.stream.scaladsl._
import org.apache.pekko.testkit.PekkoSpec
import org.apache.pekko.stream.ActorAttributes
import scala.concurrent.duration._

class FlowErrorDocSpec extends PekkoSpec {

  "demonstrate fail stream" in {
    // #stop
    val source = Source(0 to 5).map(100 / _)
    val result = source.runWith(Sink.fold(0)(_ + _))
    // division by zero will fail the stream and the
    // result here will be a Future completed with Failure(ArithmeticException)
    // #stop

    intercept[ArithmeticException] {
      Await.result(result, 3.seconds)
    }
  }

  "demonstrate resume stream" in {
    // #resume
    val decider: Supervision.Decider = {
      case _: ArithmeticException => Supervision.Resume
      case _                      => Supervision.Stop
    }
    val source = Source(0 to 5).map(100 / _)
    val runnableGraph =
      source.toMat(Sink.fold(0)(_ + _))(Keep.right)

    val withCustomSupervision = runnableGraph.withAttributes(ActorAttributes.supervisionStrategy(decider))

    val result = withCustomSupervision.run()
    // the element causing division by zero will be dropped
    // result here will be a Future completed with Success(228)
    // #resume

    Await.result(result, 3.seconds) should be(228)
  }

  "demonstrate resume section" in {
    // #resume-section
    val decider: Supervision.Decider = {
      case _: ArithmeticException => Supervision.Resume
      case _                      => Supervision.Stop
    }
    val flow = Flow[Int]
      .filter(100 / _ < 50)
      .map(elem => 100 / (5 - elem))
      .withAttributes(ActorAttributes.supervisionStrategy(decider))
    val source = Source(0 to 5).via(flow)

    val result = source.runWith(Sink.fold(0)(_ + _))
    // the elements causing division by zero will be dropped
    // result here will be a Future completed with Success(150)
    // #resume-section

    Await.result(result, 3.seconds) should be(150)
  }

  "demonstrate restart section" in {
    // #restart-section
    val decider: Supervision.Decider = {
      case _: IllegalArgumentException => Supervision.Restart
      case _                           => Supervision.Stop
    }
    val flow = Flow[Int]
      .scan(0) { (acc, elem) =>
        if (elem < 0) throw new IllegalArgumentException("negative not allowed")
        else acc + elem
      }
      .withAttributes(ActorAttributes.supervisionStrategy(decider))
    val source = Source(List(1, 3, -1, 5, 7)).via(flow)
    val result = source.limit(1000).runWith(Sink.seq)
    // the negative element cause the scan stage to be restarted,
    // i.e. start from 0 again
    // result here will be a Future completed with Success(Vector(0, 1, 4, 0, 5, 12))
    // #restart-section

    Await.result(result, 3.seconds) should be(Vector(0, 1, 4, 0, 5, 12))
  }

  "demonstrate recover" in {
    // #recover
    Source(0 to 6)
      .map(n =>
        // assuming `4` and `5` are unexpected values that could throw exception
        if (List(4, 5).contains(n)) throw new RuntimeException(s"Boom! Bad value found: $n")
        else n.toString)
      .recover {
        case e: RuntimeException => e.getMessage
      }
      .runForeach(println)
    // #recover

    /*
Output:
//#recover-output
0
1
2
3                         // last element before failure
Boom! Bad value found: 4  // first element on failure
//#recover-output
     */
  }

  "demonstrate recoverWithRetries" in {
    // #recoverWithRetries
    val planB = Source(List("five", "six", "seven", "eight"))

    Source(0 to 10)
      .map(n =>
        if (n < 5) n.toString
        else throw new RuntimeException("Boom!"))
      .recoverWithRetries(attempts = 1,
        {
          case _: RuntimeException => planB
        })
      .runForeach(println)
    // #recoverWithRetries

    /*
Output:
//#recoverWithRetries-output
0
1
2
3
4
five
six
seven
eight
//#recoverWithRetries-output
     */
  }

}
