// See LICENSE for license details.

package firrtlTests

import firrtl._
import firrtl.annotations._
import firrtl.passes.{InlineAnnotation, InlineInstances, ResolveKinds}
import firrtl.transforms.NoCircuitDedupAnnotation
import firrtl.testutils._
import firrtl.testutils.FirrtlCheckers._
import firrtl.stage.TransformManager
import firrtl.options.Dependency

/**
 * Tests inline instances transformation
 */
class InlineInstancesTests extends LowTransformSpec {
  def transform = new InlineInstances
  def inline(mod: String): Annotation = {
    val parts = mod.split('.')
    val modName = ModuleName(parts.head, CircuitName("Top")) // If this fails, bad input
    val name = if (parts.size == 1) modName else ComponentName(parts.tail.mkString("."), modName)
    InlineAnnotation(name)
  }
   // Set this to debug, this will apply to all tests
   // Logger.setLevel(this.getClass, Debug)
   "The module Inline" should "be inlined" in {
      val input =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    inst i of Inline
           |    i.a <= a
           |    b <= i.b
           |  module Inline :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    b <= a""".stripMargin
      val check =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    wire i_a : UInt<32>
           |    wire i_b : UInt<32>
           |    i_b <= i_a
           |    b <= i_b
           |    i_a <= a""".stripMargin
      execute(input, check, Seq(inline("Inline")))
   }

   "The all instances of Simple" should "be inlined" in {
      val input =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    inst i0 of Simple
           |    inst i1 of Simple
           |    i0.a <= a
           |    i1.a <= i0.b
           |    b <= i1.b
           |  module Simple :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    b <= a""".stripMargin
      val check =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    wire i0_a : UInt<32>
           |    wire i0_b : UInt<32>
           |    i0_b <= i0_a
           |    wire i1_a : UInt<32>
           |    wire i1_b : UInt<32>
           |    i1_b <= i1_a
           |    b <= i1_b
           |    i0_a <= a
           |    i1_a <= i0_b""".stripMargin
      execute(input, check, Seq(inline("Simple")))
   }

   "Only one instance of Simple" should "be inlined" in {
      val input =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    inst i0 of Simple
           |    inst i1 of Simple
           |    i0.a <= a
           |    i1.a <= i0.b
           |    b <= i1.b
           |  module Simple :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    b <= a""".stripMargin
      val check =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    wire i0_a : UInt<32>
           |    wire i0_b : UInt<32>
           |    i0_b <= i0_a
           |    inst i1 of Simple
           |    b <= i1.b
           |    i0_a <= a
           |    i1.a <= i0_b
           |  module Simple :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    b <= a""".stripMargin
      execute(input, check, Seq(inline("Top.i0")))
   }

   "All instances of A" should "be inlined" in {
      val input =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    inst i0 of A
           |    inst i1 of B
           |    i0.a <= a
           |    i1.a <= i0.b
           |    b <= i1.b
           |  module A :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    b <= a
           |  module B :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    inst i of A
           |    i.a <= a
           |    b <= i.b""".stripMargin
      val check =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    wire i0_a : UInt<32>
           |    wire i0_b : UInt<32>
           |    i0_b <= i0_a
           |    inst i1 of B
           |    b <= i1.b
           |    i0_a <= a
           |    i1.a <= i0_b
           |  module B :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    wire i_a : UInt<32>
           |    wire i_b : UInt<32>
           |    i_b <= i_a
           |    b <= i_b
           |    i_a <= a""".stripMargin
      execute(input, check, Seq(inline("A")))
   }

   "Non-inlined instances" should "still prepend prefix" in {
      val input =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    inst i of A
           |    i.a <= a
           |    b <= i.b
           |  module A :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    inst i of B
           |    i.a <= a
           |    b <= i.b
           |  module B :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    b <= a""".stripMargin
      val check =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    wire i_a : UInt<32>
           |    wire i_b : UInt<32>
           |    inst i_i of B
           |    i_b <= i_i.b
           |    i_i.a <= i_a
           |    b <= i_b
           |    i_a <= a
           |  module B :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    b <= a""".stripMargin
      execute(input, check, Seq(inline("A")))
   }

  "A module with nested inlines" should "still prepend prefixes" in {
    val input =
      """|circuit Top:
         |  module Top:
         |    inst foo of Foo
         |  module Foo:
         |    inst bar of Bar
         |    inst baz of Bar
         |    node foo = UInt<1>("h0")
         |  module Bar:
         |    node bar = UInt<1>("h0")
         |""".stripMargin
    val check =
      """|circuit Top:
         |  module Top:
         |    node foo_bar_bar = UInt<1>("h0")
         |    inst foo_baz of Bar
         |    node foo_foo = UInt<1>("h0")
         |  module Bar:
         |    node bar = UInt<1>("h0")
         |""".stripMargin
    execute(input, check, Seq(inline("Foo"), inline("Foo.bar")))
  }

  "An inlined module" should "NOT be prefix unique" in {
    val input =
      """|circuit Top:
         |  module Top:
         |    inst a of A
         |    node a_foo = UInt<1>("h0")
         |    node a__bar = UInt<1>("h0")
         |  module A:
         |    node bar = UInt<1>("h0")
         |""".stripMargin
    val check =
      """|circuit Top:
         |  module Top:
         |    node a_bar = UInt<1>("h0")
         |    node a_foo = UInt<1>("h0")
         |    node a__bar = UInt<1>("h0")
         |""".stripMargin
    execute(input, check, Seq(inline("A")))
  }

  /* This test is mutually exclusive with the above */
  ignore should "be prefix unique" in {
    val input =
      """|circuit Top:
         |  module Top:
         |    inst a of A
         |    node a_foo = UInt<1>("h0")
         |    node a__bar = UInt<1>("h0")
         |  module A:
         |    node bar = UInt<1>("h0")
         |""".stripMargin
    val check =
      """|circuit Top:
         |  module Top:
         |    node a___bar = UInt<1>("h0")
         |    node a_foo = UInt<1>("h0")
         |    node a__bar = UInt<1>("h0")
         |""".stripMargin
    execute(input, check, Seq(inline("A")))
  }

  it should "uniquify sanely" in {
    val input =
      """|circuit Top:
         |  module Top:
         |    inst foo of Foo
         |    node foo_ = UInt<1>("h0")
         |    node foo__bar = UInt<1>("h0")
         |  module Foo:
         |    inst bar of Bar
         |    inst baz of Bar
         |    node foo = UInt<1>("h0")
         |  module Bar:
         |    node bar = UInt<1>("h0")
         |""".stripMargin
    val check =
      """|circuit Top:
         |  module Top:
         |    node foo__bar_bar = UInt<1>("h0")
         |    inst foo__baz of Bar
         |    node foo__foo = UInt<1>("h0")
         |    node foo_ = UInt<1>("h0")
         |    node foo__bar = UInt<1>("h0")
         |  module Bar:
         |    node bar = UInt<1>("h0")
         |""".stripMargin
    execute(input, check, Seq(inline("Foo"), inline("Foo.bar")))
  }

   // ---- Errors ----
   // 1) ext module
   "External module" should "not be inlined" in {
      val input =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    inst i of A
           |    i.a <= a
           |    b <= i.b
           |  extmodule A :
           |    input a : UInt<32>
           |    output b : UInt<32>""".stripMargin
      failingexecute(input, Seq(inline("A")))
   }
   // 2) ext instance
   "External instance" should "not be inlined" in {
      val input =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    inst i of A
           |    i.a <= a
           |    b <= i.b
           |  extmodule A :
           |    input a : UInt<32>
           |    output b : UInt<32>""".stripMargin
      failingexecute(input, Seq(inline("A")))
   }
   // 3) no module
   "Inlined module" should "exist" in {
      val input =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    b <= a""".stripMargin
      failingexecute(input, Seq(inline("A")))
   }
   // 4) no inst
   "Inlined instance" should "exist" in {
      val input =
         """circuit Top :
           |  module Top :
           |    input a : UInt<32>
           |    output b : UInt<32>
           |    b <= a""".stripMargin
      failingexecute(input, Seq(inline("A")))
   }

  "Jack's Bug" should "not fail" in {

    val input = """circuit Top :
                  |  module Top :
                  |    input a : UInt<32>
                  |    output b : UInt<32>
                  |    inst i of Inline
                  |    i.a <= a
                  |    b <= i.b
                  |  module Inline :
                  |    input a : UInt<32>
                  |    output b : UInt<32>
                  |    inst child of InlineChild
                  |    child.a <= a
                  |    b <= child.b
                  |  module InlineChild :
                  |    input a : UInt<32>
                  |    output b : UInt<32>
                  |    b <= a""".stripMargin
    val check = """circuit Top :
                  |  module Top :
                  |    input a : UInt<32>
                  |    output b : UInt<32>
                  |    wire i_a : UInt<32>
                  |    wire i_b : UInt<32>
                  |    inst i_child of InlineChild
                  |    i_b <= i_child.b
                  |    i_child.a <= i_a
                  |    b <= i_b
                  |    i_a <= a
                  |  module InlineChild :
                  |    input a : UInt<32>
                  |    output b : UInt<32>
                  |    b <= a""".stripMargin
    execute(input, check, Seq(inline("Inline")))
  }

  case class DummyAnno(target: ReferenceTarget) extends SingleTargetAnnotation[ReferenceTarget] {
    override def duplicate(n: ReferenceTarget): Annotation = DummyAnno(n)
  }
  "annotations" should "be renamed" in {
     val input =
        """circuit Top :
          |  module Top :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    inst i of Inline
          |    i.a <= a
          |    b <= i.b
          |  module Inline :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    inst foo of NestedInline
          |    inst bar of NestedNoInline
          |    foo.a <= a
          |    bar.a <= foo.b
          |    b <= bar.b
          |  module NestedInline :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    b <= a
          |  module NestedNoInline :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    b <= a
          |""".stripMargin
     val check =
        """circuit Top :
          |  module Top :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    wire i_a : UInt<32>
          |    wire i_b : UInt<32>
          |    wire i_foo_a : UInt<32>
          |    wire i_foo_b : UInt<32>
          |    i_foo_b <= i_foo_a
          |    inst i_bar of NestedNoInline
          |    i_b <= i_bar.b
          |    i_foo_a <= i_a
          |    i_bar.a <= i_foo_b
          |    b <= i_b
          |    i_a <= a
          |  module NestedNoInline :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    b <= a
          |""".stripMargin
    val top = CircuitTarget("Top").module("Top")
    val inlined = top.instOf("i", "Inline")
    val nestedInlined = top.instOf("i", "Inline").instOf("foo", "NestedInline")
    val nestedNotInlined = top.instOf("i", "Inline").instOf("bar", "NestedNoInline")

     executeWithAnnos(input, check,
       Seq(
         inline("Inline"),
         inline("NestedInline"),
         NoCircuitDedupAnnotation,
         DummyAnno(inlined.ref("a")),
         DummyAnno(inlined.ref("b")),
         DummyAnno(nestedInlined.ref("a")),
         DummyAnno(nestedInlined.ref("b")),
         DummyAnno(nestedNotInlined.ref("a")),
         DummyAnno(nestedNotInlined.ref("b"))
       ),
       Seq(
         DummyAnno(top.ref("i_a")),
         DummyAnno(top.ref("i_b")),
         DummyAnno(top.ref("i_foo_a")),
         DummyAnno(top.ref("i_foo_b")),
         DummyAnno(top.instOf("i_bar", "NestedNoInline").ref("a")),
         DummyAnno(top.instOf("i_bar", "NestedNoInline").ref("b"))
       )
     )
  }

  "inlining both grandparent and grandchild" should "should work" in {
     val input =
        """circuit Top :
          |  module Top :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    inst i of Inline
          |    i.a <= a
          |    b <= i.b
          |  module Inline :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    inst foo of NestedInline
          |    inst bar of NestedNoInline
          |    foo.a <= a
          |    bar.a <= foo.b
          |    b <= bar.b
          |  module NestedInline :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    b <= a
          |  module NestedNoInline :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    inst foo of NestedInline
          |    foo.a <= a
          |    b <= foo.b
          |""".stripMargin
     val check =
        """circuit Top :
          |  module Top :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    wire i_a : UInt<32>
          |    wire i_b : UInt<32>
          |    wire i_foo_a : UInt<32>
          |    wire i_foo_b : UInt<32>
          |    i_foo_b <= i_foo_a
          |    inst i_bar of NestedNoInline
          |    i_b <= i_bar.b
          |    i_foo_a <= i_a
          |    i_bar.a <= i_foo_b
          |    b <= i_b
          |    i_a <= a
          |  module NestedNoInline :
          |    input a : UInt<32>
          |    output b : UInt<32>
          |    wire foo_a : UInt<32>
          |    wire foo_b : UInt<32>
          |    foo_b <= foo_a
          |    b <= foo_b
          |    foo_a <= a
          |""".stripMargin
    val top = CircuitTarget("Top").module("Top")
    val inlined = top.instOf("i", "Inline")
    val nestedInlined = inlined.instOf("foo", "NestedInline")
    val nestedNotInlined = inlined.instOf("bar", "NestedNoInline")
    val innerNestedInlined = nestedNotInlined.instOf("foo", "NestedInline")

     executeWithAnnos(input, check,
       Seq(
         inline("Inline"),
         inline("NestedInline"),
         DummyAnno(inlined.ref("a")),
         DummyAnno(inlined.ref("b")),
         DummyAnno(nestedInlined.ref("a")),
         DummyAnno(nestedInlined.ref("b")),
         DummyAnno(nestedNotInlined.ref("a")),
         DummyAnno(nestedNotInlined.ref("b")),
         DummyAnno(innerNestedInlined.ref("a")),
         DummyAnno(innerNestedInlined.ref("b"))
       ),
       Seq(
         DummyAnno(top.ref("i_a")),
         DummyAnno(top.ref("i_b")),
         DummyAnno(top.ref("i_foo_a")),
         DummyAnno(top.ref("i_foo_b")),
         DummyAnno(top.instOf("i_bar", "NestedNoInline").ref("a")),
         DummyAnno(top.instOf("i_bar", "NestedNoInline").ref("b")),
         DummyAnno(top.instOf("i_bar", "NestedNoInline").ref("foo_a")),
         DummyAnno(top.instOf("i_bar", "NestedNoInline").ref("foo_b"))
       )
     )
  }

  "InlineInstances" should "properly invalidate ResolveKinds" in {
    val input =
      """circuit Top :
        |  module Top :
        |    input a : UInt<32>
        |    output b : UInt<32>
        |    inst i of Inline
        |    i.a <= a
        |    b <= i.b
        |  module Inline :
        |    input a : UInt<32>
        |    output b : UInt<32>
        |    b <= a""".stripMargin

    val state = CircuitState(parse(input), ChirrtlForm, Seq(inline("Inline")))
    val manager = new TransformManager(Seq(Dependency[InlineInstances], Dependency(ResolveKinds)))
    val result = manager.execute(state)

    result shouldNot containTree { case WRef("i_a", _, PortKind, _) => true }
    result should    containTree { case WRef("i_a", _, WireKind, _) => true }
  }
}

// Execution driven tests for inlining modules
// TODO(izraelevitz) fix this test
//class InlineInstancesIntegrationSpec extends FirrtlPropSpec {
//  // Shorthand for creating annotations to inline modules
//  def inlineModules(names: Seq[String]): Seq[CircuitAnnotation] =
//    Seq(StickyCircuitAnnotation(InlineCAKind, names.map(n => ModuleName(n) -> TagAnnotation).toMap))
//
//  case class Test(name: String, dir: String, ann: Seq[CircuitAnnotation])
//
//  val runTests = Seq(
//    Test("GCDTester", "/integration", inlineModules(Seq("DecoupledGCD")))
//  )
//
//  runTests foreach { test =>
//    property(s"${test.name} should execute correctly with inlining") {
//      println(s"Got annotations ${test.ann}")
//      runFirrtlTest(test.name, test.dir, test.ann)
//    }
//  }
//}
