package wacc

import org.scalatest.{ParallelTestExecution, BeforeAndAfterAll}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.Inspectors.forEvery
import java.io.File
import sys.process._
import java.io.PrintStream

class ParallelExamplesSpec extends AnyFlatSpec with BeforeAndAfterAll with ParallelTestExecution {
  val files =
    allWaccFiles("wacc-examples/valid").map { p =>
      (p.toString, List(0))
    } ++
      allWaccFiles("wacc-examples/invalid/syntaxErr").map { p =>
        (p.toString, List(100))
      } ++
      allWaccFiles("wacc-examples/invalid/semanticErr").map { p =>
        (p.toString, List(200))
      } ++
      allWaccFiles("wacc-examples/invalid/whack").map { p =>
        (p.toString, List(100, 200))
      }

  // tests go here
  forEvery(files) { (filename, expectedResult) =>
    val baseFilename = filename.stripSuffix(".wacc")
    given stdout: PrintStream = PrintStream(File(baseFilename + ".out"))
    val result = compile(filename)

    s"$filename" should "be compiled with correct result" in {
      assert(expectedResult.contains(result))
    }

    if (result == 0) it should "run with correct result" in {
      if (fileIsDisallowedBackend(filename)) pending

      // Retrieve contents to get input and expected output + exit code
      val contents = scala.io.Source.fromFile(File(filename)).getLines.toList
      val inputLine =
        contents.find(_.matches("^# ?[Ii]nput:.*$")).map(_.split(":").last.strip).getOrElse("")
      val outputLineIdx = contents.indexWhere(_.matches("^# ?[Oo]utput:.*$"))
      val expectedOutput =
        if (outputLineIdx == -1) ""
        else
          contents
            .drop(outputLineIdx + 1)
            .takeWhile(_.startsWith("#"))
            .map(_.stripPrefix("#").stripLeading)
            .mkString("\n")

      val exitLineIdx = contents.indexWhere(_.matches("^# ?[Ee]xit:.*$"))
      val expectedExit =
        if (exitLineIdx == -1) 0
        else contents(exitLineIdx + 1).stripPrefix("#").strip.toInt

      // Assembly and link using gcc
      val asmFilename = baseFilename + ".s"
      val execFilename = baseFilename
      val gccResult = s"gcc -o $execFilename -z noexecstack $asmFilename".!
      assert(gccResult == 0)

      // Run the executable with the provided input
      val stdout = new StringBuilder
      // val execResult = s"$execFilename".!(ProcessLogger(stdout.append(_)))
      val execResult =
        s"echo $inputLine" #| s"timeout 5s $execFilename" ! ProcessLogger(stdout.append(_))

      assert(execResult == expectedExit)
      assert(stdout.toString == expectedOutput)
    }
  }

  def allWaccFiles(dir: String): IndexedSeq[os.Path] =
    val d = java.io.File(dir)
    os.walk(os.Path(d.getAbsolutePath)).filter { _.ext == "wacc" }

  def fileIsDisallowedBackend(filename: String): Boolean =
    Seq(
      // format: off
      // disable formatting to avoid binPack
      "^.*wacc-examples/valid/advanced.*$",
      "^.*wacc-examples/valid/array.*$",
      "^.*wacc-examples/valid/basic/skip.*$",
      "^.*wacc-examples/valid/expressions.*$",
      "^.*wacc-examples/valid/function/nested_functions.*$",
      "^.*wacc-examples/valid/function/simple_functions.*$",
      "^.*wacc-examples/valid/if.*$",
      "^.*wacc-examples/valid/IO/print.*$",
      "^.*wacc-examples/valid/IO/read(?!echoInt\\.wacc).*$",
      "^.*wacc-examples/valid/IO/IOLoop.wacc.*$",
      "^.*wacc-examples/valid/IO/IOSequence.wacc.*$",
      "^.*wacc-examples/valid/pairs.*$",
      "^.*wacc-examples/valid/runtimeErr.*$",
      "^.*wacc-examples/valid/scope.*$",
      "^.*wacc-examples/valid/sequence.*$",
      "^.*wacc-examples/valid/variables.*$",
      "^.*wacc-examples/valid/while.*$",
      // format: on
    ).find(filename.matches).isDefined
}