// See LICENSE for license details.

package firrtlTests.options.phases

import java.io.{File, PrintWriter}

import firrtl.AnnotationSeq
import firrtl.annotations.{AnnotationFileNotFoundException, JsonProtocol, NoTargetAnnotation}
import firrtl.options.phases.GetIncludes
import firrtl.options.{InputAnnotationFileAnnotation, Phase}
import firrtl.util.BackendCompilationUtilities
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

case object A extends NoTargetAnnotation
case object B extends NoTargetAnnotation
case object C extends NoTargetAnnotation
case object D extends NoTargetAnnotation
case object E extends NoTargetAnnotation

class GetIncludesSpec extends AnyFlatSpec with Matchers with BackendCompilationUtilities with firrtl.testutils.Utils {

  val dir = new File("test_run_dir/GetIncludesSpec")
  dir.mkdirs()

  def ref(filename: String): InputAnnotationFileAnnotation = InputAnnotationFileAnnotation(s"$dir/$filename.anno.json")

  def checkAnnos(a: AnnotationSeq, b: AnnotationSeq): Unit = {
    info("read the expected number of annotations")
    a.size should be(b.size)

    info("annotations match exact order")
    a.zip(b).foreach { case (ax, bx) => ax should be(bx) }
  }

  val files = Seq(
    new File(dir + "/a.anno.json") -> Seq(A, ref("b")),
    new File(dir + "/b.anno.json") -> Seq(B, ref("c"), ref("a")),
    new File(dir + "/c.anno.json") -> Seq(C, ref("d"), ref("e")),
    new File(dir + "/d.anno.json") -> Seq(D),
    new File(dir + "/e.anno.json") -> Seq(E)
  )

  files.foreach {
    case (file, annotations) =>
      val pw = new PrintWriter(file)
      pw.write(JsonProtocol.serialize(annotations))
      pw.close()
  }

  class Fixture { val phase: Phase = new GetIncludes }

  behavior.of(classOf[GetIncludes].toString)

  it should "throw an exception if the annotation file doesn't exit" in new Fixture {
    intercept[AnnotationFileNotFoundException] { phase.transform(Seq(ref("f"))) }.getMessage should startWith(
      "Annotation file"
    )
  }

  it should "read annotations from a file" in new Fixture {
    val e = ref("e")
    val in = Seq(e)
    val expect = Seq(E)
    val out = phase.transform(in)

    checkAnnos(out, expect)
  }

  it should "read annotations from multiple files, but not reading duplicates" in new Fixture {
    val Seq(d, e) = Seq("d", "e").map(ref)
    val in = Seq(d, e, e, d)
    val expect = Seq(D, E)
    val (stdout, _, out) = grabStdOutErr { phase.transform(in) }

    checkAnnos(out, expect)

    Seq("d", "e").foreach { x =>
      info(s"a warning about '$x.anno.json' was printed")
      stdout should include(s"Warning: Annotation file ($dir/$x.anno.json) already included!")
    }
  }

  it should "handle recursive references gracefully, but show a warning" in new Fixture {
    val Seq(a, b, c, d, e) = Seq("a", "b", "c", "d", "e").map(ref)
    val in = Seq(a)
    val expect = Seq(A, B, C, D, E)
    val (stdout, _, out) = grabStdOutErr { phase.transform(in) }

    checkAnnos(out, expect)

    info("a warning about 'a.anno.json' was printed")
    stdout should include(s"Warning: Annotation file ($dir/a.anno.json)")
  }

}
