// SPDX-License-Identifier: Apache-2.0

package firrtl
package annotations

import firrtl.ir.{Field => _, _}
import firrtl.Utils.{field_type, sub_type}
import AnnotationUtils.{toExp, validComponentName, validModuleName}
import TargetToken._

import scala.collection.mutable

/** Refers to something in a FIRRTL [[firrtl.ir.Circuit]]. Used for Annotation targets.
  *
  * Can be in various states of completion/resolved:
  *   - Legal: [[TargetToken]]'s in tokens are in an order that makes sense
  *   - Complete: circuitOpt and moduleOpt are non-empty, and all Instance(_) are followed by OfModule(_)
  *   - Local: tokens does not refer to things through an instance hierarchy (no Instance(_) or OfModule(_) tokens)
  */
sealed trait Target extends Named {

  /** @return Circuit name, if it exists */
  def circuitOpt: Option[String]

  /** @return Module name, if it exists */
  def moduleOpt: Option[String]

  /** @return [[Target]] tokens */
  def tokens: Seq[TargetToken]

  /** @return Returns a new [[GenericTarget]] with new values */
  def modify(
    circuitOpt: Option[String] = circuitOpt,
    moduleOpt:  Option[String] = moduleOpt,
    tokens:     Seq[TargetToken] = tokens
  ): GenericTarget = GenericTarget(circuitOpt, moduleOpt, tokens.toVector)

  /** @return Human-readable serialization */
  def serialize: String = {
    val circuitString = "~" + circuitOpt.getOrElse("???")
    val moduleString = "|" + moduleOpt.getOrElse("???")
    val tokensString = tokens.map {
      case Ref(r)               => s">$r"
      case Instance(i)          => s"/$i"
      case OfModule(o)          => s":$o"
      case TargetToken.Field(f) => s".$f"
      case Index(v)             => s"[$v]"
      case Clock                => s"@clock"
      case Reset                => s"@reset"
      case Init                 => s"@init"
    }.mkString("")
    if (moduleOpt.isEmpty && tokens.isEmpty) {
      circuitString
    } else if (tokens.isEmpty) {
      circuitString + moduleString
    } else {
      circuitString + moduleString + tokensString
    }
  }

  /** Pretty serialization, ideal for error messages. Cannot be deserialized.
    * @return Human-readable serialization
    */
  def prettyPrint(tab: String = ""): String = {
    val circuitString = s"""${tab}circuit ${circuitOpt.getOrElse("???")}:"""
    val moduleString = s"""\n$tab└── module ${moduleOpt.getOrElse("???")}:"""
    var depth = 4
    val tokenString = tokens.map {
      case Ref(r)      => val rx = s"""\n$tab${" " * depth}└── $r"""; depth += 4; rx
      case Instance(i) => val ix = s"""\n$tab${" " * depth}└── inst $i """; ix
      case OfModule(o) => val ox = s"of $o:"; depth += 4; ox
      case Field(f)    => s".$f"
      case Index(v)    => s"[$v]"
      case Clock       => s"@clock"
      case Reset       => s"@reset"
      case Init        => s"@init"
    }.mkString("")

    (moduleOpt.isEmpty, tokens.isEmpty) match {
      case (true, true) => circuitString
      case (_, true)    => circuitString + moduleString
      case (_, _)       => circuitString + moduleString + tokenString
    }
  }

  /** @return Converts this [[Target]] into a [[GenericTarget]] */
  def toGenericTarget: GenericTarget = GenericTarget(circuitOpt, moduleOpt, tokens.toVector)

  /** @return Converts this [[Target]] into either a [[CircuitName]], [[ModuleName]], or [[ComponentName]] */
  def toNamed: Named = toGenericTarget.toNamed

  /** @return If legal, convert this [[Target]] into a [[CompleteTarget]] */
  def getComplete: Option[CompleteTarget]

  /** @return Converts this [[Target]] into a [[CompleteTarget]] */
  def complete: CompleteTarget = getComplete.get

  /** @return Converts this [[Target]] into a [[CompleteTarget]], or if it can't, return original [[Target]] */
  def tryToComplete: Target = getComplete.getOrElse(this)

  /** Whether the target is directly instantiated in its root module */
  def isLocal: Boolean

  /** Share root module */
  def sharedRoot(other: Target): Boolean = this.moduleOpt == other.moduleOpt && other.moduleOpt.nonEmpty

  /** Checks whether this is inside of other */
  def encapsulatedBy(other: IsModule): Boolean = this.moduleOpt.contains(other.encapsulatingModule)

  /** @return Returns the instance hierarchy path, if one exists */
  def path: Seq[(Instance, OfModule)]
}

object Target {
  def asTarget(m: ModuleTarget)(e: Expression): ReferenceTarget = e match {
    case r: ir.Reference => m.ref(r.name)
    case s: ir.SubIndex  => asTarget(m)(s.expr).index(s.value)
    case s: ir.SubField  => asTarget(m)(s.expr).field(s.name)
    case s: ir.SubAccess => asTarget(m)(s.expr).field("@" + s.index.serialize)
    case d: DoPrim       => m.ref("@" + d.serialize)
    case d: Mux          => m.ref("@" + d.serialize)
    case d: ValidIf      => m.ref("@" + d.serialize)
    case d: Literal      => m.ref("@" + d.serialize)
    case other => sys.error(s"Unsupported: $other")
  }

  def apply(circuitOpt: Option[String], moduleOpt: Option[String], reference: Seq[TargetToken]): GenericTarget =
    GenericTarget(circuitOpt, moduleOpt, reference.toVector)

  def unapply(t: Target): Option[(Option[String], Option[String], Seq[TargetToken])] =
    Some((t.circuitOpt, t.moduleOpt, t.tokens))

  case class NamedException(message: String) extends Exception(message)

  implicit def convertCircuitTarget2CircuitName(c:     CircuitTarget): CircuitName = c.toNamed
  implicit def convertModuleTarget2ModuleName(c:       ModuleTarget):  ModuleName = c.toNamed
  implicit def convertIsComponent2ComponentName(c:     IsComponent):   ComponentName = c.toNamed
  implicit def convertTarget2Named(c:                  Target):        Named = c.toNamed
  implicit def convertCircuitName2CircuitTarget(c:     CircuitName):   CircuitTarget = c.toTarget
  implicit def convertModuleName2ModuleTarget(c:       ModuleName):    ModuleTarget = c.toTarget
  implicit def convertComponentName2ReferenceTarget(c: ComponentName): ReferenceTarget = c.toTarget
  implicit def convertNamed2Target(n:                  Named):         CompleteTarget = n.toTarget

  /** Converts [[ComponentName]]'s name into TargetTokens
    * @param name
    * @return
    */
  def toTargetTokens(name: String): Seq[TargetToken] = {
    val tokens = AnnotationUtils.tokenize(name)
    val subComps = mutable.ArrayBuffer[TargetToken]()
    subComps += Ref(tokens.head)
    if (tokens.tail.nonEmpty) {
      tokens.tail.zip(tokens.tail.tail).foreach {
        case (".", value: String) => subComps += Field(value)
        case ("[", value: String) => subComps += Index(value.toInt)
        case other =>
      }
    }
    subComps.toSeq
  }

  /** Checks if seq only contains [[TargetToken]]'s with select keywords
    * @param seq
    * @param keywords
    * @return
    */
  def isOnly(seq: Seq[TargetToken], keywords: String*): Boolean = {
    seq.map(_.is(keywords: _*)).foldLeft(false)(_ || _) && keywords.nonEmpty
  }

  /** @return [[Target]] from human-readable serialization */
  def deserialize(s: String): Target = {
    val regex = """(?=[~|>/:.\[@])"""
    s.split(regex)
      .foldLeft(GenericTarget(None, None, Vector.empty)) { (t, tokenString) =>
        val value = tokenString.tail
        tokenString(0) match {
          case '~' if t.circuitOpt.isEmpty && t.moduleOpt.isEmpty && t.tokens.isEmpty =>
            if (value == "???") t else t.copy(circuitOpt = Some(value))
          case '|' if t.moduleOpt.isEmpty && t.tokens.isEmpty =>
            if (value == "???") t else t.copy(moduleOpt = Some(value))
          case '/'                                  => t.add(Instance(value))
          case ':'                                  => t.add(OfModule(value))
          case '>'                                  => t.add(Ref(value))
          case '.'                                  => t.add(Field(value))
          case '[' if value.dropRight(1).toInt >= 0 => t.add(Index(value.dropRight(1).toInt))
          case '@' if value == "clock"              => t.add(Clock)
          case '@' if value == "init"               => t.add(Init)
          case '@' if value == "reset"              => t.add(Reset)
          case other                                => throw NamedException(s"Cannot deserialize Target: $s")
        }
      }
      .tryToComplete
  }

  /** Returns the module that a [[Target]] "refers" to.
    *
    * For a [[ModuleTarget]] or a [[ReferenceTarget]], this is simply the deepest module. For an [[InstanceTarget]] this
    * is *the module of the instance*.
    *
    * @note This differs from [[InstanceTarget.pathlessTarget]] which refers to the module instantiating the instance.
    */
  def referringModule(a: IsMember): ModuleTarget = a match {
    case b: ModuleTarget    => b
    case b: InstanceTarget  => b.ofModuleTarget
    case b: ReferenceTarget => b.pathlessTarget.moduleTarget
  }

  def getPathlessTarget(t: Target): Target = {
    t.tryToComplete match {
      case c: CircuitTarget => c
      case m: IsMember => m.pathlessTarget
      case t: GenericTarget if t.isLegal =>
        val newTokens = t.tokens.dropWhile(x => x.isInstanceOf[Instance] || x.isInstanceOf[OfModule])
        GenericTarget(t.circuitOpt, t.moduleOpt, newTokens)
      case other => sys.error(s"Can't make $other pathless!")
    }
  }

  def getReferenceTarget(t: Target): Target = {
    (t.toGenericTarget match {
      case t: GenericTarget if t.isLegal =>
        val newTokens = t.tokens.reverse
          .dropWhile({
            case x: Field => true
            case x: Index => true
            case Clock => true
            case Init  => true
            case Reset => true
            case other => false
          })
          .reverse
        GenericTarget(t.circuitOpt, t.moduleOpt, newTokens)
      case other => sys.error(s"Can't make $other pathless!")
    }).tryToComplete
  }
}

/** Represents incomplete or non-standard [[Target]]s
  * @param circuitOpt Optional circuit name
  * @param moduleOpt Optional module name
  * @param tokens [[TargetToken]]s to represent the target in a circuit and module
  */
case class GenericTarget(circuitOpt: Option[String], moduleOpt: Option[String], tokens: Vector[TargetToken])
    extends Target {

  override def toGenericTarget: GenericTarget = this

  override def toNamed: Named = getComplete match {
    case Some(c: IsComponent) if c.isLocal => c.toNamed
    case Some(c: ModuleTarget) => c.toNamed
    case Some(c: CircuitTarget) => c.toNamed
    case other => throw Target.NamedException(s"Cannot convert $this to [[Named]]")
  }

  override def toTarget: CompleteTarget = getComplete.get

  override def getComplete: Option[CompleteTarget] = {
    if (!isComplete) None
    else {
      val target = this match {
        case GenericTarget(Some(c), None, Vector())                                  => CircuitTarget(c)
        case GenericTarget(Some(c), Some(m), Vector())                               => ModuleTarget(c, m)
        case GenericTarget(Some(c), Some(m), Ref(r) +: component)                    => ReferenceTarget(c, m, Nil, r, component)
        case GenericTarget(Some(c), Some(m), Instance(i) +: OfModule(o) +: Vector()) => InstanceTarget(c, m, Nil, i, o)
        case GenericTarget(Some(c), Some(m), component) =>
          val path = getPath.getOrElse(Nil)
          (getRef, getInstanceOf) match {
            case (Some((r, comps)), _) => ReferenceTarget(c, m, path, r, comps)
            case (None, Some((i, o)))  => InstanceTarget(c, m, path, i, o)
          }
      }
      Some(target)
    }
  }

  override def isLocal: Boolean = !(getPath.nonEmpty && getPath.get.nonEmpty)

  def path: Vector[(Instance, OfModule)] = if (isComplete) {
    tokens.zip(tokens.tail).collect {
      case (i: Instance, o: OfModule) => (i, o)
    }
  } else Vector.empty[(Instance, OfModule)]

  /** If complete, return this [[GenericTarget]]'s path
    * @return
    */
  def getPath: Option[Seq[(Instance, OfModule)]] = if (isComplete) {
    val allInstOfs = tokens.grouped(2).collect { case Seq(i: Instance, o: OfModule) => (i, o) }.toSeq
    if (tokens.nonEmpty && tokens.last.isInstanceOf[OfModule]) Some(allInstOfs.dropRight(1)) else Some(allInstOfs)
  } else {
    None
  }

  /** If complete and a reference, return the reference and subcomponents
    * @return
    */
  def getRef: Option[(String, Seq[TargetToken])] = if (isComplete) {
    val (optRef, comps) = tokens.foldLeft((None: Option[String], Vector.empty[TargetToken])) {
      case ((None, v), Ref(r)) => (Some(r), v)
      case ((r: Some[String], comps), c) => (r, comps :+ c)
      case ((r, v), other) => (None, v)
    }
    optRef.map(x => (x, comps))
  } else {
    None
  }

  /** If complete and an instance target, return the instance and ofmodule
    * @return
    */
  def getInstanceOf: Option[(String, String)] = if (isComplete) {
    tokens.grouped(2).foldLeft(None: Option[(String, String)]) {
      case (instOf, Seq(i: Instance, o: OfModule)) => Some((i.value, o.value))
      case (instOf, _) => None
    }
  } else {
    None
  }

  /** Requires the last [[TargetToken]] in tokens to be one of the [[TargetToken]] keywords
    * @param default Return value if tokens is empty
    * @param keywords
    */
  private def requireLast(default: Boolean, keywords: String*): Unit = {
    val isOne = if (tokens.isEmpty) default else tokens.last.is(keywords: _*)
    require(isOne, s"${tokens.last} is not one of $keywords")
  }

  /** Appends a target token to tokens, asserts legality
    * @param token
    * @return
    */
  def add(token: TargetToken): GenericTarget = {
    token match {
      case _: Instance => requireLast(true, "inst", "of")
      case _: OfModule => requireLast(false, "inst")
      case _: Ref      => requireLast(true, "inst", "of")
      case _: Field    => requireLast(true, "ref", "[]", ".", "init", "clock", "reset")
      case _: Index    => requireLast(true, "ref", "[]", ".", "init", "clock", "reset")
      case Init  => requireLast(true, "ref", "[]", ".", "init", "clock", "reset")
      case Clock => requireLast(true, "ref", "[]", ".", "init", "clock", "reset")
      case Reset => requireLast(true, "ref", "[]", ".", "init", "clock", "reset")
    }
    this.copy(tokens = tokens :+ token)
  }

  /** Removes n number of target tokens from the right side of [[tokens]] */
  def remove(n: Int): GenericTarget = this.copy(tokens = tokens.dropRight(n))

  /** Optionally tries to append token to tokens, fails return is not a legal Target */
  def optAdd(token: TargetToken): Option[Target] = {
    try {
      Some(add(token))
    } catch {
      case _: IllegalArgumentException => None
    }
  }

  /** Checks whether the component is legal (incomplete is ok)
    * @return
    */
  def isLegal: Boolean = {
    try {
      var comp: GenericTarget = this.copy(tokens = Vector.empty)
      for (token <- tokens) {
        comp = comp.add(token)
      }
      true
    } catch {
      case _: IllegalArgumentException => false
    }
  }

  /** Checks whether the component is legal and complete, meaning the circuitOpt and moduleOpt are nonEmpty and
    * all Instance(_) are followed by OfModule(_)
    * @return
    */
  def isComplete: Boolean = {
    isLegal && (isCircuitTarget || isModuleTarget || (isComponentTarget && tokens.tails.forall {
      case Instance(_) +: OfModule(_) +: tail => true
      case Instance(_) +: x +: tail           => false
      case x +: OfModule(_) +: tail           => false
      case _                                  => true
    }))
  }

  def isCircuitTarget:   Boolean = circuitOpt.nonEmpty && moduleOpt.isEmpty && tokens.isEmpty
  def isModuleTarget:    Boolean = circuitOpt.nonEmpty && moduleOpt.nonEmpty && tokens.isEmpty
  def isComponentTarget: Boolean = circuitOpt.nonEmpty && moduleOpt.nonEmpty && tokens.nonEmpty

  lazy val (parentModule: Option[String], astModule: Option[String]) = path match {
    case Seq()                 => (None, moduleOpt)
    case Seq((i, OfModule(o))) => (moduleOpt, Some(o))
    case seq if seq.size > 1 =>
      val reversed = seq.reverse
      (Some(reversed(1)._2.value), Some(reversed(0)._2.value))
  }
}

/** Concretely points to a FIRRTL target, no generic selectors
  * IsLegal
  */
trait CompleteTarget extends Target {

  /** @return The circuit of this target */
  def circuit: String

  /** @return The [[CircuitTarget]] of this target's circuit */
  def circuitTarget: CircuitTarget = CircuitTarget(circuitOpt.get)

  def getComplete: Option[CompleteTarget] = Some(this)

  /** Adds another level of instance hierarchy
    * Example: Given root=A and instance=b, transforms (Top, B)/c:C -> (Top, A)/b:B/c:C
    * @param root
    * @param instance
    * @return
    */
  def addHierarchy(root: String, instance: String): IsComponent

  override def toTarget: CompleteTarget = this

  // Very useful for debugging, I (@azidar) think this is reasonable
  override def toString: String = serialize
}

/** A member of a FIRRTL Circuit (e.g. cannot point to a CircuitTarget)
  * Concrete Subclasses are: [[ModuleTarget]], [[InstanceTarget]], and [[ReferenceTarget]]
  */
trait IsMember extends CompleteTarget {

  /** @return Root module, e.g. top-level module of this target */
  def module: String

  /** @return Returns the instance hierarchy path, if one exists */
  def path: Seq[(Instance, OfModule)]

  /** @return Creates a path, assuming all Instance and OfModules in this [[IsMember]] is used as a path */
  def asPath: Seq[(Instance, OfModule)]

  /** @return Tokens of just this member's path */
  def justPath: Seq[TargetToken]

  /** @return Local tokens of what this member points (not a path) */
  def notPath: Seq[TargetToken]

  /** @return Same target without a path */
  def pathlessTarget: IsMember

  /** @return Member's path target */
  def pathTarget: CompleteTarget

  /** @return Member's top-level module target */
  def moduleTarget: ModuleTarget = ModuleTarget(circuitOpt.get, moduleOpt.get)

  /** @return Member's parent target */
  def targetParent: CompleteTarget

  /** @return List of local Instance Targets refering to each instance/ofModule in this member's path */
  def pathAsTargets: Seq[InstanceTarget] = {
    path
      .foldLeft((module, Vector.empty[InstanceTarget])) {
        case ((m, vec), (Instance(i), OfModule(o))) =>
          (o, vec :+ InstanceTarget(circuit, m, Nil, i, o))
      }
      ._2
  }

  /** Resets this target to have a new path
    * @param newPath
    * @return
    */
  def setPathTarget(newPath: IsModule): CompleteTarget

  /** @return The [[ModuleTarget]] of the module that directly contains this component */
  def encapsulatingModule: String = if (path.isEmpty) module else path.last._2.value

  def encapsulatingModuleTarget: ModuleTarget = ModuleTarget(circuit, encapsulatingModule)

  def leafModule: String
}

/** References a module-like target (e.g. a [[ModuleTarget]] or an [[InstanceTarget]])
  */
trait IsModule extends IsMember {

  /** @return Creates a new Target, appending a ref */
  def ref(value: String): ReferenceTarget

  /** @return Creates a new Target, appending an instance and ofmodule */
  def instOf(instance: String, of: String): InstanceTarget

  def addHierarchy(root: String, inst: String): InstanceTarget
}

/** A component of a FIRRTL Module (e.g. cannot point to a CircuitTarget or ModuleTarget)
  */
trait IsComponent extends IsMember {

  /** Removes n levels of instance hierarchy
    *
    * Example: n=1, transforms (Top, A)/b:B/c:C -> (Top, B)/c:C
    * @param n
    * @return
    */
  def stripHierarchy(n: Int): IsMember

  override def toNamed: ComponentName = {
    if (isLocal) {
      val mn = ModuleName(module, CircuitName(circuit))
      Seq(tokens: _*) match {
        case Seq(Ref(name)) => ComponentName(name, mn)
        case Ref(_) :: tail if Target.isOnly(tail, ".", "[]") =>
          val name = tokens.foldLeft("") {
            case ("", Ref(name))        => name
            case (string, Field(value)) => s"$string.$value"
            case (string, Index(value)) => s"$string[$value]"
          }
          ComponentName(name, mn)
        case Seq(Instance(name), OfModule(o)) => ComponentName(name, mn)
      }
    } else {
      throw new Exception(s"Cannot convert $this to [[ComponentName]]")
    }
  }

  override def justPath: Seq[TargetToken] = path.foldLeft(Vector.empty[TargetToken]) {
    case (vec, (i, o)) => vec ++ Seq(i, o)
  }

  override def pathTarget: IsModule = {
    if (path.isEmpty) moduleTarget
    else {
      val (i, o) = path.last
      InstanceTarget(circuit, module, path.dropRight(1), i.value, o.value)
    }
  }

  override def tokens = justPath ++ notPath

  override def isLocal = path.isEmpty
}

/** Target pointing to a FIRRTL [[firrtl.ir.Circuit]]
  * @param circuit Name of a FIRRTL circuit
  */
case class CircuitTarget(circuit: String) extends CompleteTarget {

  /** Creates a [[ModuleTarget]] of provided name and this circuit
    * @param m
    * @return
    */
  def module(m: String): ModuleTarget = ModuleTarget(circuit, m)

  override def circuitOpt: Option[String] = Some(circuit)

  override def moduleOpt: Option[String] = None

  override def tokens = Nil

  override def isLocal = true

  override def addHierarchy(root: String, instance: String): ReferenceTarget =
    ReferenceTarget(circuit, root, Nil, instance, Nil)

  override def path = Seq()

  override def toNamed: CircuitName = CircuitName(circuit)
}

/** Target pointing to a FIRRTL [[firrtl.ir.DefModule]]
  * @param circuit Circuit containing the module
  * @param module Name of the module
  */
case class ModuleTarget(circuit: String, module: String) extends IsModule {

  override def circuitOpt: Option[String] = Some(circuit)

  override def moduleOpt: Option[String] = Some(module)

  override def tokens: Seq[TargetToken] = Nil

  override def targetParent: CircuitTarget = CircuitTarget(circuit)

  override def addHierarchy(root: String, instance: String): InstanceTarget =
    InstanceTarget(circuit, root, Nil, instance, module)

  override def ref(value: String): ReferenceTarget = ReferenceTarget(circuit, module, Nil, value, Nil)

  override def instOf(instance: String, of: String): InstanceTarget = InstanceTarget(circuit, module, Nil, instance, of)

  override def asPath = Nil

  override def path: Seq[(Instance, OfModule)] = Nil

  override def justPath: Seq[TargetToken] = Nil

  override def notPath: Seq[TargetToken] = Nil

  override def pathlessTarget: ModuleTarget = this

  override def pathTarget: ModuleTarget = this

  override def isLocal = true

  override def setPathTarget(newPath: IsModule): IsModule = newPath

  override def toNamed: ModuleName = ModuleName(module, CircuitName(circuit))

  override def leafModule: String = module
}

/** Target pointing to a declared named component in a [[firrtl.ir.DefModule]]
  * This includes: [[firrtl.ir.Port]], [[firrtl.ir.DefWire]], [[firrtl.ir.DefRegister]], [[firrtl.ir.DefInstance]],
  *   [[firrtl.ir.DefMemory]], [[firrtl.ir.DefNode]]
  * @param circuit Name of the encapsulating circuit
  * @param module Name of the root module of this reference
  * @param path Path through instance/ofModules
  * @param ref Name of component
  * @param component Subcomponent of this reference, e.g. field or index
  */
case class ReferenceTarget(
  circuit:           String,
  module:            String,
  override val path: Seq[(Instance, OfModule)],
  ref:               String,
  component:         Seq[TargetToken])
    extends IsComponent {

  /** @param value Index value of this target
    * @return A new [[ReferenceTarget]] to the specified index of this [[ReferenceTarget]]
    */
  def index(value: Int): ReferenceTarget = ReferenceTarget(circuit, module, path, ref, component :+ Index(value))

  /** @param value Field name of this target
    * @return A new [[ReferenceTarget]] to the specified field of this [[ReferenceTarget]]
    */
  def field(value: String): ReferenceTarget = ReferenceTarget(circuit, module, path, ref, component :+ Field(value))

  /** @return The initialization value of this reference, must be to a [[firrtl.ir.DefRegister]] */
  def init: ReferenceTarget = ReferenceTarget(circuit, module, path, ref, component :+ Init)

  /** @return The reset signal of this reference, must be to a [[firrtl.ir.DefRegister]] */
  def reset: ReferenceTarget = ReferenceTarget(circuit, module, path, ref, component :+ Reset)

  /** @return The clock signal of this reference, must be to a [[firrtl.ir.DefRegister]] */
  def clock: ReferenceTarget = ReferenceTarget(circuit, module, path, ref, component :+ Clock)

  /** @param the type of this target's ref
    * @return the type of the subcomponent specified by this target's component
    */
  def componentType(baseType: Type): Type = componentType(baseType, tokens)

  private def componentType(baseType: Type, tokens: Seq[TargetToken]): Type = {
    if (tokens.isEmpty) {
      baseType
    } else {
      val headType = tokens.head match {
        case Index(idx)   => sub_type(baseType)
        case Field(field) => field_type(baseType, field)
        case _: Ref => baseType
      }
      componentType(headType, tokens.tail)
    }
  }

  override def circuitOpt: Option[String] = Some(circuit)

  override def moduleOpt: Option[String] = Some(module)

  override def targetParent: CompleteTarget = component match {
    case Nil =>
      if (path.isEmpty) moduleTarget
      else {
        val (i, o) = path.last
        InstanceTarget(circuit, module, path.dropRight(1), i.value, o.value)
      }
    case other => ReferenceTarget(circuit, module, path, ref, component.dropRight(1))
  }

  override def notPath: Seq[TargetToken] = Ref(ref) +: component

  override def addHierarchy(root: String, instance: String): ReferenceTarget =
    ReferenceTarget(circuit, root, (Instance(instance), OfModule(module)) +: path, ref, component)

  override def stripHierarchy(n: Int): ReferenceTarget = {
    require(path.size >= n, s"Cannot strip $n levels of hierarchy from $this")
    if (n == 0) this
    else {
      val newModule = path(n - 1)._2.value
      ReferenceTarget(circuit, newModule, path.drop(n), ref, component)
    }
  }

  override def pathlessTarget: ReferenceTarget = ReferenceTarget(circuit, encapsulatingModule, Nil, ref, component)

  override def setPathTarget(newPath: IsModule): ReferenceTarget =
    ReferenceTarget(newPath.circuit, newPath.module, newPath.asPath, ref, component)

  override def asPath: Seq[(Instance, OfModule)] = path

  def isClock: Boolean = tokens.last == Clock

  def isInit: Boolean = tokens.last == Init

  def isReset: Boolean = tokens.last == Reset

  def noComponents: ReferenceTarget = this.copy(component = Nil)

  def leafSubTargets(tpe: firrtl.ir.Type): Seq[ReferenceTarget] = tpe match {
    case _: firrtl.ir.GroundType => Vector(this)
    case firrtl.ir.VectorType(t, size) => (0 until size).flatMap { i => index(i).leafSubTargets(t) }
    case firrtl.ir.BundleType(fields)  => fields.flatMap { f => field(f.name).leafSubTargets(f.tpe) }
    case other                         => sys.error(s"Error! Unexpected type $other")
  }

  def allSubTargets(tpe: firrtl.ir.Type): Seq[ReferenceTarget] = tpe match {
    case _: firrtl.ir.GroundType => Vector(this)
    case firrtl.ir.VectorType(t, size) => this +: (0 until size).flatMap { i => index(i).allSubTargets(t) }
    case firrtl.ir.BundleType(fields)  => this +: fields.flatMap { f => field(f.name).allSubTargets(f.tpe) }
    case other                         => sys.error(s"Error! Unexpected type $other")
  }

  override def leafModule: String = encapsulatingModule
}

/** Points to an instance declaration of a module (termed an ofModule)
  * @param circuit Encapsulating circuit
  * @param module Root module (e.g. the base module of this target)
  * @param path Path through instance/ofModules
  * @param instance Name of the instance
  * @param ofModule Name of the instance's module
  */
case class InstanceTarget(
  circuit:           String,
  module:            String,
  override val path: Seq[(Instance, OfModule)],
  instance:          String,
  ofModule:          String)
    extends IsModule
    with IsComponent {

  /** @return a [[ReferenceTarget]] referring to this declaration of this instance */
  def asReference: ReferenceTarget = ReferenceTarget(circuit, module, path, instance, Nil)

  /** @return a [[ModuleTarget]] referring to declaration of this ofModule */
  def ofModuleTarget: ModuleTarget = ModuleTarget(circuit, ofModule)

  /** @return a [[ReferenceTarget]] referring to given reference within this instance */
  def addReference(rt: ReferenceTarget): ReferenceTarget = {
    require(rt.module == ofModule)
    ReferenceTarget(circuit, module, asPath, rt.ref, rt.component)
  }

  override def circuitOpt: Option[String] = Some(circuit)

  override def moduleOpt: Option[String] = Some(module)

  override def targetParent: IsModule = {
    if (isLocal) ModuleTarget(circuit, module)
    else {
      val (newInstance, newOfModule) = path.last
      InstanceTarget(circuit, module, path.dropRight(1), newInstance.value, newOfModule.value)
    }
  }

  override def addHierarchy(root: String, inst: String): InstanceTarget =
    InstanceTarget(circuit, root, (Instance(inst), OfModule(module)) +: path, instance, ofModule)

  override def ref(value: String): ReferenceTarget = ReferenceTarget(circuit, module, asPath, value, Nil)

  override def instOf(inst: String, of: String): InstanceTarget = InstanceTarget(circuit, module, asPath, inst, of)

  override def stripHierarchy(n: Int): IsModule = {
    require(path.size + 1 >= n, s"Cannot strip $n levels of hierarchy from $this")
    if (n == 0) this
    else {
      if (path.size < n) {
        ModuleTarget(circuit, ofModule)
      } else {
        val newModule = path(n - 1)._2.value
        InstanceTarget(circuit, newModule, path.drop(n), instance, ofModule)
      }
    }
  }

  override def asPath: Seq[(Instance, OfModule)] = path :+ ((Instance(instance), OfModule(ofModule)))

  override def pathlessTarget: InstanceTarget = InstanceTarget(circuit, encapsulatingModule, Nil, instance, ofModule)

  override def notPath = Seq(Instance(instance), OfModule(ofModule))

  override def setPathTarget(newPath: IsModule): InstanceTarget =
    InstanceTarget(newPath.circuit, newPath.module, newPath.asPath, instance, ofModule)

  override def leafModule: String = ofModule
}

/** Named classes associate an annotation with a component in a Firrtl circuit */
sealed trait Named {
  def serialize: String
  def toTarget:  CompleteTarget
}

final case class CircuitName(name: String) extends Named {
  if (!validModuleName(name)) throw AnnotationException(s"Illegal circuit name: $name")
  def serialize: String = name
  def toTarget:  CircuitTarget = CircuitTarget(name)
}

final case class ModuleName(name: String, circuit: CircuitName) extends Named {
  if (!validModuleName(name)) throw AnnotationException(s"Illegal module name: $name")
  def serialize: String = circuit.serialize + "." + name
  def toTarget:  ModuleTarget = ModuleTarget(circuit.name, name)
}

final case class ComponentName(name: String, module: ModuleName) extends Named {
  if (!validComponentName(name)) throw AnnotationException(s"Illegal component name: $name")
  def expr:      Expression = toExp(name)
  def serialize: String = module.serialize + "." + name
  def toTarget: ReferenceTarget = {
    Target.toTargetTokens(name).toList match {
      case Ref(r) :: components => ReferenceTarget(module.circuit.name, module.name, Nil, r, components)
      case other                => throw Target.NamedException(s"Cannot convert $this into [[ReferenceTarget]]: $other")
    }
  }
}
