package spinal.lib.bus.bmb

import spinal.core._
import spinal.lib._
import spinal.lib.bus.misc.{BusSlaveFactoryDelayed, BusSlaveFactoryElement, SingleMapping}
import scala.collection.Seq

object BmbSlaveFactory{
  def getBmbCapabilities(accessSource : BmbAccessCapabilities,
                         addressWidth : Int,
                         dataWidth : Int) = accessSource.copy(
    addressWidth = addressWidth,
    dataWidth = dataWidth,
    lengthWidthMax = log2Up(dataWidth/8),
    alignment = BmbParameter.BurstAlignement.LENGTH,
    canMask = false
  )
}

case class BmbSlaveFactory(bus: Bmb) extends BusSlaveFactoryDelayed{
  val readHaltTrigger, writeHaltTrigger = False
  val rsp = Stream(Fragment(BmbRsp(bus.p)))
  bus.rsp << rsp.haltWhen(readHaltTrigger || writeHaltTrigger).stage()


  val askWrite = (bus.cmd.valid && bus.cmd.isWrite).allowPruning()
  val askRead  = (bus.cmd.valid && bus.cmd.isRead).allowPruning()
  val doWrite  = (bus.cmd.fire && bus.cmd.isWrite).allowPruning()
  val doRead   = (bus.cmd.fire && bus.cmd.isRead).allowPruning()

  rsp.arbitrationFrom(bus.cmd)
  rsp.last := True
  when(doWrite && writeErrorFlag) {
    rsp.setError()
  } elsewhen (doRead && readErrorFlag) {
    rsp.setError()
  } otherwise {
    rsp.setSuccess()
  }
  rsp.data := 0
  rsp.context := bus.cmd.context
  rsp.source := bus.cmd.source

  override def readAddress() : UInt = bus.cmd.address
  override def writeAddress() : UInt = bus.cmd.address

  override def writeByteEnable(): Bits = bus.cmd.mask

  override def readHalt(): Unit = readHaltTrigger := True
  override def writeHalt(): Unit = writeHaltTrigger := True

  override def readFire(): Bool  = bus.cmd.fire
  override def writeFire(): Bool = bus.cmd.fire

  override def build(): Unit = {
    super.doNonStopWrite(bus.cmd.data)

    def doMappedElements(jobs : Seq[BusSlaveFactoryElement]) = super.doMappedElements(
      jobs = jobs,
      askWrite = askWrite,
      askRead = askRead,
      doWrite = doWrite,
      doRead = doRead,
      writeData = bus.cmd.data,
      readData = rsp.data
    )

    switch(bus.cmd.address) {
      for ((address, jobs) <- elementsPerAddress if address.isInstanceOf[SingleMapping]) {
        is(address.asInstanceOf[SingleMapping].address) {
          doMappedElements(jobs)
        }
      }
    }

    for ((address, jobs) <- elementsPerAddress if !address.isInstanceOf[SingleMapping]) {
      when(address.hit(bus.cmd.address)){
        doMappedElements(jobs)
      }
    }
  }

  override def busDataWidth: Int = bus.p.access.dataWidth
}
