// SPDX-License-Identifier: Apache-2.0

package chiselTests

import chisel3._
import chisel3.util.Decoupled
import chisel3.stage.ChiselStage
import chisel3.testers.BasicTester

trait BundleSpecUtils {
  class BundleFooBar extends Bundle {
    val foo = UInt(16.W)
    val bar = UInt(16.W)
  }
  class BundleBarFoo extends Bundle {
    val bar = UInt(16.W)
    val foo = UInt(16.W)
  }
  class BundleFoo extends Bundle {
    val foo = UInt(16.W)
  }
  class BundleBar extends Bundle {
    val bar = UInt(16.W)
  }

  class BadSeqBundle extends Bundle {
    val bar = Seq(UInt(16.W), UInt(8.W), UInt(4.W))
  }

  class BadSeqBundleWithIgnoreSeqInBundle extends Bundle with IgnoreSeqInBundle {
    val bar = Seq(UInt(16.W), UInt(8.W), UInt(4.W))
  }

  class MyModule(output: Bundle, input: Bundle) extends Module {
    val io = IO(new Bundle {
      val in = Input(input)
      val out = Output(output)
    })
    io.out <> io.in
  }

  class BundleSerializationTest extends BasicTester {
    // Note that foo is higher order because its defined earlier in the Bundle
    val bundle = Wire(new BundleFooBar)
    bundle.foo := 0x1234.U
    bundle.bar := 0x5678.U
    // To UInt
    val uint = bundle.asUInt
    assert(uint.getWidth == 32) // elaboration time
    assert(uint === "h12345678".asUInt(32.W))
    // Back to Bundle
    val bundle2 = uint.asTypeOf(new BundleFooBar)
    assert(0x1234.U === bundle2.foo)
    assert(0x5678.U === bundle2.bar)
    stop()
  }
}

class BundleSpec extends ChiselFlatSpec with BundleSpecUtils with Utils {
  "Bundles with the same fields but in different orders" should "bulk connect" in {
    ChiselStage.elaborate { new MyModule(new BundleFooBar, new BundleBarFoo) }
  }

  "Bundles" should "follow UInt serialization/deserialization API" in {
    assertTesterPasses { new BundleSerializationTest }
  }

  "Bulk connect on Bundles" should "check that the fields match" in {
    (the[ChiselException] thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate { new MyModule(new BundleFooBar, new BundleFoo) }
    }).getMessage should include("Right Record missing field")

    (the[ChiselException] thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate { new MyModule(new BundleFoo, new BundleFooBar) }
    }).getMessage should include("Left Record missing field")
  }

  "Bundles" should "not be able to use Seq for constructing hardware" in {
    (the[ChiselException] thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate {
        new Module {
          val io = IO(new Bundle {
            val b = new BadSeqBundle
          })
        }
      }
    }).getMessage should include("Public Seq members cannot be used to define Bundle elements")
  }

  "Bundles" should "be allowed to have Seq if need be" in {
    assertTesterPasses {
      new BasicTester {
        val m = Module(new Module {
          val io = IO(new Bundle {
            val b = new BadSeqBundleWithIgnoreSeqInBundle
          })
        })
        stop()
      }
    }
  }

  "Bundles" should "be allowed to have non-Chisel Seqs" in {
    assertTesterPasses {
      new BasicTester {
        val m = Module(new Module {
          val io = IO(new Bundle {
            val f = Output(UInt(8.W))
            val unrelated = (0 to 10).toSeq
            val unrelated2 = Seq("Hello", "World", "Chisel")
          })
          io.f := 0.U
        })
        stop()
      }
    }
  }

  "Bundles" should "with aliased fields, should show a helpful error message" in {
    class AliasedBundle extends Bundle {
      val a = UInt(8.W)
      val b = a
      val c = SInt(8.W)
      val d = c
    }

    (the[ChiselException] thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate {
        new Module {
          val io = IO(Output(new AliasedBundle))
          io.a := 0.U
          io.b := 1.U
        }
      }
    }).getMessage should include("contains aliased fields named (a,b),(c,d)")
  }

  "Bundles" should "not have bound hardware" in {
    (the[ChiselException] thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate {
        new Module {
          class MyBundle(val foo: UInt) extends Bundle
          val in = IO(Input(new MyBundle(123.U))) // This should error: value passed in instead of type
          val out = IO(Output(new MyBundle(UInt(8.W))))

          out := in
        }
      }
    }).getMessage should include("MyBundle contains hardware fields: foo: UInt<7>(123)")
  }
  "Bundles" should "not recursively contain aggregates with bound hardware" in {
    (the[ChiselException] thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate {
        new Module {
          class MyBundle(val foo: UInt) extends Bundle
          val out = IO(Output(Vec(2, UInt(8.W))))
          val in = IO(Input(new MyBundle(out(0)))) // This should error: Bound aggregate passed
          out := in
        }
      }
    }).getMessage should include("Bundle: MyBundle contains hardware fields: foo: BundleSpec_Anon.out")
  }
  "Unbound bundles sharing a field" should "not error" in {
    ChiselStage.elaborate {
      new RawModule {
        val foo = new Bundle {
          val x = UInt(8.W)
        }
        val bar = new Bundle {
          val y = foo.x
        }
      }
    }
  }

  // This tests the interaction of override def cloneType and the plugin.
  // We are commenting it for now because although this code fails to compile
  // as expected when just copied here, the test version is not seeing the failure.
  // """
  //     class BundleBaz(w: Int) extends Bundle {
  //       val baz = UInt(w.W)
  //       // This is a compiler error when using the plugin, which we test below.
  //       override def cloneType = (new BundleBaz(w)).asInstanceOf[this.type]
  //     }
  // """ shouldNot compile

}
