// SPDX-License-Identifier: Apache-2.0

package firrtl.transforms

import firrtl.{CircuitState, DependencyAPIMigration, Namespace, PrimOps, Transform, Utils, WRef}
import firrtl.ir._
import firrtl.Mappers._
import firrtl.options.Dependency
import firrtl.PrimOps.{Add, AsSInt, Sub, Tail}
import firrtl.stage.Forms

import scala.collection.mutable

object FixAddingNegativeLiterals {

  /** Returns the maximum negative number represented by given width
    * @param width width of the negative number
    * @return maximum negative number
    */
  def minNegValue(width: BigInt): BigInt = -(BigInt(1) << (width.toInt - 1))

  /** Updates the type of the DoPrim from its arguments (e.g. if is UnknownType)
    * @param d input DoPrim
    * @return updated DoPrim with calculated type
    */
  def setType(d: DoPrim): DoPrim = {
    PrimOps.set_primop_type(d)
  }

  /** Returns a module with fixed additions of negative literals
    * @param m input module
    * @return updated module
    */
  def fixupModule(m: DefModule): DefModule = {
    val namespace = Namespace(m)
    m.map(fixupStatement(namespace))
  }

  /** Returns a statement with fixed additions of negative literals
    * @param namespace object to enabling creating unique names
    * @param s input statement
    * @return updated statement
    */
  def fixupStatement(namespace: Namespace)(s: Statement): Statement = {
    val stmtBuffer = mutable.ListBuffer[Statement]()
    val ret = s.map(fixupStatement(namespace)).map(fixupOnExpr(Utils.get_info(s), namespace, stmtBuffer))
    if (stmtBuffer.isEmpty) {
      ret
    } else {
      stmtBuffer += ret
      Block(stmtBuffer.toList)
    }
  }

  /** Returns a statement with fixed additions of negative literals
    * @param info Info of statement containing this expression
    * @param namespace object to enabling creating unique names
    * @param e expression to fixup
    * @return generated statements and the fixed expression
    */
  def fixupExpression(info: Info, namespace: Namespace)(e: Expression): (Seq[Statement], Expression) = {
    val stmtBuffer = mutable.ListBuffer[Statement]()
    val retExpr = fixupOnExpr(info, namespace, stmtBuffer)(e)
    (stmtBuffer.toList, retExpr)
  }

  /** Returns a statement with fixed additions of negative literals
    * @param info Info of statement containing this expression
    * @param namespace object to enabling creating unique names
    * @param stmtBuffer mutable buffer of statements - append to this for it to be inlined in the module
    * @param e expression to fixup
    * @return fixed expression
    */
  private def fixupOnExpr(
    info:       Info,
    namespace:  Namespace,
    stmtBuffer: mutable.ListBuffer[Statement]
  )(e:          Expression
  ): Expression = {

    // Helper function to create the subtraction expression
    def fixupAdd(expr: Expression, litValue: BigInt, litWidth: BigInt): DoPrim = {
      if (litValue == minNegValue(litWidth)) {
        val posLiteral = SIntLiteral(-litValue)
        assert(posLiteral.width.asInstanceOf[IntWidth].width - 1 == litWidth)
        val sub = DefNode(info, namespace.newTemp, setType(DoPrim(Sub, Seq(expr, posLiteral), Nil, UnknownType)))
        val tail = DefNode(info, namespace.newTemp, setType(DoPrim(Tail, Seq(WRef(sub)), Seq(1), UnknownType)))
        stmtBuffer += sub
        stmtBuffer += tail
        setType(DoPrim(AsSInt, Seq(WRef(tail)), Nil, UnknownType))
      } else {
        val posLiteral = SIntLiteral(-litValue)
        setType(DoPrim(Sub, Seq(expr, SIntLiteral(-litValue, IntWidth(litWidth))), Nil, UnknownType))
      }
    }

    e.map(fixupOnExpr(info, namespace, stmtBuffer)) match {
      case DoPrim(Add, Seq(arg, lit @ SIntLiteral(value, w @ IntWidth(width))), Nil, t: SIntType) if value < 0 =>
        fixupAdd(arg, value, width)
      case DoPrim(Add, Seq(lit @ SIntLiteral(value, w @ IntWidth(width)), arg), Nil, t: SIntType) if value < 0 =>
        fixupAdd(arg, value, width)
      case other => other
    }
  }
}

/** Replaces adding a negative literal with subtracting that literal
  *
  * Verilator has a lint warning if a literal is negated in an expression, because it adds a bit to
  * the literal and thus not all expressions in the add are the same. This is fixed here when we directly
  * subtract the literal instead.
  */
class FixAddingNegativeLiterals extends Transform with DependencyAPIMigration {

  override def prerequisites = Forms.LowFormMinimumOptimized :+ Dependency[BlackBoxSourceHelper]

  override def optionalPrerequisites = firrtl.stage.Forms.LowFormOptimized

  override def optionalPrerequisiteOf = Seq.empty

  override def invalidates(a: Transform) = false

  def execute(state: CircuitState): CircuitState = {
    val modulesx = state.circuit.modules.map(FixAddingNegativeLiterals.fixupModule)
    state.copy(circuit = state.circuit.copy(modules = modulesx))
  }
}
