package org.questions

import org.questions.fibonacci.{Fibonacci, Iterative, Recursive, TailRecursion}
import org.scalacheck.Gen
import org.specs2.ScalaCheck
import org.specs2.mutable.Specification

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}

/**
 * @author maximn
 * @since 26-Oct-2015
 */
trait FibonacciTest extends Specification with FibonacciProperty {
  val fib: Fibonacci

  "negative input" should {
    "be invalid" in {
      fib.nth(-1) must throwA[IllegalArgumentException]
    }
  }

  "0th" should {
    "be 0" in {
      fib.nth(0) must be_===(0)
    }
  }

  "1st" should {
    "be 1" in {
      fib.nth(1) must be_===(1)
    }
  }

  "2nd" should {
    "be 1" in {
      fib.nth(2) must be_===(1)
    }
  }
}

trait BigN {
  self: FibonacciTest =>

  import scala.concurrent.ExecutionContext.Implicits.global

  "big N" should {
    "be computed in sub seconds" in {
      val calcBigN = Future.apply {
        fib.nth(100)
      }

      Await.ready(calcBigN, Duration("1 second")).isCompleted must beTrue
    }
  }
}

trait FibonacciProperty extends ScalaCheck {
  self: FibonacciTest =>

  val smallInteger = Gen.choose[Int](2,30)

  "fib(n)" should {
    "be equal to the sum of the results of last 2 elements" >> prop { n: Int =>
      fib.nth(n) must be_===(fib.nth(n - 1) + fib.nth(n - 2))
    }.setGen(smallInteger)
  }
}

class RecursiveTest extends FibonacciTest {
  override val fib = new Recursive
}

class IterativeTest extends FibonacciTest with BigN {
  override val fib = new Iterative
}

class TailRecursionTest extends FibonacciTest with BigN {
  override val fib: Fibonacci = new TailRecursion
}