package sbt
package compiler

	import org.scalacheck._
	import Prop._
	import scala.tools.nsc.reporters.StoreReporter

object EvalTest extends Properties("eval")
{
	private[this] val reporter = new StoreReporter
	import reporter.{ERROR,Info,Severity}
	private[this] val eval = new Eval(_ => reporter, None)
	
	property("inferred integer") = forAll{ (i: Int) =>
		val result = eval.eval(i.toString)
		(label("Value", value(result)) |: (value(result) == i)) &&
		(label("Type", value(result)) |: (result.tpe == IntType)) &&
		(label("Files", result.generated) |: (result.generated.isEmpty))
	}

	property("explicit integer") = forAll{ (i: Int) =>
		val result = eval.eval(i.toString, tpeName = Some(IntType))
		(label("Value", value(result)) |: (value(result) == i)) &&
		(label("Type", result.tpe) |: (result.tpe == IntType)) &&
		(label("Files", result.generated) |: (result.generated.isEmpty))
	}
	
	property("type mismatch") = forAll{ (i: Int, l: Int) =>
		val line = math.abs(l)
		val src = "mismatch"
		throws(eval.eval(i.toString, tpeName =Some(BooleanType), line = line, srcName = src), classOf[RuntimeException]) &&
		hasErrors(line+1, src)
	}

	property("backed local class") = forAll{ (i: Int) =>
		IO.withTemporaryDirectory { dir =>
			val eval = new Eval(_ => reporter, backing = Some(dir))
			val result = eval.eval(local(i))
			val v = value(result).asInstanceOf[{def i: Int}].i
			(label("Value", v) |: (v == i)) &&
			(label("Type", result.tpe) |: (result.tpe == LocalType)) &&
			(label("Files", result.generated) |: (!result.generated.isEmpty))
		}
	}

	val ValTestNames = Set("x", "a")
	val ValTestContent = """
val x: Int = {
  val y: Int = 4
  y
}
val z: Double = 3.0
val a = 9
val p = {
   object B { val i = 3 }
   class C { val j = 4 }
   "asdf"
}
"""

	property("val test") = secure {
		val defs = (ValTestContent, 1 to 7) :: Nil
		val res = eval.evalDefinitions(defs, new EvalImports(Nil, ""), "<defs>", "scala.Int" :: Nil)
		label("Val names", res.valNames) |: (res.valNames.toSet == ValTestNames)
	}


	property("explicit import") = forAll(testImport("import math.abs" :: Nil))
	property("wildcard import") = forAll(testImport("import math._" :: Nil))
	property("comma-separated imports") = forAll(testImport("import util._, math._, xml._" :: Nil))
	property("multiple imports") = forAll(testImport("import util._" :: "import math._" :: "import xml._" :: Nil))

	private[this] def testImport(imports: Seq[String]): Int => Prop = i =>
		value(eval.eval("abs("+i+")", new EvalImports(imports.zipWithIndex, "imp"))) == math.abs(i)

	private[this] def local(i: Int) = "{ class ETest(val i: Int); new ETest(" + i + ") }"
	val LocalType = "Object{val i: Int}"

	private[this] def value(r: EvalResult) = r.getValue(getClass.getClassLoader)
	private[this] def hasErrors(line: Int, src: String) =
	{
		val is = reporter.infos
		("Has errors" |: (!is.isEmpty)) &&
		all(is.toSeq.map(validPosition(line,src)) :_*)
	}
	private[this] def validPosition(line: Int, src: String)(i: Info) =
	{
		val nme = i.pos.source.file.name
		(label("Severity", i.severity) |: (i.severity == ERROR)) &&
		(label("Line", i.pos.line) |: (i.pos.line == line)) &&
		(label("Name", nme) |: (nme == src))
	}
	val IntType = "Int"
	val BooleanType = "Boolean"

	def label(s: String, value: Any) = s + " (" + value + ")"
}
