// SPDX-License-Identifier: Apache-2.0

package chisel3.naming

/** Experimental! Please don't use. */
trait HasCustomIdentifier {
  protected val customDefinitionIdentifierProposal: String
  final def customDefinitionIdentifier: String = IdentifierProposer.filterProposal(customDefinitionIdentifierProposal)
}

/** Experimental! Please don't use. */
object IdentifierProposer {

  // Any proposal needs valid characters
  // E.g. `chisel3.internal.Blah@123412` -> `chisel3_internal_Blah`
  // E.g. `(Foo)` -> `Foo`
  // E.g. `Foo(1)` -> `Foo_1`
  def filterProposal(s: String): String = {
    def legalStartOrEnd(c: Char) = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
    def legal(c:           Char) = legalStartOrEnd(c) || c == '_'
    def terminate(c:       Char) = c == '@'
    var firstOkChar: Int = -1
    var lastOkChar:  Int = 0
    var finalChar:   Int = -1
    for (i <- (0 until s.length)) {
      if (finalChar != -1 && finalChar < i) {} else {
        if (terminate(s(i))) finalChar = i
        else {
          if (legalStartOrEnd(s(i))) {
            lastOkChar = i
            if (firstOkChar == -1) firstOkChar = i
          }
        }
      }
    }
    if (s == "") s
    else
      s.substring(firstOkChar, if (finalChar == -1) lastOkChar + 1 else finalChar).map { x =>
        if (!legal(x)) '_' else x
      }
  }

  // Summons correct IdentifierProposer to generate a proposal
  def getProposal[T](obj: T): String = {
    obj match {
      case i: HasCustomIdentifier             => i.customDefinitionIdentifier // First give precedence to custom identifiers
      case b: chisel3.experimental.BaseModule => b.definitionIdentifier // Then to the one generated by the plugin
      case l: Iterable[_]                     => makeProposal(l.toList.map(getProposal): _*) // Handle iterables automatically
      case _ => filterProposal(obj.toString)
    }
  }

  // Algorithm to create an identifier proposal derived from a list of proposals
  def makeProposal(proposals: String*): String = proposals.filter(_ != "").mkString("_")
}
