import scala.tools.nsc.Settings
import scala.tools.nsc.interpreter.{ ILoop, replProps }
import scala.tools.nsc.settings.ClassPathRepresentationType
import scala.tools.partest._

object Test extends StoreReporterDirectTest {
  def code = ???

  lazy val headerLength = replProps.welcome.lines.size
  lazy val promptLength = replProps.prompt.lines.size - 1  // extra newlines

  def compileCode(code: String, jarFileName: String) = {
    val classpath = List(sys.props("partest.lib"), testOutput.path) mkString sys.props("path.separator")
    compileString(newCompiler("-cp", classpath, "-d", s"${testOutput.path}/$jarFileName"))(code)
  }

  // TODO flat classpath doesn't support the classpath invalidation yet so we force using the recursive one
  // it's the only test which needed such a workaround
  override def settings = {
    val settings = new Settings
    settings.YclasspathImpl.value = ClassPathRepresentationType.Recursive
    settings
  }

  def app1 = """
    package test

    object Test extends App {
      def test(): Unit = {
        println("testing...")
      }
    }"""

  def app2 = """
    package test

    object Test extends App {
      def test(): Unit = {
        println("testing differently...")
      }
    }"""

  def app3 = """
    package test

    object Test3 extends App {
      def test(): Unit = {
        println("new object in existing package")
      }
    }"""

  def app6 = """
    package test6
    class A extends Test { println("created test6.A") }
    class Z extends Test { println("created test6.Z") }
    trait Test"""

  def test1(): Unit = {
    val jar = "test1.jar"
    compileCode(app1, jar)

    val codeToRun = s"""
      |:require ${testOutput.path}/$jar
      |test.Test.test()
      |""".stripMargin.trim
    val output = ILoop.run(codeToRun, settings)
    var lines  = output.lines.drop(headerLength)
    lines      = lines drop promptLength
    val added  = lines.next
    assert (
      added.contains("Added") && added.contains("test1.jar"),
      s"[${added}] in [${output.lines.mkString("/")}]"
    )
    lines      = lines drop promptLength
    assert {
      lines.next.contains("testing...")
    }
  }

  def test2(): Unit = {
    // should reject jars with conflicting entries
    val jar1 = "test1.jar"
    val jar2 = "test2.jar"
    compileCode(app2, jar2)

    val codeToRun = s"""
      |:require ${testOutput.path}/$jar1
      |:require ${testOutput.path}/$jar2
      |""".stripMargin.trim
    val output = ILoop.run(codeToRun, settings)
    var lines  = output.lines.drop(headerLength)
    lines      = lines drop promptLength
    val added  = lines.next
    assert {
      added.contains("Added") && added.contains("test1.jar")
    }
    lines      = lines drop promptLength
    val msg    = lines.next
    assert {
      msg.contains("test2.jar") && msg.contains("existing classpath entries conflict")
    }
  }

  def test3(): Unit = {
    // should accept jars with overlapping packages, but no conflicts
    val jar1 = "test1.jar"
    val jar3 = "test3.jar"
    compileCode(app3, jar3)

    val codeToRun = s"""
      |:require ${testOutput.path}/$jar1
      |:require ${testOutput.path}/$jar3
      |test.Test3.test()
      |""".stripMargin.trim
    val output = ILoop.run(codeToRun, settings)
    var lines  = output.lines.drop(headerLength)
    lines      = lines drop promptLength
    val added  = lines.next
    assert {
      added.contains("Added") && added.contains("test1.jar")
    }
    lines      = lines drop (2 * promptLength + 1)
    assert {
      lines.next.contains("new object in existing package")
    }
  }

  def test4(): Unit = {
    // twice the same jar should be rejected
    val jar1   = "test1.jar"
    val codeToRun = s"""
      |:require ${testOutput.path}/$jar1
      |:require ${testOutput.path}/$jar1
      |""".stripMargin.trim
    val output = ILoop.run(codeToRun, settings)
    var lines  = output.lines.drop(headerLength)
    lines      = lines drop promptLength
    val added  = lines.next
    assert {
      added.contains("Added") && added.contains("test1.jar")
    }
    lines      = lines drop promptLength
    val msg    = lines.next
    assert {
      msg.contains("test1.jar") && msg.contains("existing classpath entries conflict")
    }
  }

  def test5(): Unit = {
    val codeToRun = ":require /does/not/exist.jar"
    val output = ILoop.run(codeToRun, settings)
    assert(!output.contains("NullPointerException"), output)
    assert(output.contains("Cannot load '/does/not/exist.jar'"), output)
  }

  def test6(): Unit = {
    // Avoid java.lang.NoClassDefFoundError triggered by the old approach of using a Java
    // classloader to parse .class files in order to read their names.
    val jar = "test6.jar"
    compileCode(app6, jar)
    val codeToRun = s"""
      |:require ${testOutput.path}/$jar
      |import test6._; new A; new Z
      |""".stripMargin.trim
    val output = ILoop.run(codeToRun, settings)
    assert(output.contains("created test6.A"), output)
    assert(output.contains("created test6.Z"), output)
  }

  def show(): Unit = {
    test1()
    test2()
    test3()
    test4()
    test5()
    test6()
  }
}
