/*                                                                           *\
**        _____ ____  _____   _____    __                                    **
**       / ___// __ \/  _/ | / /   |  / /   HDL Lib                          **
**       \__ \/ /_/ // //  |/ / /| | / /    (c) Dolu, All rights reserved    **
**      ___/ / ____// // /|  / ___ |/ /___                                   **
**     /____/_/   /___/_/ |_/_/  |_/_____/  MIT Licence                      **
**                                                                           **
** Permission is hereby granted, free of charge, to any person obtaining a   **
** copy of this software and associated documentation files (the "Software"),**
** to deal in the Software without restriction, including without limitation **
** the rights to use, copy, modify, merge, publish, distribute, sublicense,  **
** and/or sell copies of the Software, and to permit persons to whom the     **
** Software is furnished to do so, subject to the following conditions:      **
**                                                                           **
** The above copyright notice and this permission notice shall be included   **
** in all copies or substantial portions of the Software.                    **
**                                                                           **
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS   **
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF                **
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.    **
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY      **
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT **
** OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR  **
** THE USE OR OTHER DEALINGS IN THE SOFTWARE.                                **
\*                                                                           */
package spinal.lib.bus.misc

import spinal.core._
import spinal.lib._

import scala.collection.mutable
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
import scala.collection.Seq


/**
  * Configuration of the bus Slave Factory
  *
  * @param wordEndianness Endianness for the multiWrite or multiRead operations
  */
case class BusSlaveFactoryConfig(wordEndianness: Endianness = LITTLE){
}


/**
  * Bus slave factory is a tool that provide an abstract and smooth way to define register bank
  */
trait BusSlaveFactory extends Area{

  /** Configuration of the BusSlaveFactory */
  protected var _config = BusSlaveFactoryConfig()
  def getConfig : BusSlaveFactoryConfig = _config
  def setConfig(value : BusSlaveFactoryConfig) : this.type = {
    _config = value
    this
  }

  /** Return the data width of the bus */
  def busDataWidth: Int

  /** Address incrementation used by the read and write multi words registers */
  def wordAddressInc: Int = busDataWidth / 8

  /** Set the endianness during write/read multiword */
  def setWordEndianness(value : Endianness) = setConfig(getConfig.copy(wordEndianness = value))

  def withOffset(offset : BigInt) = new BusSlaveFactoryAddressWrapper(this, offset)


  /**
    * Return true if the configuration if set to little-endian
    */
  private def isLittleWordEndianness: Boolean = getConfig.wordEndianness match{
    case LITTLE => true
    case BIG    => false
  }

  def readPrimitive[T <: Data](that          : T,
                               address       : AddressMapping,
                               bitOffset     : Int,
                               documentation : String): Unit

  def writePrimitive[T <: Data](that          : T,
                                address       : AddressMapping,
                                bitOffset     : Int,
                                documentation : String): Unit

  def onWritePrimitive(address       : AddressMapping,
                       haltSensitive : Boolean,
                       documentation : String)(doThat: => Unit): Unit

  def onReadPrimitive(address       : AddressMapping,
                      haltSensitive : Boolean,
                      documentation : String)(doThat: => Unit): Unit

  def readHalt(): Unit
  def writeHalt(): Unit

  def readFire(): Bool  = ???
  def writeFire(): Bool = ???

  def readAddress(): UInt
  def writeAddress(): UInt

  val readErrorFlag: Bool = False.allowPruning()
  val writeErrorFlag: Bool = False.allowPruning()

  def readError(): Unit = readErrorFlag := True
  def writeError(): Unit = writeErrorFlag := True

  /**
   * Byte enable bits, defaulting to all ones
   */
  def writeByteEnable(): Bits = null //B(busDataWidth / 8 bits, default -> True)

  /**
    * Permanently assign that by the bus write data from bitOffset
    */
  def nonStopWrite[T <: Data](that          : T,
                              bitOffset     : Int = 0,
                              documentation : String = null): T


  /**
    * When the bus read the address, fill the response with that at bitOffset
    */
  def read[T <: Data](that          : T,
                      address       : BigInt,
                      bitOffset     : Int = 0,
                      documentation : String = null): T = {
    readPrimitive(
      that          = that,
      address       = SingleMapping(address),
      bitOffset     = bitOffset,
      documentation = documentation
    )
    that
  }

  /**
    * When the bus write the address, assign that with bus’s data from bitOffset
    */
  def write[T <: Data](that          : T,
                       address       : BigInt,
                       bitOffset     : Int = 0,
                       documentation : String = null): T = {
    writePrimitive(
      that          = that,
      address       = SingleMapping(address),
      bitOffset     = bitOffset,
      documentation = documentation
    )
    that
  }


  /**
    * Call doThat when a write transaction occurs on address
    */
  def onWrite(address: BigInt, documentation: String = null)(doThat: => Unit): Unit = {
    onWritePrimitive(
      address       = SingleMapping(address),
      haltSensitive = true,
      documentation = documentation
    )(doThat)
  }


  /**
    * Call doThat when a read transaction occurs on address
    */
  def onRead(address: BigInt, documentation: String = null)(doThat: => Unit): Unit = {
    onReadPrimitive(
      address       = SingleMapping(address),
      haltSensitive = true,
      documentation = documentation
    )(doThat)
  }

  def write[T <: Data](address    : BigInt,
                       bitMapping : (Int, Data)*): Unit = {
    bitMapping.foreach{ case (bitId, that) => write(that, address, bitId) }
  }

  def read[T <: Data](address    : BigInt,
                       bitMapping : (Int, Data)*): Unit = {
    bitMapping.foreach{ case (bitId, that) => read(that, address, bitId) }
  }


  /**
    * Make that readable and writable at address and placed at bitOffset in the word
    */
  def readAndWrite(that          : Data,
                   address       : BigInt,
                   bitOffset     : Int = 0,
                   documentation : String = null): Unit = {
    write(that, address, bitOffset, documentation)
    read(that, address, bitOffset, documentation)
  }

  /** Return true if the bus is writing */
  def isWriting(address: BigInt): Bool = {
    val ret = False
    onWrite(address){ ret := True }
    ret
  }

  /** Return true if the bus is reading */
  def isReading (address: BigInt): Bool = {
    val ret = False
    onRead(address){ ret := True }
    ret
  }

  /**
    * Create the memory mapping to read `that` from `address`
    * If `that` is bigger than one word it extends the register on following addresses.
    */
  def readMultiWord(that: Data, address: BigInt, documentation: String = null): Unit  = {
    // round up
    val wordCount = (widthOf(that) - 1) / busDataWidth + 1
    val valueBits = that.asBits
    var pos = if(isLittleWordEndianness) 0 else widthOf(that) - (if (widthOf(that) % busDataWidth  == 0)  busDataWidth else widthOf(that) % busDataWidth  )
    for (wordId <- 0 until wordCount) {
      // support unaligned access
      val mapping = SizeMapping(address + wordId * wordAddressInc, wordAddressInc)
      if (isLittleWordEndianness) {
        readPrimitive(valueBits(pos, Math.min(widthOf(that) - pos, busDataWidth) bits), mapping, 0, documentation)
        pos += busDataWidth
      } else {
        readPrimitive(valueBits(pos, Math.min(widthOf(that) - ((wordCount - 1) - wordId) * busDataWidth, busDataWidth) bits), mapping, 0, documentation)
        pos -= Math.min(pos, busDataWidth)
      }
    }
  }

  /**
    * Create the memory mapping to write that at address.
    * If `that` is bigger than one word it extends the register on following addresses.
    */
  def writeMultiWord(that: Data, address: BigInt, documentation: String = null): Unit = {
    // round up
    val wordCount = (widthOf(that) - 1) / busDataWidth + 1
    for (wordId <- 0 until wordCount) {
      // support unaligned access
      val mapping = SizeMapping(address + wordId * wordAddressInc, wordAddressInc)
      // split `that` into words
      writePrimitive(
        that = new DataWrapper {
          override def getBitsWidth: Int = if (isLittleWordEndianness) {
            Math.min(busDataWidth, widthOf(that) - wordId * busDataWidth)
          } else {
            Math.min(busDataWidth, widthOf(that) - ((wordCount - 1) - wordId) * busDataWidth)
          }

          override def assignFromBits(value: Bits): Unit = {
            assignFromBits(value, offset = 0, bitCount = getBitsWidth bits)
          }

          override def assignFromBits(value: Bits, offset: Int, bitCount: BitCount): Unit = {
            assert(bitCount.value <= getBitsWidth)
            that.assignFromBits(
              bits = value.resize(bitCount),
              offset = offset + (if (isLittleWordEndianness) wordId * busDataWidth else ((wordCount - 1) - wordId) * busDataWidth),
              bitCount = bitCount)
          }
        },
        mapping, 0, documentation)
    }
  }

  /**
    * Create the memory mapping to write/read that from address
    */
  def readAndWriteMultiWord(that: Data, address: BigInt, documentation: String = null): Unit = {
    writeMultiWord(that, address, documentation)
    readMultiWord(that, address, documentation)
  }


  /**
    * Create a write-only register of type dataType at address and placed at bitOffset in the word
    */
  def createWriteOnly[T <: Data](dataType      : T,
                                 address       : BigInt,
                                 bitOffset     : Int = 0,
                                 documentation : String = null): T = {
    val ret = Reg(dataType)
    write(ret, address, bitOffset, documentation)
    ret
  }


  /**
    * Create a read-only register of type dataType at address and placed at bitOffset in the word
    */
  def createReadOnly[T <: Data](dataType      : T,
                                address       : BigInt,
                                bitOffset     : Int = 0,
                                documentation : String = null): T = {
    val ret = Reg(dataType)
    read(ret, address, bitOffset)
    ret
  }

  /**
    * Create a read-write register of type dataType at address and placed at bitOffset in the word
    */
  def createReadAndWrite[T <: Data](dataType      : T,
                                    address       : BigInt,
                                    bitOffset     : Int = 0,
                                    documentation : String = null): T = {
    val reg = Reg(dataType)
    write(reg, address, bitOffset, documentation)
    read(reg, address, bitOffset, documentation)
    reg
  }



  def createReadAndClearOnSet[T <: Data](dataType  : T,
                                         address   : BigInt,
                                         bitOffset : Int = 0):T= {
    readAndClearOnSet(Reg(dataType), address, bitOffset)
  }

  def readAndClearOnSet[T <: Data](that      : T,
                                   address   : BigInt,
                                   bitOffset : Int = 0): T = {
    clearOnSet(that, address, bitOffset)
    read(that, address, bitOffset)
    that
  }


  def clearOnSet[T <: Data](that      : T,
                            address   : BigInt,
                            bitOffset : Int = 0): T = {
    val bitClears = nonStopWrite(Bits(widthOf(that) bits), bitOffset)
    when(isWriting(address)){
      if(that.isInstanceOf[SpinalEnumCraft[_]]){
        val w = widthOf(that)
        that.assignFromBits(that.asBits & ~bitClears)
      } else {
        for (i <- 0 until widthOf(that)) {
          when(bitClears(i)) {
            that.assignFromBits(B"0", i, 1 bits)
          }
        }
      }
    }
    that
  }


  def createReadAndSetOnSet[T <: Data](dataType  : T,
                                       address   : BigInt,
                                       bitOffset : Int = 0):T= {
    readAndSetOnSet(Reg(dataType), address, bitOffset)
  }


  def readAndSetOnSet[T <: Data](that      : T,
                                 address   : BigInt,
                                 bitOffset : Int = 0): T = {
    setOnSet(that, address, bitOffset)
    read(that, address, bitOffset)
    that
  }


  def setOnSet[T <: Data](that      : T,
                          address   : BigInt,
                          bitOffset : Int = 0): T = {
    val bitSets = nonStopWrite(Bits(widthOf(that) bits), bitOffset)
    when(isWriting(address)){
      for(i <- 0 until widthOf(that)){
        when(bitSets(i)){
          that.assignFromBits(B"1", i, 1 bits)
        }
      }
    }
    that
  }


  def clearOnClear[T <: Data](that      : T,
                          address   : BigInt,
                          bitOffset : Int = 0): T = {
    val bitSets = nonStopWrite(Bits(widthOf(that) bits), bitOffset)
    when(isWriting(address)){
      for(i <- 0 until widthOf(that)){
        when(!bitSets(i)){
          that.assignFromBits(B"0", i, 1 bits)
        }
      }
    }
    that
  }

  def setOnClear[T <: Data](that      : T,
                          address   : BigInt,
                          bitOffset : Int = 0): T = {
    val bitSets = nonStopWrite(Bits(widthOf(that) bits), bitOffset)
    when(isWriting(address)){
      for(i <- 0 until widthOf(that)){
        when(!bitSets(i)){
          that.assignFromBits(B"1", i, 1 bits)
        }
      }
    }
    that
  }


  @deprecated("Use createReadAndWrite instead", "???")
  def createReadWrite[T <: Data](dataType  : T,
                                 address   : BigInt,
                                 bitOffset : Int = 0): T = createReadAndWrite(dataType,address,bitOffset)

  /**
    * Create a writable Flow register of type dataType at address and placed at bitOffset in the word
    *
    * @param checkByteEnable do not trigger flow if byte enable is all zero.  See [[https://github.com/SpinalHDL/SpinalHDL/issues/1265]]
    *                        for the discussion about this behaviour.
    */
  def createAndDriveFlow[T <: Data](dataType       : T,
                                    address        : BigInt,
                                    bitOffset      : Int = 0,
                                    checkByteEnable: Boolean = false,
                                    documentation: String = null): Flow[T] = {
    val flow = Flow(dataType)
    driveFlow(flow, address, bitOffset, checkByteEnable, documentation)
    flow
  }

  /**
   * Emit on that a transaction when a write happen at address, by using data placed at bitOffset in the word.
   * Block the write transaction until the transaction succeeds (stream becomes ready).
   */
  def createAndDriveStream[T <: Data](dataType       : T,
                                      address        : BigInt,
                                      bitOffset      : Int = 0,
                                      documentation  : String = null): Stream[T] = {
    val stream = Stream(dataType)
    driveStream(stream, address, bitOffset, documentation)
    stream
  }

  /**
    * Create multi-words write register of type dataType
    */
  def createWriteMultiWord[T <: Data](that: T, address: BigInt, documentation: String = null): T = {
    val reg = Reg(that)
    writeMultiWord(reg, address, documentation)
    reg
  }

  /**
    * Create multi-words read register of type dataType
    */
  def createReadMultiWord[T <: Data](that: T, address: BigInt, documentation: String = null): T = {
    val reg = Reg(that)
    readMultiWord(reg, address, documentation)
    reg
  }

  /**
    * Create multi-words write and read register of type dataType
    */
  def createWriteAndReadMultiWord[T <: Data](that: T, address: BigInt, documentation: String = null): T = {
    val reg = Reg(that)
    writeMultiWord(reg, address, documentation)
    readMultiWord(reg, address, documentation)
    reg
  }



  /**
    * Drive that with a register writable at address placed at bitOffset in the word
    */
  def drive[T <: Data](that          : T,
                       address       : BigInt,
                       bitOffset     : Int = 0,
                       documentation : String = null): T = {
    val reg = Reg(that).setCompositeName(that, "driver", true)
    write(reg, address, bitOffset, documentation)
    that := reg
    reg
  }

  def drive[T <: Data](address    : BigInt,
                       bitMapping : (Int, Data)*): Unit = {
    bitMapping.foreach{ case (bitId, that) => drive(that, address, bitId) }
  }

  /**
    * Drive that with a register writable and readable at address placed at bitOffset in the word
    */
  def driveAndRead[T <: Data](that          : T,
                              address       : BigInt,
                              bitOffset     : Int = 0,
                              documentation : String = null): T = {
    val reg = Reg(that).setCompositeName(that, "driver", true)
    write(reg, address, bitOffset, documentation)
    read(reg, address, bitOffset,  documentation)
    that := reg
    reg
  }


  /**
    * Drive that on multi-words
    */
  def driveMultiWord[T <: Data](that: T, address: BigInt, documentation: String = null): T = {
    val reg = Reg(that)
    writeMultiWord(reg, address, documentation)
    that := reg
    reg
  }


  /**
    * Drive and read that on multi-word
    */
  def driveAndReadMultiWord[T <: Data](that: T, address: BigInt, documentation: String = null): T = {
    val reg = Reg(that)
    writeMultiWord(reg, address, documentation)
    readMultiWord(reg, address, documentation)
    that := reg
    reg
  }

  /**
    * Emit on that a transaction when a write happen at address by using data placed at bitOffset in the word
    *
    * @param checkByteEnable do not trigger flow if byte enable is all zero.  See [[https://github.com/SpinalHDL/SpinalHDL/issues/1265]]
   *                        for the discussion about this behaviour.
    */
  def driveFlow[T <: Data](that           : Flow[T],
                           address        : BigInt,
                           bitOffset      : Int = 0,
                           checkByteEnable: Boolean = false,
                           documentation: String = null): Unit = {

    val wordCount = (bitOffset + widthOf(that.payload) - 1 ) / busDataWidth + 1
    val byteEnable = writeByteEnable()
    def assignValidNext(valid: Bool): Unit = {
      if (byteEnable != null && checkByteEnable) {
        when(byteEnable =/= 0) { valid := True }
      } else {
        valid := True
      }
    }

    if (wordCount == 1){
      that.valid := False
      onWrite(address, documentation){ assignValidNext(that.valid) }
      nonStopWrite(that.payload, bitOffset, documentation)
    }else{

      assert(bitOffset == 0, "BusSlaveFactory ERROR [driveFlow] : BitOffset must be equal to 0 if the payload of the Flow is bigger than the data bus width")

      val regValid = RegNext(False) init(False)
      onWrite(address + ((wordCount - 1) * wordAddressInc), documentation){ assignValidNext(regValid) }
      driveMultiWord(that.payload, address, documentation)
      that.valid := regValid
    }
  }

  /**
   * Emit on that a transaction when a write happen at address, by using data placed at bitOffset in the word.
   * Block the write transaction until the transaction succeeds (stream becomes ready).
   */
  def driveStream[T <: Data](that: Stream[T], address: BigInt, bitOffset: Int = 0, documentation: String = null): Unit = {
    val wordCount = (bitOffset + widthOf(that.payload) - 1) / busDataWidth + 1
    onWritePrimitive(SizeMapping(address, wordCount * wordAddressInc), haltSensitive = false, null) {
      when(!that.ready) {
        writeHalt()
      }
    }
    val flow = Flow(that.payloadType())
    driveFlow(flow, address, bitOffset, checkByteEnable = true, documentation = documentation)
    that << flow.toStream
  }

  /**
    * Read that (that is bigger than the busWidth) and consume the transaction when a read happen at address.
    * @note in order to avoid to read wrong data read first the address which contains the
    *       valid signal.
    *       Little : payload - valid at address 0x00
    *       Big    : valid - payload at address 0x00
    *       Once the valid signal is true you can read all registers
    */
  def readStreamNonBlocking[T <: Data](that: Stream[T], address: BigInt): Unit = {

    val wordCount = (1 + widthOf(that.payload) - 1 ) / busDataWidth + 1
    val addressHigh = address + (wordCount - 1) * wordAddressInc

    if (wordCount == 1) {
      // we set that.ready irrespective of that.valid.
      that.ready := isReading(addressHigh)
    } else {
      // we set that.ready to the value of that.valid that it had when reading the base address
      val payloadIsValid = RegInit(False)
      onRead(address) { payloadIsValid := that.valid }

      that.ready := False
      onRead(addressHigh) {
        that.ready := payloadIsValid
        payloadIsValid := False
      }
    }

    if(isLittleWordEndianness){
      readMultiWord(that.payload ## that.valid, address)
    }else{
      readMultiWord(that.valid ## that.payload, address)
    }
  }

  /**
   * Same as {@link readStreamNonBlocking}, but block the bus for at most `blockCycles` before returning the NACK.
   * @param that data to read over bus
   * @param address address to map at
   * @param blockCycles cycles to block read transaction before returning NACK
   * @param timeout whether the read transaction timed out (returned NACK)
   * @tparam T type of stream payload
   */
  def readStreamBlockCycles[T <: Data](that: Stream[T], address: BigInt, blockCycles: UInt, timeout: Bool = null): Unit = new Composite(that, "readBlockCycles") {
    val counter = Counter(blockCycles.getWidth bits)
    val wordCount = (1 + that.payload.getBitsWidth - 1) / busDataWidth + 1
    val counterWillOverflow = counter === blockCycles
    val readIssued = False
    val respReady = RegNextWhen(True, readIssued && (counterWillOverflow || (!counterWillOverflow && that.valid))) init False
    val counterOverflowed = RegNextWhen(True, counterWillOverflow) init False
    // we only halt the first beat, since if we got a stream payload we got it all
    onReadPrimitive(SingleMapping(address), haltSensitive = false, null) {
      readIssued := True
      when(!respReady) {
        counter.increment()
        readHalt()
      } otherwise {
        counter.clear()
      }
    }

    // report timeout after transaction has completed (last beat)
    timeout != null generate { timeout := False }
    onReadPrimitive(SingleMapping(address + (wordCount - 1) * wordAddressInc), haltSensitive = true, null) {
      timeout != null generate {
        timeout := counterOverflowed
        counterOverflowed := False
      }
      respReady := False
    }

    readStreamNonBlocking(that, address)
  }

  /**
    * Read that and consume the transaction when a read happen at address.
    */
  def readStreamNonBlocking[T <: Data](that             : Stream[T],
                                       address          : BigInt,
                                       validBitOffset   : Int,
                                       payloadBitOffset : Int,
                                       validInverted    : Boolean = false): Unit = {

    assert(widthOf(that.payload) + 1 <= busDataWidth, "BusSlaveFactory ERROR [readStreamNonBlocking] : width of that parameter payload + valid signal is bigger than the data bus width. To solve it use readStreamNonBlocking(that: Stream, address: BigInt)")
    assert(payloadBitOffset + widthOf(that.payload) <= busDataWidth, "BusSlaveFactory ERROR [readStreamNonBlocking] : payloadBitOffset + width of that parameter payload is bigger than the data bus width" )
    assert(validBitOffset <= busDataWidth - 1, "BusSlaveFactory ERROR [readStreamNonBlocking] : validBitOffset is outside the data bus width")

    that.ready := False
    onRead(address){
      that.ready := True
    }
    read(that.valid ^ Bool(validInverted),   address, validBitOffset)
    read(that.payload, address, payloadBitOffset)
  }


  /**
    * Instantiate an internal register which at each cycle do : reg := reg | that
    * Then when a read occur, the register is cleared. This register is readable at address and placed at bitOffset in the word
    */
  def doBitsAccumulationAndClearOnRead(that      : Bits,
                                       address   : BigInt,
                                       bitOffset : Int = 0): Unit = {

    assert(bitOffset + that.getWidth <= busDataWidth, "BusSlaveFactory ERROR [doBitsAccumulationAndClearOnRead] : the width of the parameter that is bigger than the data bus width")

    val reg = Reg(that)
    reg := reg | that
    read(reg, address, bitOffset)
    onRead(address){ reg := that }
  }

  def multiCycleRead(address: AddressMapping, cycles: BigInt): Unit = {
    val counter = Counter(cycles)
    onReadPrimitive(
      address       = address,
      haltSensitive = false,
      documentation = null
    ){
      counter.increment()
      when(!counter.willOverflowIfInc){
        readHalt()
      }
    }
  }

  def readAddress(address: AddressMapping): UInt = address.removeOffset(readAddress())
  def writeAddress(address: AddressMapping): UInt = address.removeOffset(writeAddress())

  def readSyncMemWordAligned[T <: Data](mem           : Mem[T],
                                        addressOffset : BigInt,
                                        bitOffset     : Int = 0,
                                        memOffset     : UInt = U(0).resized) : Mem[T] = {
    val mapping = SizeMapping(addressOffset,mem.wordCount << log2Up(busDataWidth/8))
    val memAddress = (readAddress(mapping) >> log2Up(busDataWidth/8)) + memOffset
    val readData = mem.readSync(memAddress)
    multiCycleRead(mapping,2)
    readPrimitive(readData, mapping, bitOffset, null)
    mem
  }

  /**
    * Memory map a Mem to bus for reading. Elements can be larger than bus data width in bits.
    */
  def readSyncMemMultiWord[T <: Data](mem: Mem[T],
                                      addressOffset: BigInt,
                                      memOffset    : UInt = U(0).resized): Mem[T] = {
    val mapping = SizeMapping(addressOffset, mem.wordCount << log2Up(mem.width / 8))
    val memAddress = (readAddress(mapping) >> log2Up(mem.width / 8)) + memOffset
    val readData = mem.readSync(memAddress).asBits
    val offset = readAddress(mapping)(log2Up(mem.width / 8) - 1 downto log2Up(busDataWidth / 8))
    val partialRead = readData(offset << log2Up(busDataWidth), busDataWidth bits)
    multiCycleRead(mapping, 2)
    readPrimitive(partialRead, mapping, 0, null)
    mem
  }

  def writeMemWordAligned[T <: Data](mem           : Mem[T],
                                     addressOffset : BigInt,
                                     bitOffset     : Int = 0,
                                     memOffset     : UInt = U(0).resized) : Mem[T] = {
    val mapping    = SizeMapping(addressOffset,mem.wordCount << log2Up(busDataWidth / 8))
    val memAddress = (writeAddress(mapping) >> log2Up(busDataWidth / 8)) + memOffset

    // handle masking
    if (writeByteEnable != null) {
      val port = mem.writePortWithMask(widthOf(writeByteEnable()))
      port.address := memAddress
      port.valid := False
      onWritePrimitive(mapping,true, null){
        port.valid := True
      }
      nonStopWrite(port.data, bitOffset)

      port.mask := writeByteEnable
    } else {
      val port = mem.writePort
      port.address := memAddress
      port.valid := False
      onWritePrimitive(mapping,true, null){
        port.valid := True
      }
      nonStopWrite(port.data, bitOffset)
    }
    mem
  }

  /**
    * Memory map a Mem to bus for writing. Elements can be larger than bus data width in bits.
    */
  def writeMemMultiWord[T <: Data](mem: Mem[T],
                                   addressOffset: BigInt): Mem[T] = {
    // sanity check
    if (mem.width % busDataWidth != 0) {
      PendingError(s"Memory width ${mem.width} must be multiple of bus data width ${busDataWidth} \n${getScalaLocationLong}")
    }

    val maskWidth = mem.width / busDataWidth
    val mapping = SizeMapping(addressOffset, mem.wordCount << log2Up(mem.width / 8))
    val memAddress = writeAddress(mapping) >> log2Up(mem.width / 8)
    val port = mem.writePortWithMask(maskWidth)
    val data = Bits(busDataWidth bits)

    port.address := memAddress
    port.valid := False
    onWritePrimitive(mapping, true, null) {
      port.valid := True
    }
    // replicate data to `mem.width` bits
    port.data.assignFromBits(Cat(Seq.fill(mem.width / busDataWidth)(data)))

    // generate mask
    val mask = UInt(maskWidth bits)
    mask := 0
    mask.allowOverride
    mask(writeAddress(mapping)(log2Up(mem.width / 8) - 1 downto log2Up(busDataWidth / 8))) := True
    port.mask := mask.asBits

    nonStopWrite(data)
    mem
  }
}


/**
  * Base element
  */
trait BusSlaveFactoryElement{
  def mapping: AddressMapping
}


/**
  * Ask to make that readable when an access is done on address
  * bitOffset specify where that is placed on the answer
  */
case class BusSlaveFactoryRead(that          : Data,
                               address       : AddressMapping,
                               bitOffset     : Int,
                               documentation : String) extends BusSlaveFactoryElement{
  def mapping: AddressMapping = address
}


/**
  * Ask to make that writable when a access is done on address.
  * bitOffset specify where `that` get bits from the request
  */
case class BusSlaveFactoryWrite(that          : Data,
                                address       : AddressMapping,
                                bitOffset     : Int,
                                documentation : String) extends BusSlaveFactoryElement{
  def mapping: AddressMapping = address
}


/** Ask to execute doThat when a write access is done on address */
case class BusSlaveFactoryOnWriteAtAddress(address       : AddressMapping,
                                           haltSensitive : Boolean,
                                           documentation : String,
                                           doThat        : () => Unit) extends BusSlaveFactoryElement{
  def mapping: AddressMapping = address
}


/**  Ask to execute doThat when a read access is done on address */
case class BusSlaveFactoryOnReadAtAddress(address       : AddressMapping,
                                          haltSensitive : Boolean,
                                          documentation : String,
                                          doThat        : () => Unit) extends BusSlaveFactoryElement{
  def mapping: AddressMapping = address
}



/**
  * Ask to constantly drive that with the data bus
  * bitOffset specify where that get bits from the request
  */
case class BusSlaveFactoryNonStopWrite(that          : Data,
                                       bitOffset     : Int,
                                       documentation : String) extends BusSlaveFactoryElement{
  def mapping: AddressMapping = null
}



/**
  * BusSlaveFactoryDelayed captures each primitives (BusSlaveFactoryElement) into a data-model
  *
  * @example{{{
  *     class Apb3SlaveFactory(bus : Apb3) extends BusSlaveFactoryDelayed{
  *         override def build(): Unit = { ... }
  *         override def busDataWidth: Int = bus.config.dataWidth
  *      }
  * }}}
  */
trait BusSlaveFactoryDelayed extends BusSlaveFactory {

  /** Contains all elements created */
  val elements = ArrayBuffer[BusSlaveFactoryElement]()
  /** Contains all elements related to an address */
  val elementsPerAddress = mutable.LinkedHashMap[AddressMapping, ArrayBuffer[BusSlaveFactoryElement]]()
  val elementsOk = mutable.HashSet[BusSlaveFactoryElement]()


  component.addPrePopTask(() => {

    // prohibit reading two signals on the same address / bit
    for ((address, jobs) <- elementsPerAddress) {
      val occupied_range = new ListBuffer[Int]
      for (job <- jobs if job.isInstanceOf[BusSlaveFactoryRead]) {
        val read_job = job.asInstanceOf[BusSlaveFactoryRead]
        val current_bits = List.range(read_job.bitOffset, read_job.that.getBitsWidth + read_job.bitOffset, 1)
        for (bit <- current_bits) {
          assert(!occupied_range.contains(bit), s"BusSlaveFactory DOUBLE-READ-ERROR : bit $bit of bus address ${address.asInstanceOf[SingleMapping].address} should be written by ${read_job.that.getName()} but it is already occupied by another signal at the same address!")
          occupied_range.append(bit)
        }
      }
    }

    build()

    if (elementsOk.size != elements.size) {
      PendingError(s"$this isn't able generate the following requests :\n${(elements --= elementsOk).mkString("\n")} at \n${this.getScalaLocationLong}")
    }
  })

  private def addElement(e: BusSlaveFactoryElement) = {
    elements += e
    if (e.mapping != null)
      elementsPerAddress.getOrElseUpdate(e.mapping, ArrayBuffer[BusSlaveFactoryElement]()) += e
  }

  override def readPrimitive[T <: Data](that          : T,
                                        address       : AddressMapping,
                                        bitOffset     : Int,
                                        documentation : String): Unit = {

    assert(bitOffset + that.getBitsWidth <= busDataWidth, "BusSlaveFactory ERROR [readPrimitive] : bitOffset + width of the parameter that is bigger than the data bus width")

    addElement(BusSlaveFactoryRead(
      that          = that,
      address       = address,
      bitOffset     = bitOffset,
      documentation = documentation
    ))
  }

  override def writePrimitive[T <: Data](that          : T,
                                         address       : AddressMapping,
                                         bitOffset     : Int,
                                         documentation : String): Unit = {

    assert(bitOffset + that.getBitsWidth <= busDataWidth, "BusSlaveFactory ERROR [writePrimitive] : bitOffset + width of the parameter that is bigger than the data bus width")

    addElement(BusSlaveFactoryWrite(
      that          = that,
      address       = address,
      bitOffset     = bitOffset,
      documentation = documentation
    ))
  }

  override def onWritePrimitive(address       : AddressMapping,
                                haltSensitive : Boolean,
                                documentation : String)(doThat: => Unit): Unit = {
    addElement(BusSlaveFactoryOnWriteAtAddress(
      address       = address,
      haltSensitive = haltSensitive,
      documentation = documentation,
      doThat        = () => doThat
    ))
  }


  def onReadPrimitive(address       : AddressMapping,
                      haltSensitive : Boolean,
                      documentation : String)(doThat: => Unit): Unit = {
    addElement(BusSlaveFactoryOnReadAtAddress(
      address       = address,
      haltSensitive = haltSensitive,
      documentation = documentation,
      doThat        = () => doThat
    ))
  }


  /**
    * Permanently assign that by the bus write data from bitOffset
    */
  def nonStopWrite[T <: Data](that          : T,
                              bitOffset     : Int = 0,
                              documentation : String = null): T = {
    addElement(BusSlaveFactoryNonStopWrite(
      that          = that,
      bitOffset     = bitOffset,
      documentation = documentation
    ))
    that
  }


  /**
    * In this function you have to define the read/write logic thanks to element, elementsPerAddress and elementsPerRangeAddress
    * This is the only thing with def busDataWidth that should be implement by class that extends BusSlaveFactoryDelay
    */
  def build(): Unit


  def dataModelString(): String = {
    val builder = new StringBuilder()
    for ((address, tasks) <- elementsPerAddress.toList.sortBy(_._1.lowerBound)) {
      builder ++= s"$address :\n"
      for (task <- tasks) task match {
        case task: BusSlaveFactoryRead  => builder ++= s"  R[${task.bitOffset + widthOf(task.that) - 1}:${task.bitOffset}] ${task.that.getName()} ${if(task.documentation != null) s"- ${task.documentation}" else ""} \n"
        case task: BusSlaveFactoryWrite => builder ++= s"  W[${task.bitOffset + widthOf(task.that) - 1}:${task.bitOffset}] ${task.that.getName()} ${if(task.documentation != null) s"- ${task.documentation}" else ""} \n"
        case _ =>
      }
    }
    builder.toString
  }

  def printDataModel(): Unit = print(dataModelString())


  def doNonStopWrite(writeData: Bits): Unit = {
    for (element <- elements) element match {
      case element: BusSlaveFactoryNonStopWrite =>
        element.that.assignFromBits(writeData(element.bitOffset, element.that.getBitsWidth bits))
        elementsOk += element
      case _ =>
    }
  }

  def doMappedReadElements(jobs: Seq[BusSlaveFactoryElement], askRead: Bool, doRead: Bool, readData: Bits): Unit = {
    when(askRead) {
      for (element <- jobs) element match {
        case element: BusSlaveFactoryOnReadAtAddress if !element.haltSensitive =>
          element.doThat()
          elementsOk += element
        case _ =>
      }
    }

    when(doRead) {
      for (element <- jobs) element match {
        case element: BusSlaveFactoryOnReadAtAddress if element.haltSensitive =>
          element.doThat()
          elementsOk += element
        case _ =>
      }
    }

    for (element <- jobs) element match {
      case element: BusSlaveFactoryRead =>
        readData(element.bitOffset, element.that.getBitsWidth bits) := element.that.asBits
        elementsOk += element
      case _ =>
    }
  }

  def doMappedWriteElements(jobs: Seq[BusSlaveFactoryElement], askWrite: Bool, doWrite: Bool, writeData: Bits): Unit = {
    when(askWrite) {
      for (element <- jobs) element match {
        case element: BusSlaveFactoryOnWriteAtAddress if !element.haltSensitive =>
          element.doThat()
          elementsOk += element
        case _ =>
      }
    }

    val byteEnable = writeByteEnable()
    when(doWrite) {
      for (element <- jobs) element match {
        case element: BusSlaveFactoryWrite =>
          // check byte enable
          if(byteEnable != null) {
            for (i <- (0 until busDataWidth / 8)) {
              val from = element.bitOffset max i * 8
              val to = (element.bitOffset + element.that.getBitsWidth) min (i + 1) * 8
              if (from < to) {
                when(byteEnable(i)) {
                  element.that.assignFromBits(writeData(from until to), from - element.bitOffset, to - from bits)
                }
              }
            }
          } else {
            element.that.assignFromBits(writeData(element.bitOffset, element.that.getBitsWidth bits))
          }

          elementsOk += element
        case element: BusSlaveFactoryOnWriteAtAddress if element.haltSensitive =>
          element.doThat()
          elementsOk += element
        case _ =>
      }
    }
  }

  def doMappedElements(jobs: Seq[BusSlaveFactoryElement], askWrite: Bool, askRead: Bool, doWrite: Bool, doRead: Bool, writeData: Bits, readData: Bits): Unit = {
    doMappedWriteElements(jobs, askWrite, doWrite, writeData)
    doMappedReadElements(jobs, askRead, doRead, readData)
  }
}



class BusSlaveFactoryAddressWrapper(f: BusSlaveFactory, addressOffset: BigInt) extends BusSlaveFactory {
  override def busDataWidth: Int = f.busDataWidth
  override def nonStopWrite[T <: Data](that: T, bitOffset: Int, documentation: String): T = f.nonStopWrite(that, bitOffset, documentation)
  override def wordAddressInc: Int = f.wordAddressInc
  override def getConfig = f.getConfig
  override def setConfig(value : BusSlaveFactoryConfig): this.type = {
    f.setConfig(value)
    this
  }

  override def readPrimitive[T <: Data](that: T, address: AddressMapping, bitOffset: Int, documentation: String): Unit = f.readPrimitive(that, address.withOffset(addressOffset), bitOffset, documentation)
  override def writePrimitive[T <: Data](that: T, address: AddressMapping, bitOffset: Int, documentation: String): Unit = f.writePrimitive(that, address.withOffset(addressOffset), bitOffset, documentation)
  override def onWritePrimitive(address: AddressMapping, haltSensitive: Boolean, documentation: String)(doThat: => Unit): Unit = f.onWritePrimitive(address.withOffset(addressOffset), haltSensitive, documentation)(doThat)
  override def onReadPrimitive(address: AddressMapping, haltSensitive: Boolean, documentation: String)(doThat: => Unit): Unit = f.onReadPrimitive(address.withOffset(addressOffset), haltSensitive, documentation)(doThat)
  override def readHalt(): Unit = f.readHalt()
  override def writeHalt(): Unit = f.writeHalt()
  override def readAddress(): UInt = f.readAddress()  - addressOffset
  override def writeAddress(): UInt = f.writeAddress() - addressOffset
}
