// SPDX-License-Identifier: Apache-2.0

package firrtlTests

import firrtl.RenameMap
import firrtl.RenameMap.IllegalRenameException
import firrtl.annotations._
import firrtl.annotations.TargetToken.{Instance, OfModule}
import firrtl.analyses.InstanceKeyGraph
import firrtl.testutils._

class RenameMapSpec extends FirrtlFlatSpec {
  val cir = CircuitTarget("Top")
  val cir2 = CircuitTarget("Pot")
  val cir3 = CircuitTarget("Cir3")
  val modA = cir.module("A")
  val modA2 = cir2.module("A")
  val modB = cir.module("B")
  val foo = modA.ref("foo")
  val foo2 = modA2.ref("foo")
  val bar = modA.ref("bar")
  val fizz = modA.ref("fizz")
  val fooB = modB.ref("foo")
  val barB = modB.ref("bar")

  val tmb = cir.module("Top").instOf("mid", "Middle").instOf("bot", "Bottom")
  val tm2b = cir.module("Top").instOf("mid", "Middle2").instOf("bot", "Bottom")
  val middle = cir.module("Middle")
  val middle2 = cir.module("Middle2")

  behavior.of("RenameMap")

  it should "return None if it does not rename something" in {
    val renames = RenameMap()
    renames.get(modA) should be(None)
    renames.get(foo) should be(None)
  }

  it should "return a Seq of renamed things if it does rename something" in {
    val renames = RenameMap()
    renames.record(foo, bar)
    renames.get(foo) should be(Some(Seq(bar)))
  }

  it should "allow something to be renamed to multiple things" in {
    val renames = RenameMap()
    renames.record(foo, bar)
    renames.record(foo, fizz)
    renames.get(foo) should be(Some(Seq(bar, fizz)))
  }

  it should "allow something to be renamed to nothing (ie. deleted)" in {
    val renames = RenameMap()
    renames.record(foo, Seq())
    renames.get(foo) should be(Some(Seq()))
  }

  it should "return None if something is renamed to itself" in {
    val renames = RenameMap()
    renames.record(foo, foo)
    renames.get(foo) should be(None)
  }

  it should "allow targets to change module" in {
    val renames = RenameMap()
    renames.record(foo, fooB)
    renames.get(foo) should be(Some(Seq(fooB)))
  }

  it should "rename targets if their module is renamed" in {
    val renames = RenameMap()
    renames.record(modA, modB)
    renames.get(foo) should be(Some(Seq(fooB)))
    renames.get(bar) should be(Some(Seq(barB)))
  }

  it should "not rename already renamed targets if the module of the target is renamed" in {
    val renames = RenameMap()
    renames.record(modA, modB)
    renames.record(foo, bar)
    renames.get(foo) should be(Some(Seq(bar)))
  }

  it should "rename modules if their circuit is renamed" in {
    val renames = RenameMap()
    renames.record(cir, cir2)
    renames.get(modA) should be(Some(Seq(modA2)))
  }

  it should "rename targets if their circuit is renamed" in {
    val renames = RenameMap()
    renames.record(cir, cir2)
    renames.get(foo) should be(Some(Seq(foo2)))
  }

  val TopCircuit = cir
  val Top = cir.module("Top")
  val Top_m = Top.instOf("m", "Middle")
  val Top_m_l = Top_m.instOf("l", "Leaf")
  val Top_m_l_a = Top_m_l.ref("a")
  val Top_m_la = Top_m.ref("l").field("a")
  val Middle = cir.module("Middle")
  val Middle2 = cir.module("Middle2")
  val Middle_la = Middle.ref("l").field("a")
  val Middle_l_a = Middle.instOf("l", "Leaf").ref("a")

  it should "rename targets if modules in the path are renamed" in {
    val renames = RenameMap()
    renames.record(Middle, Middle2)
    renames.get(Top_m) should be(Some(Seq(Top.instOf("m", "Middle2"))))
  }

  it should "rename only the instance if instance and module in the path are renamed" in {
    val renames = RenameMap()
    renames.record(Middle, Middle2)
    renames.record(Top.instOf("m", "Middle"), Top.instOf("m2", "Middle"))
    renames.get(Top_m) should be(Some(Seq(Top.instOf("m2", "Middle"))))
  }

  it should "rename targets if instance in the path are renamed" in {
    val renames = RenameMap()
    renames.record(Top.instOf("m", "Middle"), Top.instOf("m2", "Middle"))
    renames.get(Top_m) should be(Some(Seq(Top.instOf("m2", "Middle"))))
  }

  it should "rename targets if instance and ofmodule in the path are renamed" in {
    val renames = RenameMap()
    val Top_m2 = Top.instOf("m2", "Middle2")
    renames.record(Top_m, Top_m2)
    renames.get(Top_m) should be(Some(Seq(Top_m2)))
  }

  it should "properly do nothing if no remaps" in {
    val renames = RenameMap()
    renames.get(Top_m_l_a) should be(None)
  }

  it should "properly rename if leaf is inlined" in {
    val renames = RenameMap()
    renames.record(Middle_l_a, Middle_la)
    renames.get(Top_m_l_a) should be(Some(Seq(Top_m_la)))
  }

  it should "properly rename if middle is inlined" in {
    val renames = RenameMap()
    renames.record(Top_m_l, Top.instOf("m_l", "Leaf"))
    renames.get(Top_m_l_a) should be(Some(Seq(Top.instOf("m_l", "Leaf").ref("a"))))
  }

  it should "properly rename if leaf and middle are inlined" in {
    val renames = RenameMap()
    val inlined = Top.ref("m_l_a")
    renames.record(Top_m_l_a, inlined)
    renames.record(Top_m_l, Nil)
    renames.record(Top_m, Nil)
    renames.get(Top_m_l_a) should be(Some(Seq(inlined)))
  }

  it should "quickly rename a target with a long path" in {
    (0 until 50 by 10).foreach { endIdx =>
      val renames = RenameMap()
      renames.record(TopCircuit.module("Y0"), TopCircuit.module("X0"))
      val deepTarget = (0 until endIdx)
        .foldLeft(Top: IsModule) { (t, idx) =>
          t.instOf("a", "A" + idx)
        }
        .ref("ref")
      val (millis, rename) = firrtl.Utils.time(renames.get(deepTarget))
    //rename should be(None)
    }
  }

  it should "rename only once with multiple renames" in {
    val renames = RenameMap()
    val Middle2 = cir.module("Middle2")
    renames.record(Middle, Middle2)
    renames.record(Middle.ref("l"), Middle.ref("lx"))
    renames.get(Middle.ref("l")) should be(Some(Seq(Middle.ref("lx"))))
  }

  it should "rename with fields" in {
    val Middle_o = Middle.ref("o")
    val Middle_i = Middle.ref("i")
    val Middle_o_f = Middle.ref("o").field("f")
    val Middle_i_f = Middle.ref("i").field("f")
    val renames = RenameMap()
    renames.record(Middle_o, Middle_i)
    renames.get(Middle_o_f) should be(Some(Seq(Middle_i_f)))
  }

  it should "rename instances with same ofModule" in {
    val Middle_o = Middle.instOf("o", "O")
    val Middle_i = Middle.instOf("i", "O")
    val renames = RenameMap()
    renames.record(Middle_o, Middle_i)
    renames.get(Middle.instOf("o", "O")) should be(Some(Seq(Middle.instOf("i", "O"))))
  }

  it should "not treat references as instances targets" in {
    val Middle_o = Middle.ref("o")
    val Middle_i = Middle.ref("i")
    val renames = RenameMap()
    renames.record(Middle_o, Middle_i)
    renames.get(Middle.instOf("o", "O")) should be(None)
  }

  it should "be able to rename weird stuff" in {
    // Renaming `from` to each of the `tos` at the same time should be ok
    case class BadRename(from: CompleteTarget, tos: Seq[CompleteTarget])
    val badRenames =
      Seq( //BadRename(foo, Seq(cir)),
        //BadRename(foo, Seq(modB)),
        //BadRename(modA, Seq(fooB)),
        //BadRename(modA, Seq(cir)),
        //BadRename(cir, Seq(foo)),
        //BadRename(cir, Seq(modA)),
        BadRename(cir, Seq(cir2, cir3))
      )
    // Run all BadRename tests
    for (BadRename(from, tos) <- badRenames) {
      val fromN = from
      val tosN = tos.mkString(", ")
      //it should s"error if a $fromN is renamed to $tosN" in {
      val renames = RenameMap()
      for (to <- tos) {
        (from, to) match {
          case (f: CircuitTarget, t: CircuitTarget) => renames.record(f, t)
          case (f: IsMember, t: IsMember) => renames.record(f, t)
          case _ => sys.error("Unexpected!")
        }
      }
      //a [FIRRTLException] shouldBe thrownBy {
      renames.get(from)
      //}
      //}
    }
  }

  it should "not error if a circular rename occurs" in {
    val renames = RenameMap()
    val top = CircuitTarget("Top")
    renames.record(top.module("A"), top.module("B").instOf("c", "C"))
    renames.record(top.module("B"), top.module("A").instOf("c", "C"))
    renames.get(top.module("A")) should be {
      Some(Seq(top.module("B").instOf("c", "C")))
    }
  }

  it should "not error if a swapping rename occurs" in {
    val renames = RenameMap()
    val top = CircuitTarget("Top")
    renames.record(top.module("A"), top.module("B"))
    renames.record(top.module("B"), top.module("A"))
    renames.get(top.module("A")) should be(Some(Seq(top.module("B"))))
    renames.get(top.module("B")) should be(Some(Seq(top.module("A"))))
  }

  it should "error if a reference is renamed to a module and vice versa" in {
    val renames = RenameMap()
    val top = CircuitTarget("Top")
    renames.record(top.module("A").ref("ref"), top.module("B"))
    renames.record(top.module("C"), top.module("D").ref("ref"))
    a[IllegalRenameException] shouldBe thrownBy {
      renames.get(top.module("C"))
    }
    a[IllegalRenameException] shouldBe thrownBy {
      renames.get(top.module("A").ref("ref").field("field"))
    }
    renames.get(top.module("A").instOf("ref", "R")) should be(None)
  }

  it should "error if we rename an instance's ofModule into a non-module" in {
    val renames = RenameMap()
    val top = CircuitTarget("Top")

    renames.record(top.module("C"), top.module("D").ref("x"))
    a[IllegalRenameException] shouldBe thrownBy {
      renames.get(top.module("A").instOf("c", "C"))
    }
  }

  it should "error if path is renamed into a non-path" ignore {
    val renames = RenameMap()
    val top = CircuitTarget("Top")

    renames.record(top.module("E").instOf("f", "F"), top.module("E").ref("g"))

    a[IllegalRenameException] shouldBe thrownBy {
      renames.get(top.module("E").instOf("f", "F").ref("g"))
    }
  }

  it should "rename reference targets with paths if their corresponding pathless target are renamed" in {
    val cir = CircuitTarget("Top")
    val modTop = cir.module("Top")
    val modA = cir.module("A")

    val aggregate = modA.ref("agg")
    val subField1 = aggregate.field("field1")
    val subField2 = aggregate.field("field2")

    val lowered1 = aggregate.copy(ref = "agg_field1")
    val lowered2 = aggregate.copy(ref = "agg_field2")

    // simulating LowerTypes transform
    val renames = RenameMap()
    renames.record(subField1, lowered1)
    renames.record(subField2, lowered2)
    renames.record(aggregate, Seq(lowered1, lowered2))

    val path = modTop.instOf("b", "B").instOf("a", "A")
    val testRef1 = subField1.setPathTarget(path)

    renames.get(testRef1) should be {
      Some(Seq(testRef1.copy(ref = "agg_field1", component = Nil)))
    }
  }

  // ~Top|Module/i1:I1>foo.field, where rename map contains ~Top|Module/i1:I1>foo -> ~Top|Module/i1:I1>bar.
  it should "properly rename reference targets with the same paths" in {
    val cir = CircuitTarget("Top")
    val modTop = cir.module("Top")
    val mod = cir.module("Module")

    val path = mod.instOf("i1", "I1")
    val oldAgg = mod.ref("foo").setPathTarget(path)
    val newAgg = mod.ref("bar").setPathTarget(path)

    val renames = RenameMap()
    renames.record(oldAgg, newAgg)

    val testRef = oldAgg.field("field")
    renames.get(testRef) should be {
      Some(Seq(newAgg.field("field")))
    }
  }

  it should "properly rename reference targets with partially matching paths" in {
    val cir = CircuitTarget("Top")
    val modTop = cir.module("Top")
    val modA = cir.module("A")
    val modB = cir.module("B")

    val path = modB.instOf("a", "A")
    val oldRef = modA.ref("oldRef").setPathTarget(path)
    val newRef = modA.ref("newRef").setPathTarget(path)

    val renames = RenameMap()
    renames.record(oldRef, newRef)

    val testRef = oldRef.addHierarchy("B", "b")
    renames.get(testRef) should be {
      Some(Seq(newRef.addHierarchy("B", "b")))
    }
  }

  it should "properly rename reference targets with partially matching paths and partially matching target tokens" in {
    val cir = CircuitTarget("Top")
    val modTop = cir.module("Top")
    val modA = cir.module("A")
    val modB = cir.module("B")

    val path = modB.instOf("a", "A")
    val oldAgg = modA.ref("oldAgg").setPathTarget(path).field("field1")
    val newAgg = modA.ref("newAgg").setPathTarget(path)

    val renames = RenameMap()
    renames.record(oldAgg, newAgg)

    val testRef = oldAgg.addHierarchy("B", "b").field("field2")
    renames.get(testRef) should be {
      Some(Seq(newAgg.addHierarchy("B", "b").field("field2")))
    }
  }

  it should "rename targets with multiple renames starting from most specific to least specific" in {
    val cir = CircuitTarget("Top")
    val modTop = cir.module("Top")
    val modA = cir.module("A")
    val modB = cir.module("B")
    val modC = cir.module("C")

    // from: ~Top|A/b:B/c:C>ref.f1.f2.f3
    // to: ~Top|A/b:B/c:C>ref.f1.f2.f333
    // renamed first because it is an exact match
    val from1 = modA
      .instOf("b", "B")
      .instOf("c", "C")
      .ref("ref")
      .field("f1")
      .field("f2")
      .field("f3")
    val to1 = modA
      .instOf("b", "B")
      .instOf("c", "C")
      .ref("ref")
      .field("f1")
      .field("f2")
      .field("f33")

    // from: ~Top|A/b:B/c:C>ref.f1.f2
    // to: ~Top|A/b:B/c:C>ref.f1
    // renamed second because it is a parent target
    val from2 = modA
      .instOf("b", "B")
      .instOf("c", "C")
      .ref("ref")
      .field("f1")
      .field("f2")
    val to2 = modA
      .instOf("b", "B")
      .instOf("c", "C")
      .ref("ref")
      .field("f1")
      .field("f22")

    // from: ~Top|B/c:C>ref.f1
    // to: ~Top|B/c:C>ref.f11
    // renamed third because it has a smaller hierarchy
    val from3 = modB
      .instOf("c", "C")
      .ref("ref")
      .field("f1")
    val to3 = modB
      .instOf("c", "C")
      .ref("ref")
      .field("f11")

    // from: ~Top|C>ref
    // to: ~Top|C>refref
    // renamed last because it has no path
    val from4 = modC.ref("ref")
    val to4 = modC.ref("refref")

    val renames1 = RenameMap()
    renames1.record(from1, to1)
    renames1.record(from2, to2)
    renames1.record(from3, to3)
    renames1.record(from4, to4)

    renames1.get(from1) should be {
      Some(
        Seq(
          modA
            .instOf("b", "B")
            .instOf("c", "C")
            .ref("ref")
            .field("f1")
            .field("f2")
            .field("f33")
        )
      )
    }

    val renames2 = RenameMap()
    renames2.record(from2, to2)
    renames2.record(from3, to3)
    renames2.record(from4, to4)

    renames2.get(from1) should be {
      Some(
        Seq(
          modA
            .instOf("b", "B")
            .instOf("c", "C")
            .ref("ref")
            .field("f1")
            .field("f22")
            .field("f3")
        )
      )
    }

    val renames3 = RenameMap()
    renames3.record(from3, to3)
    renames3.record(from4, to4)

    renames3.get(from1) should be {
      Some(
        Seq(
          modA
            .instOf("b", "B")
            .instOf("c", "C")
            .ref("ref")
            .field("f11")
            .field("f2")
            .field("f3")
        )
      )
    }
  }

  it should "correctly handle renaming of modules to instances" in {
    val cir = CircuitTarget("Top")
    val renames = RenameMap()
    val from = cir.module("C")
    val to = cir.module("D").instOf("e", "E").instOf("f", "F")
    renames.record(from, to)
    renames.get(cir.module("C")) should be {
      Some(Seq(cir.module("D").instOf("e", "E").instOf("f", "F")))
    }
    renames.get(cir.module("A").instOf("b", "B").instOf("c", "C")) should be {
      Some(Seq(cir.module("A").instOf("b", "B").instOf("c", "D").instOf("e", "E").instOf("f", "F")))
    }
  }

  it should "correctly handle renaming of paths and components at the same time" in {
    val cir = CircuitTarget("Top")
    val renames = RenameMap()
    val from = cir.module("C").ref("foo").field("bar")
    val to = cir.module("D").instOf("e", "E").instOf("f", "F").ref("foo").field("foo")
    renames.record(from, to)
    renames.get(cir.module("A").instOf("b", "B").instOf("c", "C").ref("foo").field("bar")) should be {
      Some(
        Seq(
          cir
            .module("A")
            .instOf("b", "B")
            .instOf("c", "D")
            .instOf("e", "E")
            .instOf("f", "F")
            .ref("foo")
            .field("foo")
        )
      )
    }
  }

  it should "error if an instance is renamed to a ReferenceTarget" in {
    val top = CircuitTarget("Top").module("Top")
    val renames = RenameMap()
    val from = top.instOf("a", "A")
    val to = top.ref("b")
    renames.record(from, to)
    a[IllegalRenameException] shouldBe thrownBy {
      renames.get(from)
    }
  }

  it should "not allow renaming of instances even if there is a matching reference rename" in {
    val top = CircuitTarget("Top").module("Top")
    val renames = RenameMap()
    val from = top.ref("a")
    val to = top.ref("b")
    renames.record(from, to)
    renames.get(top.instOf("a", "Foo")) should be(None)
  }

  it should "correctly chain renames together" in {
    val top = CircuitTarget("Top")

    val renames1 = RenameMap()
    val from1 = top.module("A")
    val to1 = top.module("Top").instOf("b", "B")
    renames1.record(from1, to1)

    val renames2 = RenameMap()
    val from2 = top.module("B")
    val to2 = top.module("B1")
    renames2.record(from2, to2)

    val renames = renames1.andThen(renames2)

    renames.get(from1) should be {
      Some(Seq(top.module("Top").instOf("b", "B1")))
    }

    renames.get(from2) should be {
      Some(Seq(to2))
    }
  }

  it should "correctly chain deleted targets" in {
    val top = CircuitTarget("Top")
    val modA = top.module("A")
    val modA1 = top.module("A1")
    val modB = top.module("B")
    val modB1 = top.module("B1")

    val renames1 = RenameMap()
    renames1.delete(modA)
    renames1.record(modB, modB1)

    val renames2 = RenameMap()
    renames2.record(modA, modA1)
    renames2.delete(modB1)

    val renames = renames1.andThen(renames2)

    renames.get(modA) should be {
      Some(Seq.empty)
    }
    renames.get(modB) should be {
      Some(Seq.empty)
    }
  }

  it should "correctly ++ renameMaps" in {
    val top = CircuitTarget("Top")
    val modA = top.module("A")
    val modA1 = top.module("A1")
    val modA2 = top.module("A1")
    val modB = top.module("B")
    val modB1 = top.module("B1")
    val modC = top.module("C")
    val modC1 = top.module("C1")

    val renames1 = RenameMap()
    renames1.record(modA, modA1)
    renames1.record(modC, modC1)

    val renames2 = RenameMap()
    renames2.record(modA, modA2)
    renames2.record(modB, modB1)

    val renames = renames1 ++ renames2
    renames.get(modA) should be {
      Some(Seq(modA2))
    }
    renames.get(modB) should be {
      Some(Seq(modB1))
    }
    renames.get(modC) should be {
      Some(Seq(modC1))
    }
  }

  it should "be able to inline instances" in {
    val top = CircuitTarget("Top")
    val inlineRename1 = {
      val inlineMod = top.module("A")
      val inlineInst = top.module("A").instOf("b", "B")
      val oldRef = inlineMod.ref("bar")
      val prefixRef = inlineMod.ref("foo")

      val renames1 = RenameMap()
      renames1.record(inlineInst, inlineMod)

      val renames2 = RenameMap()
      renames2.record(oldRef, prefixRef)

      renames1.andThen(renames2)
    }

    val inlineRename2 = {
      val inlineMod = top.module("A1")
      val inlineInst = top.module("A1").instOf("b", "B1")
      val oldRef = inlineMod.ref("bar")
      val prefixRef = inlineMod.ref("foo")

      val renames1 = RenameMap()
      renames1.record(inlineInst, inlineMod)

      val renames2 = RenameMap()
      renames2.record(oldRef, prefixRef)

      renames1.andThen(renames2)
    }

    val renames = inlineRename1 ++ inlineRename2
    renames.get(top.module("A").instOf("b", "B").ref("bar")) should be {
      Some(Seq(top.module("A").ref("foo")))
    }

    renames.get(top.module("A1").instOf("b", "B1").ref("bar")) should be {
      Some(Seq(top.module("A1").ref("foo")))
    }
  }

  it should "be able to dedup modules" in {
    val top = CircuitTarget("Top")
    val topMod = top.module("Top")
    val dedupedMod = top.module("A")
    val dupMod1 = top.module("A1")
    val dupMod2 = top.module("A2")

    val relPath1 = dupMod1.addHierarchy("Foo", "a") //top.module("Foo").instOf("a", "A1")
    val relPath2 = dupMod2.addHierarchy("Foo", "a") //top.module("Foo").instOf("a", "A2")

    val absPath1 = relPath1.addHierarchy("Top", "foo")
    val absPath2 = relPath2.addHierarchy("Top", "foo")

    val renames1 = RenameMap()
    renames1.record(dupMod1, absPath1)
    renames1.record(dupMod2, absPath2)
    renames1.record(relPath1, absPath1)
    renames1.record(relPath2, absPath2)

    val renames2 = RenameMap()
    renames2.record(dupMod1, dedupedMod)
    renames2.record(dupMod2, dedupedMod)

    val renames = renames1.andThen(renames2)

    renames.get(dupMod1.instOf("foo", "Bar").ref("ref")) should be {
      Some(Seq(absPath1.copy(ofModule = "A").instOf("foo", "Bar").ref("ref")))
    }

    renames.get(dupMod2.ref("ref")) should be {
      Some(Seq(absPath2.copy(ofModule = "A").ref("ref")))
    }

    renames.get(absPath1.instOf("b", "B").ref("ref")) should be {
      Some(Seq(absPath1.copy(ofModule = "A").instOf("b", "B").ref("ref")))
    }
  }

  it should "should able to chain many rename maps" in {
    val top = CircuitTarget("Top")
    val inlineRename1 = {
      val inlineMod = top.module("A")
      val inlineInst = top.module("A").instOf("b", "B")
      val oldRef = inlineMod.ref("bar")
      val prefixRef = inlineMod.ref("foo")

      val renames1 = RenameMap()
      renames1.record(inlineInst, inlineMod)

      val renames2 = RenameMap()
      renames2.record(oldRef, prefixRef)

      renames1.andThen(renames2)
    }

    val inlineRename2 = {
      val inlineMod = top.module("A1")
      val inlineInst = top.module("A1").instOf("b", "B1")
      val oldRef = inlineMod.ref("bar")
      val prefixRef = inlineMod.ref("foo")

      val renames1 = RenameMap()
      renames1.record(inlineInst, inlineMod)

      val renames2 = RenameMap()
      renames2.record(oldRef, prefixRef)

      inlineRename1.andThen(renames1).andThen(renames2)
    }

    val renames = inlineRename2
    renames.get(top.module("A").instOf("b", "B").ref("bar")) should be {
      Some(Seq(top.module("A").ref("foo")))
    }

    renames.get(top.module("A1").instOf("b", "B1").ref("bar")) should be {
      Some(Seq(top.module("A1").ref("foo")))
    }
  }

  it should "should able to chain chained rename maps" in {
    val top = CircuitTarget("Top").module("Top")
    val foo1 = top.instOf("foo1", "Mod")
    val foo2 = top.instOf("foo2", "Mod")
    val foo3 = top.instOf("foo3", "Mod")

    val bar1 = top.instOf("bar1", "Mod")
    val bar2 = top.instOf("bar2", "Mod")

    val foo1Rename = RenameMap()
    val foo2Rename = RenameMap()

    val bar1Rename = RenameMap()
    val bar2Rename = RenameMap()

    foo1Rename.record(foo1, foo2)
    foo2Rename.record(foo2, foo3)

    bar1Rename.record(foo3, bar1)
    bar2Rename.record(bar1, bar2)

    val chained1 = foo1Rename.andThen(foo2Rename)
    val chained2 = bar1Rename.andThen(bar2Rename)

    val renames = chained1.andThen(chained2)

    renames.get(foo1) should be {
      Some(Seq(bar2))
    }
  }

  it should "record a self-rename" in {
    val top = CircuitTarget("Top").module("Top")
    val foo = top.instOf("foo", "Mod")
    val bar = top.instOf("bar", "Mod")

    val r = RenameMap()

    r.record(foo, bar)
    r.record(foo, foo)

    r.get(foo) should not be (empty)
    (r.get(foo).get should contain).allOf(foo, bar)
  }

  it should "not record the same rename multiple times" in {
    val top = CircuitTarget("Top").module("Top")
    val foo = top.instOf("foo", "Mod")
    val bar = top.instOf("bar", "Mod")

    val r = RenameMap()

    r.record(foo, bar)
    r.record(foo, bar)

    r.get(foo) should not be (empty)
    r.get(foo).get should contain theSameElementsAs Seq(bar)
  }

  it should "not circularly rename" in {
    val top = CircuitTarget("Top").module("Top")
    val foo = top.instOf("foo", "Mod")
    val Mod = CircuitTarget("Top").module("Mod")
    val Mod2 = CircuitTarget("Top").module("Mod2")

    val r = RenameMap()

    r.record(foo, Mod)
    r.record(Mod, Mod2)

    r.get(foo) should not be (empty)
    r.get(foo).get should contain theSameElementsAs Seq(Mod)
    r.get(Mod).get should contain theSameElementsAs Seq(Mod2)
  }

  it should "delete instances of deleted modules" in {
    val top = CircuitTarget("Top").module("Top")
    val foo = top.instOf("foo", "Mod")
    val Mod = CircuitTarget("Top").module("Mod")

    val r = RenameMap()

    r.delete(Mod)
    r.get(foo) should be(Some(Nil))
  }

  it should "rename an instance if it has been renamed" in {
    // this could happen if the instance name needed to be uniquified to avoid clashes in LowerTypes
    val top = CircuitTarget("top").module("top")

    val r = RenameMap()
    val i = top.instOf("i", "child")
    val i_ = top.instOf("i_", "child")
    r.record(i, i_)
    r.get(i) should be(Some(Seq(i_)))
    r.get(i.ref("a")) should be(Some(Seq(i_.ref("a"))))
  }

  it should "rename references to an instance's ports if the ports of the module have been renamed" in {
    // this could happen if the port name needed to be uniquified to avoid clashes in LowerTypes
    val top = CircuitTarget("top").module("top")
    val child = CircuitTarget("top").module("child")

    val r = RenameMap()
    r.record(child.ref("a"), Seq(child.ref("a_0"), child.ref("a_1")))
    val i = top.instOf("i", "child")
    r.get(i.ref("a")) should be(Some(Seq(i.ref("a_0"), i.ref("a_1"))))
  }

  it should "rename references to renamed instance's ports if the ports of the module have been renamed" in {
    // this could happen if the instance as well as the port name needed to be uniquified to avoid clashes in LowerTypes
    val top = CircuitTarget("top").module("top")
    val child = CircuitTarget("top").module("child")

    val portRenames = RenameMap()
    portRenames.record(child.ref("a"), Seq(child.ref("a_0"), child.ref("a_1")))

    val instanceRenames = RenameMap()
    val i = top.instOf("i", "child")
    val i_ = top.instOf("i_", "child")
    instanceRenames.record(i, i_)

    // The port and instance renames must be *explicitly* chained!
    val r = portRenames.andThen(instanceRenames)
    r.get(i.ref("a")) should be(Some(Seq(i_.ref("a_0"), i_.ref("a_1"))))
  }
}
