package xiangshan.backend.fu.NewCSR

import chisel3._
import chisel3.experimental.SourceInfo
import chisel3.util.{Cat, Fill}
import chisel3.experimental.BundleLiterals._

import scala.language.experimental.macros


abstract class CSRBundle extends Bundle {
  val len: Int = 64

  var eventFields: Set[CSREnumType] = Set()

  override def do_asUInt(implicit sourceInfo: SourceInfo): UInt = {
    // sorted from msb to lsb
    val fields = this.getFields.sortWith((l, r) => l.lsb > r.lsb)
    var paddedFields: Seq[Data] = Seq()
    var lsb = len

    for (field <- fields) {
      val diffWidth = lsb - field.lsb - field.getWidth
      if (diffWidth > 0)
        paddedFields :+= 0.U((diffWidth).W)
      paddedFields :+= field
      lsb = field.lsb
    }

    if (fields.last.lsb > 0) {
      paddedFields :+= 0.U(fields.last.lsb.W)
    }

    Cat(paddedFields.map(x => x.asUInt))
  }

  def := (that: UInt): Unit = {
    val fields = this.getFields

    suppressEnumCastWarning {
      for (field <- fields) {
        field := field.factory.apply(that(field.lsb + field.getWidth - 1, field.lsb))
      }
    }
  }

  @inline
  def init: this.type = {
    val init = Wire(this)
    suppressEnumCastWarning {
      init.elements.foreach { case (str, field: CSREnumType) =>
        field := (if (field.init != null) field.factory(field.init.asUInt) else field.factory(0.U))
      }
    }
    init.asInstanceOf[this.type]
  }

  /**
   * filtered read connection
   *
   * CSRBundle will be filtered by CSRFields' read filter function.
   */
  def :|= [T <: CSRBundle](that: T): Unit = {
    if (this.getClass != that.getClass) {
      throw MonoConnectException(s"Type miss match! (sink :|= source) " +
        s"sink type: ${this.getClass}, " +
        s"source type: ${that.getClass}")
    }

    for ((sink: CSREnumType, source: CSREnumType)  <- this.getFields.zip(that.getFields)) {
      if (sink.rfn == null)
        sink := source // .factory.apply(sink.rfn(source.asUInt, Seq()))
      else
        sink := sink.factory(sink.rfn(source.asUInt, Seq()))
    }
  }

  def getFields: Seq[CSREnumType] = this.getElements.map(_.asInstanceOf[CSREnumType])

  def needReset: Boolean = this.getFields.exists(_.needReset)

  // used by event bundle to filter the fields need to update
  def addInEvent(fieldFns: (this.type => CSREnumType)*): this.type = {
    this.eventFields ++= fieldFns.map(fn => fn(this))
    this
  }

  override def cloneType: CSRBundle.this.type = {
    val ret = super.cloneType
    //
    (ret.getFields zip this.getFields).foreach { case (l, r) =>
      if (this.eventFields.contains(r)) {
        ret.eventFields += l
      }
    }
    ret
  }

  def & (that: CSRBundle): UInt = {
    this.asUInt & that.asUInt
  }

  def & (that: UInt): UInt = {
    require(this.asUInt.getWidth == that.getWidth,
      s"The width between left $this(${this.getWidth}) and right $that(${that.getWidth}) should be equal.")
    this.asUInt & that
  }

  def | (that: CSRBundle): UInt = {
    this.asUInt | that.asUInt
  }

  def | (that: UInt): UInt = {
    require(this.getWidth == that.getWidth)
    this.asUInt | that
  }

  def unary_~ : UInt = {
    (~this.asUInt).asUInt
  }

  def apply(num: Int) : CSREnumType = {
    this.getFields.find(x => x.lsb == num && x.msb == num).get
  }

  def apply(str: String) : CSREnumType = {
    elements(str).asInstanceOf[CSREnumType]
  }
}

object CSRBundleImplicitCast {
  class UIntField(val value: UInt) {
    def &[T <: CSRBundle] (field: T): UInt = {
      this.value & field.asUInt
    }

    def |[T <: CSRBundle] (field: T): UInt = {
      this.value | field.asUInt
    }

    def &>(that: Bool): UInt = {
      require(value.widthKnown, "The width of the left operand should be known when using >& operator")
      this.value & Fill(value.getWidth, that)
    }

    def |>(that: Bool): UInt = {
      require(value.widthKnown, "The width of the left operand should be known when using >| operator")
      this.value | Fill(value.getWidth, that)
    }
  }

  import scala.language.implicitConversions
  implicit def UIntToUIntField(uint: UInt): UIntField = new UIntField(uint)
}

object ChiselRecordForField {
  implicit class AddRecordSpecifyFields[T <: Record](val x: T) {
    def specifyField(elemFns: (T => Unit)*): Unit = {
      elemFns.foreach(_.apply(x))
    }
  }
}
