import org.scalacheck._
import Prop._
import Gen._
import Arbitrary._
import math._
import concurrent.duration.Duration.fromNanos

object DurationTest extends Properties("Division of Duration by Long") {

  val weightedLong =
    frequency(
      1 -> choose(-128L, 127L),
      1 -> (arbitrary[Byte] map (_.toLong << 8)),
      1 -> (arbitrary[Byte] map (_.toLong << 16)),
      1 -> (arbitrary[Byte] map (_.toLong << 24)),
      1 -> (arbitrary[Byte] map (_.toLong << 32)),
      1 -> (arbitrary[Byte] map (_.toLong << 40)),
      1 -> (arbitrary[Byte] map (_.toLong << 48)),
      1 -> (choose(-127L, 127L) map (_ << 56))
    )

  val genTwoSmall = for {
    a <- weightedLong
    b <- choose(-(Long.MaxValue / max(1, abs(a))), Long.MaxValue / max(1, abs(a)))
  } yield (a, b)

  val genTwoLarge = for {
    a <- weightedLong
    b <- arbitrary[Long] suchThat (b => (abs(b) > Long.MaxValue / max(1, abs(a))))
  } yield (a, b)

  val genClose = for {
    a <- weightedLong
    if a != 0
    val center = Long.MaxValue / a
    b <-
      if (center - 10 < center + 10) choose(center - 10,  center + 10)
      else choose(center + 10,  center - 10) // deal with overflow if abs(a) == 1
  } yield (a, b)

  val genBorderline =
    frequency(
      1 -> (Long.MinValue, 0L),
      1 -> (Long.MinValue, 1L),
      1 -> (Long.MinValue, -1L),
      1 -> (0L, Long.MinValue),
      1 -> (1L, Long.MinValue),
      1 -> (-1L, Long.MinValue),
      90 -> genClose
    )

  def mul(a: Long, b: Long): Long = {
    (fromNanos(a) * b).toNanos
  }

  property("without overflow") = forAll(genTwoSmall) { case (a, b) =>
    a * b == mul(a, b)
  }

  property("with overflow") = forAll(genTwoLarge) { case (a, b) =>
    try { mul(a, b); false } catch { case _: IllegalArgumentException => true }
  }

  property("on overflow edge cases") = forAll(genBorderline) { case (a, b) =>
    val shouldFit =
      a != Long.MinValue && // must fail due to illegal duration length
      (b != Long.MinValue || a == 0) && // Long factor may only be MinValue if the duration is zero, otherwise the result will be illegal
      (abs(b) <= Long.MaxValue / max(1, abs(a))) // check the rest against the “safe” division method
    try { mul(a, b); shouldFit }
    catch { case _: IllegalArgumentException => !shouldFit }
  }
}
