// SPDX-License-Identifier: Apache-2.0

package firrtl.passes
package clocklist

import firrtl._
import firrtl.ir._
import wiring.Lineage
import Utils._
import memlib.AnalysisUtils._

object ClockListUtils {

  /** Returns a list of clock outputs from instances of external modules
    */
  def getSourceList(moduleMap: Map[String, DefModule])(lin: Lineage): Seq[String] = {
    val s = lin.foldLeft(Seq[String]()) {
      case (sL, (i, l)) =>
        val sLx = getSourceList(moduleMap)(l)
        val sLxx = sLx.map(i + "$" + _)
        sL ++ sLxx
    }
    val sourceList = moduleMap(lin.name) match {
      case ExtModule(i, n, ports, dn, p) =>
        val portExps = ports.flatMap { p => create_exps(WRef(p.name, p.tpe, PortKind, to_flow(p.direction))) }
        portExps.filter(e => (e.tpe == ClockType) && (flow(e) == SinkFlow)).map(_.serialize)
      case _ => Nil
    }
    val sx = sourceList ++ s
    sx
  }

  /** Returns a map from instance name to its clock origin.
    *  Child instances are not included if they share the same clock as their parent
    */
  def getOrigins(
    connects:  Connects,
    me:        String,
    moduleMap: Map[String, DefModule]
  )(lin:       Lineage
  ): Map[String, String] = {
    val sep = if (me == "") "" else "$"
    // Get origins from all children
    val childrenOrigins = lin.foldLeft(Map[String, String]()) {
      case (o, (i, l)) =>
        o ++ getOrigins(connects, me + sep + i, moduleMap)(l)
    }
    // If I have a clock, get it
    val clockOpt = moduleMap(lin.name) match {
      case Module(i, n, ports, b)        => ports.collectFirst { case p if p.name == "clock" => me + sep + "clock" }
      case ExtModule(i, n, ports, dn, p) => None
    }
    // Return new origins with direct children removed, if they match my clock
    clockOpt match {
      case Some(clock) =>
        val myOrigin = getOrigin(connects, clock).serialize
        childrenOrigins.foldLeft(Map(me -> myOrigin)) {
          case (o, (childInstance, childOrigin)) =>
            val childrenInstances = lin.children.map { case (instance, _) => me + sep + instance }
            // If direct child shares my origin, omit it
            if (childOrigin == myOrigin && childrenInstances.contains(childInstance)) o
            else o + (childInstance -> childOrigin)
        }
      case None => childrenOrigins
    }
  }
}
