// SPDX-License-Identifier: Apache-2.0

package firrtl.passes

import scala.annotation.tailrec
import firrtl._
import firrtl.ir._
import firrtl.Utils._
import firrtl.Mappers._
import firrtl.options.Dependency

import MemPortUtils.memType

/** Resolve name collisions that would occur in the old [[LowerTypes]] pass
  *
  *  @note Must be run after [[InferTypes]] because [[ir.DefNode]]s need type
  *  @example
  *  {{{
  *      wire a = { b, c }[2]
  *      wire a_0
  *  }}}
  *    This lowers to:
  *  {{{
  *      wire a__0_b
  *      wire a__0_c
  *      wire a__1_b
  *      wire a__1_c
  *      wire a_0
  *  }}}
  *    There wouldn't be a collision even if we didn't map a -> a_, but
  *      there WOULD be collisions in references a[0] and a_0 so we still have
  *      to rename a
  */
@deprecated("Uniquify is now part of LowerTypes", "FIRRTL 1.4.0")
object Uniquify extends Transform with DependencyAPIMigration {

  override def prerequisites =
    Seq(Dependency(ResolveKinds), Dependency(InferTypes)) ++ firrtl.stage.Forms.MinimalHighForm

  override def invalidates(a: Transform): Boolean = a match {
    case ResolveKinds | InferTypes => true
    case _                         => false
  }

  private case class UniquifyException(msg: String) extends FirrtlInternalException(msg)
  private def error(msg: String)(implicit sinfo: Info, mname: String) =
    throw new UniquifyException(s"$sinfo: [moduleOpt $mname] $msg")

  // For creation of rename map
  private case class NameMapNode(name: String, elts: Map[String, NameMapNode])

  /** Appends delim to prefix until no collisions of prefix + elts in names We don't add an _ in the collision check
    * because elts could be Seq("") In this case, we're just really checking if prefix itself collides
    */
  @deprecated("Use firrtl.Namespace.findValidPrefix", "FIRRTL 1.4.0")
  def findValidPrefix(
    prefix:    String,
    elts:      Seq[String],
    namespace: collection.mutable.HashSet[String]
  ): String = Namespace.findValidPrefix(prefix, elts, namespace)

  /** Creates a Bundle Type from a Stmt */
  @deprecated("Use firrtl.Utils.stmtToType", "FIRRTL 1.4.0")
  def stmtToType(s: Statement)(implicit sinfo: Info, mname: String): BundleType =
    Utils.stmtToType(s)

  // Accepts a Type and an initial namespace
  // Returns new Type with uniquified names
  private def uniquifyNames(
    t:         BundleType,
    namespace: collection.mutable.HashSet[String]
  )(
    implicit sinfo: Info,
    mname:          String
  ): BundleType = {
    def recUniquifyNames(t: Type, namespace: collection.mutable.HashSet[String]): (Type, Seq[String]) = t match {
      case tx: BundleType =>
        // First add everything
        val newFieldsAndElts = tx.fields.map { f =>
          val newName = Namespace.findValidPrefix(f.name, Seq(""), namespace)
          namespace += newName
          Field(newName, f.flip, f.tpe)
        }.map { f =>
          f.tpe match {
            case _: GroundType => (f, Seq[String](f.name))
            case _ =>
              val (tpe, eltsx) = recUniquifyNames(f.tpe, collection.mutable.HashSet())
              // Need leading _ for findValidPrefix, it doesn't add _ for checks
              val eltsNames: Seq[String] = eltsx.map(e => "_" + e)
              val prefix = Namespace.findValidPrefix(f.name, eltsNames, namespace)
              // We added f.name in previous map, delete if we change it
              if (prefix != f.name) {
                namespace -= f.name
                namespace += prefix
              }
              val newElts: Seq[String] = eltsx.map(e => LowerTypes.loweredName(prefix +: Seq(e)))
              namespace ++= newElts
              (Field(prefix, f.flip, tpe), prefix +: newElts)
          }
        }
        val (newFields, elts) = newFieldsAndElts.unzip
        (BundleType(newFields), elts.flatten)
      case tx: VectorType =>
        val (tpe, elts) = recUniquifyNames(tx.tpe, namespace)
        val newElts = ((0 until tx.size).map(i => i.toString)) ++
          ((0 until tx.size).flatMap { i =>
            elts.map(e => LowerTypes.loweredName(Seq(i.toString, e)))
          })
        (VectorType(tpe, tx.size), newElts)
      case tx => (tx, Nil)
    }
    val (tpe, _) = recUniquifyNames(t, namespace)
    tpe match {
      case tx: BundleType => tx
      case tx => throwInternalError(s"uniquifyNames: shouldn't be here - $tx")
    }
  }

  // Creates a mapping from flattened references to members of $from ->
  //   flattened references to members of $to
  private def createNameMapping(
    from: Type,
    to:   Type
  )(
    implicit sinfo: Info,
    mname:          String
  ): Map[String, NameMapNode] = {
    (from, to) match {
      case (fromx: BundleType, tox: BundleType) =>
        (fromx.fields
          .zip(tox.fields)
          .flatMap {
            case (f, t) =>
              val eltsMap = createNameMapping(f.tpe, t.tpe)
              if ((f.name != t.name) || eltsMap.nonEmpty) {
                Map(f.name -> NameMapNode(t.name, eltsMap))
              } else {
                Map[String, NameMapNode]()
              }
          })
          .toMap
      case (fromx: VectorType, tox: VectorType) =>
        createNameMapping(fromx.tpe, tox.tpe)
      case (fromx, tox) =>
        if (fromx.getClass == tox.getClass) Map()
        else error("Types to map between do not match!")
    }
  }

  // Maps names in expression to new uniquified names
  private def uniquifyNamesExp(
    exp: Expression,
    map: Map[String, NameMapNode]
  )(
    implicit sinfo: Info,
    mname:          String
  ): Expression = {
    // Recursive Helper
    def rec(exp: Expression, m: Map[String, NameMapNode]): (Expression, Map[String, NameMapNode]) = exp match {
      case e: WRef =>
        if (m.contains(e.name)) {
          val node = m(e.name)
          (WRef(node.name, e.tpe, e.kind, e.flow), node.elts)
        } else (e, Map())
      case e: WSubField =>
        val (subExp, subMap) = rec(e.expr, m)
        val (retName, retMap) =
          if (subMap.contains(e.name)) {
            val node = subMap(e.name)
            (node.name, node.elts)
          } else {
            (e.name, Map[String, NameMapNode]())
          }
        (WSubField(subExp, retName, e.tpe, e.flow), retMap)
      case e: WSubIndex =>
        val (subExp, subMap) = rec(e.expr, m)
        (WSubIndex(subExp, e.value, e.tpe, e.flow), subMap)
      case e: WSubAccess =>
        val (subExp, subMap) = rec(e.expr, m)
        val index = uniquifyNamesExp(e.index, map)
        (WSubAccess(subExp, index, e.tpe, e.flow), subMap)
      case (_: UIntLiteral | _: SIntLiteral) => (exp, m)
      case (_: Mux | _: ValidIf | _: DoPrim) =>
        (exp.map((e: Expression) => uniquifyNamesExp(e, map)), m)
    }
    rec(exp, map)._1
  }

  // Uses map to recursively rename fields of tpe
  private def uniquifyNamesType(
    tpe: Type,
    map: Map[String, NameMapNode]
  )(
    implicit sinfo: Info,
    mname:          String
  ): Type = tpe match {
    case t: BundleType =>
      val newFields = t.fields.map { f =>
        if (map.contains(f.name)) {
          val node = map(f.name)
          Field(node.name, f.flip, uniquifyNamesType(f.tpe, node.elts))
        } else {
          f
        }
      }
      BundleType(newFields)
    case t: VectorType =>
      VectorType(uniquifyNamesType(t.tpe, map), t.size)
    case t => t
  }

  // Everything wrapped in run so that it's thread safe
  @deprecated(
    "The functionality of Uniquify is now part of LowerTypes." +
      "Please file an issue with firrtl if you use Uniquify outside of the context of LowerTypes.",
    "Firrtl 1.4"
  )
  def execute(state: CircuitState): CircuitState = {
    val c = state.circuit
    val renames = RenameMap()
    renames.setCircuit(c.main)
    // Debug state
    implicit var mname: String = ""
    implicit var sinfo: Info = NoInfo
    // Global state
    val portNameMap = collection.mutable.HashMap[String, Map[String, NameMapNode]]()
    val portTypeMap = collection.mutable.HashMap[String, Type]()

    def uniquifyModule(renames: RenameMap)(m: DefModule): DefModule = {
      renames.setModule(m.name)
      val namespace = collection.mutable.HashSet[String]()
      val nameMap = collection.mutable.HashMap[String, NameMapNode]()

      def uniquifyExp(e: Expression): Expression = e match {
        case (_: WRef | _: WSubField | _: WSubIndex | _: WSubAccess) =>
          uniquifyNamesExp(e, nameMap.toMap)
        case e: Mux     => e.map(uniquifyExp)
        case e: ValidIf => e.map(uniquifyExp)
        case (_: UIntLiteral | _: SIntLiteral) => e
        case e: DoPrim => e.map(uniquifyExp)
      }

      def uniquifyStmt(s: Statement): Statement = {
        s.map(uniquifyStmt).map(uniquifyExp) match {
          case sx: DefWire =>
            sinfo = sx.info
            if (nameMap.contains(sx.name)) {
              val node = nameMap(sx.name)
              val newType = uniquifyNamesType(sx.tpe, node.elts)
              (Utils.create_exps(sx.name, sx.tpe).zip(Utils.create_exps(node.name, newType))).foreach {
                case (from, to) => renames.rename(from.serialize, to.serialize)
              }
              DefWire(sx.info, node.name, newType)
            } else {
              sx
            }
          case sx: DefRegister =>
            sinfo = sx.info
            if (nameMap.contains(sx.name)) {
              val node = nameMap(sx.name)
              val newType = uniquifyNamesType(sx.tpe, node.elts)
              (Utils.create_exps(sx.name, sx.tpe).zip(Utils.create_exps(node.name, newType))).foreach {
                case (from, to) => renames.rename(from.serialize, to.serialize)
              }
              DefRegister(sx.info, node.name, newType, sx.clock, sx.reset, sx.init)
            } else {
              sx
            }
          case sx: WDefInstance =>
            sinfo = sx.info
            if (nameMap.contains(sx.name)) {
              val node = nameMap(sx.name)
              val newType = portTypeMap(sx.module)
              (Utils.create_exps(sx.name, sx.tpe).zip(Utils.create_exps(node.name, newType))).foreach {
                case (from, to) => renames.rename(from.serialize, to.serialize)
              }
              WDefInstance(sx.info, node.name, sx.module, newType)
            } else {
              sx
            }
          case sx: DefMemory =>
            sinfo = sx.info
            if (nameMap.contains(sx.name)) {
              val node = nameMap(sx.name)
              val dataType = uniquifyNamesType(sx.dataType, node.elts)
              val mem = sx.copy(name = node.name, dataType = dataType)
              // Create new mapping to handle references to memory data fields
              val uniqueMemMap = createNameMapping(memType(sx), memType(mem))
              (Utils.create_exps(sx.name, memType(sx)).zip(Utils.create_exps(node.name, memType(mem)))).foreach {
                case (from, to) => renames.rename(from.serialize, to.serialize)
              }
              nameMap(sx.name) = NameMapNode(node.name, node.elts ++ uniqueMemMap)
              mem
            } else {
              sx
            }
          case sx: DefNode =>
            sinfo = sx.info
            if (nameMap.contains(sx.name)) {
              val node = nameMap(sx.name)
              (Utils
                .create_exps(sx.name, s.asInstanceOf[DefNode].value.tpe)
                .zip(Utils.create_exps(node.name, sx.value.tpe)))
                .foreach {
                  case (from, to) => renames.rename(from.serialize, to.serialize)
                }
              DefNode(sx.info, node.name, sx.value)
            } else {
              sx
            }
          case sx => sx
        }
      }

      def uniquifyBody(s: Statement): Statement = {
        val bodyType = stmtToType(s)
        val uniqueBodyType = uniquifyNames(bodyType, namespace)
        val localMap = createNameMapping(bodyType, uniqueBodyType)
        nameMap ++= localMap

        uniquifyStmt(s)
      }

      // uniquify ports and expand aggregate types
      sinfo = m.info
      mname = m.name
      m match {
        case m: ExtModule => m
        case m: Module    =>
          // Adds port names to namespace and namemap
          nameMap ++= portNameMap(m.name)
          namespace ++= create_exps("", portTypeMap(m.name)).map(LowerTypes.loweredName).map(_.tail)
          m.copy(body = uniquifyBody(m.body))
      }
    }

    def uniquifyPorts(renames: RenameMap)(m: DefModule): DefModule = {
      renames.setModule(m.name)
      def uniquifyPorts(ports: Seq[Port]): Seq[Port] = {
        val portsType = BundleType(ports.map {
          case Port(_, name, dir, tpe) => Field(name, to_flip(dir), tpe)
        })
        val uniquePortsType = uniquifyNames(portsType, collection.mutable.HashSet())
        val localMap = createNameMapping(portsType, uniquePortsType)
        portNameMap += (m.name -> localMap)
        portTypeMap += (m.name -> uniquePortsType)

        ports.zip(uniquePortsType.fields).map {
          case (p, f) =>
            (Utils.create_exps(p.name, p.tpe).zip(Utils.create_exps(f.name, f.tpe))).foreach {
              case (from, to) => renames.rename(from.serialize, to.serialize)
            }
            Port(p.info, f.name, p.direction, f.tpe)
        }
      }

      sinfo = m.info
      mname = m.name
      m match {
        case m: ExtModule => m.copy(ports = uniquifyPorts(m.ports))
        case m: Module    => m.copy(ports = uniquifyPorts(m.ports))
      }
    }

    sinfo = c.info
    val result = Circuit(c.info, c.modules.map(uniquifyPorts(renames)).map(uniquifyModule(renames)), c.main)
    state.copy(circuit = result, renames = Some(renames))
  }
}
