/**
 *  Copyright (C) 2012 Typesafe Inc. <http://www.typesafe.com>
 */

import scala.concurrent.duration._
import scala.reflect._
import scala.tools.partest.TestUtil.intercept

import scala.language.{ postfixOps }

object Test extends App {

  implicit class Assert(val left: Any) extends AnyVal {
    import Duration.Undefined
    def mustBe(right: Any) = right match {
      case r: Double if r.isNaN                        => assert(left.asInstanceOf[Double].isNaN, s"$left was not NaN")
      case r: Double if r == 0 && r.compareTo(0) == -1 => assert(left == 0 && left.asInstanceOf[Double].compareTo(0) == -1, s"$left was not -0.0")
      case Undefined                                   => assert(left.asInstanceOf[AnyRef] eq Undefined, s"$left was not Undefined")
      case _                                           => assert(left == right, s"$left was not equal to $right")
    }
  }

  val zero = 0 seconds
  val one = 1 second
  val two = one + one
  val three = 3 * one
  val inf = Duration.Inf
  val minf = Duration.MinusInf
  val undef = Duration.Undefined
  val inputs = List(zero, one, inf, minf, undef)
  val nan = Double.NaN

  // test field ops
  one.isFinite mustBe true
  0 * one mustBe zero
  2 * one mustBe two
  three - two mustBe one
  three / 3 mustBe one
  two / one mustBe 2
  one + zero mustBe one
  one / 1000000 mustBe 1.micro


  // test infinities

   inf.isFinite mustBe false
  minf.isFinite mustBe false

    inf mustBe  inf
   minf mustBe minf
   -inf mustBe minf
  -minf mustBe  inf

  minf +  inf mustBe undef
   inf -  inf mustBe undef
   inf + minf mustBe undef
  minf - minf mustBe undef

   inf +  inf mustBe  inf
   inf - minf mustBe  inf
  minf -  inf mustBe minf
  minf + minf mustBe minf

  inf.compareTo(inf) mustBe 0
  inf.compareTo(one) mustBe 1
  inf.compareTo(minf) mustBe 1
  minf.compareTo(minf) mustBe 0
  minf.compareTo(one) mustBe -1
  minf.compareTo(inf) mustBe -1

  assert(inf != minf)
  assert(minf != inf)
  assert(one != inf)
  assert(minf != one)

  inf mustBe (minf * -1d)
  inf mustBe (minf / -1d)

   one /  inf mustBe  0d
  -one /  inf mustBe -0d
   one / minf mustBe -0d
  -one / minf mustBe  0d

  inputs filterNot (_.isFinite) foreach (x => x / zero mustBe x.toUnit(DAYS))
  inputs filterNot (_.isFinite) foreach (_ * 0d mustBe undef)
  inputs filterNot (_.isFinite) foreach (_ * -0d mustBe undef)
  inputs filterNot (_.isFinite) foreach (x => x * Double.PositiveInfinity mustBe x)
  inputs filterNot (_.isFinite) foreach (x => x * Double.NegativeInfinity mustBe -x)

  inf.toUnit(SECONDS) mustBe Double.PositiveInfinity
  minf.toUnit(MINUTES) mustBe Double.NegativeInfinity
  Duration.fromNanos(Double.PositiveInfinity) mustBe inf
  Duration.fromNanos(Double.NegativeInfinity) mustBe minf


  // test undefined & NaN

  undef.isFinite mustBe false
  -undef mustBe undef
  assert(undef != undef)
  assert(undef eq undef)

  inputs foreach (_ + undef mustBe undef)
  inputs foreach (_ - undef mustBe undef)
  inputs foreach (_ / undef mustBe nan)
  inputs foreach (_ / nan mustBe undef)
  inputs foreach (_ * nan mustBe undef)
  inputs foreach (undef + _ mustBe undef)
  inputs foreach (undef - _ mustBe undef)
  inputs foreach (undef / _ mustBe nan)
  undef / 1 mustBe undef
  undef / nan mustBe undef
  undef * 1 mustBe undef
  undef * nan mustBe undef
  inputs foreach (x => x / zero mustBe x.toUnit(SECONDS) / 0d)
  inputs foreach (x => x / 0d mustBe Duration.fromNanos(x.toUnit(NANOSECONDS) / 0d))
  inputs foreach (x => x / -0d mustBe Duration.fromNanos(x.toUnit(NANOSECONDS) / -0d))

  inputs filterNot (_ eq undef) foreach (_ compareTo undef mustBe -1)
  inputs filterNot (_ eq undef) foreach (undef compareTo _ mustBe 1)
  undef compare undef mustBe 0

  undef.toUnit(DAYS) mustBe nan
  Duration.fromNanos(nan) mustBe undef


  // test overflow protection
  for (unit ← Seq(DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS)) {
    val x = unit.convert(Long.MaxValue, NANOSECONDS)
    val dur = Duration(x, unit)
    val mdur = Duration(-x, unit)
    -mdur mustBe (dur)
    intercept[IllegalArgumentException] { Duration(x + 10000000d, unit) }
    intercept[IllegalArgumentException] { Duration(-x - 10000000d, unit) }
    if (unit != NANOSECONDS) {
      intercept[IllegalArgumentException] { Duration(x + 1, unit) }
      intercept[IllegalArgumentException] { Duration(-x - 1, unit) }
    }
    intercept[IllegalArgumentException] { dur + 1.day }
    intercept[IllegalArgumentException] { mdur - 1.day }
    intercept[IllegalArgumentException] { dur * 1.1 }
    intercept[IllegalArgumentException] { mdur * 1.1 }
    intercept[IllegalArgumentException] { dur * 2.1 }
    intercept[IllegalArgumentException] { mdur * 2.1 }
    intercept[IllegalArgumentException] { dur / 0.9 }
    intercept[IllegalArgumentException] { mdur / 0.9 }
    intercept[IllegalArgumentException] { dur / 0.4 }
    intercept[IllegalArgumentException] { mdur / 0.4 }
    Duration(x + unit.toString.toLowerCase)
    Duration("-" + x + unit.toString.toLowerCase)
    intercept[IllegalArgumentException] { Duration("%.0f".format(x + 10000000d) + unit.toString.toLowerCase) }
    intercept[IllegalArgumentException] { Duration("-%.0f".format(x + 10000000d) + unit.toString.toLowerCase) }
  }
  intercept[IllegalArgumentException] { Duration.fromNanos(1e20) }
  intercept[IllegalArgumentException] { Duration.fromNanos(-1e20) }


  // test precision
  1.second + 1.millisecond mustBe 1001.milliseconds
  100000.days + 1.nanosecond mustBe 8640000000000000001L.nanoseconds
  1.5.seconds.toSeconds mustBe 1
  (-1.5).seconds.toSeconds mustBe -1


  // test unit stability
  1000.millis.unit mustBe MILLISECONDS
  (1000.millis + 0.days).unit mustBe MILLISECONDS
  1.second.unit mustBe SECONDS
  (1.second + 1.millisecond).unit mustBe MILLISECONDS


  // test Deadline
  val dead = 2.seconds.fromNow
  val dead2 = 2 seconds fromNow

  { val l = dead.timeLeft; assert(l > 1.second, s"$l <= 1.second") }
  { val l = dead2.timeLeft; assert(l > 1.second, s"$l <= 1.second") }

  Thread.sleep(1.second.toMillis)

  // unfortunately it can happen that the sleep() returns early without throwing
  { val l = dead.timeLeft; assert(l <= 1100.millis, s"$l > 1100.millis") }
  { val l = dead2.timeLeft; assert(l <= 1100.millis, s"$l > 1100.millis") }


  // test integer mul/div
  500.millis * 2 mustBe 1.second
  (500.millis * 2).unit mustBe MILLISECONDS
  1.second / 2 mustBe 500.millis
  (1.second / 2).unit mustBe MILLISECONDS


  // check statically retaining finite-ness
  val finiteDuration: FiniteDuration = 1.second * 2 / 3 mul 5 div 4 plus 3.seconds minus 1.millisecond min 1.second max 1.second
  val finite2: FiniteDuration = 2 * 1.second + 3L * 2.seconds
  finite2 mustBe 8.seconds
  ((2 seconds fromNow).timeLeft: FiniteDuration) < 4.seconds mustBe true
  val finite3: FiniteDuration = 3.5 seconds span

}
