///|
pub(all) struct ModuleMetrics {
  module_name : String
  ports : Int
  signals : Int
  instances : Int
  items : Int
  driven_signals : Int
  undriven_signals : Int
  expression_nodes : Int
} derive(Eq, Debug)

///|
pub fn SemanticIndex::module_metrics(
  self : SemanticIndex,
  module_name : String,
) -> ModuleMetrics? {
  match self.find_module(module_name) {
    None => None
    Some(mod) => {
      let mut driven = 0
      let mut undriven = 0
      for signal in mod.signals {
        if signal.has_driver() {
          driven += 1
        } else if !signal.from_port {
          undriven += 1
        }
      }
      let mut expression_nodes = 0
      for syntax_mod in self.syntax_modules {
        if syntax_mod.name == module_name {
          for item in syntax_mod.items {
            match item {
              @syntax.Assign(assign) => {
                expression_nodes += assign.target.node_count()
                expression_nodes += assign.value.node_count()
              }
              @syntax.Always(always) =>
                expression_nodes += always.body.node_count()
              @syntax.Statement(statement) =>
                expression_nodes += statement.node_count()
              _ => ()
            }
          }
        }
      }
      Some({
        module_name,
        ports: mod.ports.length(),
        signals: mod.signals.length(),
        instances: mod.instances.length(),
        items: mod.ports.length() +
        mod.signals.length() +
        mod.instances.length(),
        driven_signals: driven,
        undriven_signals: undriven,
        expression_nodes,
      })
    }
  }
}

///|
pub fn SemanticIndex::all_module_metrics(
  self : SemanticIndex,
) -> Array[ModuleMetrics] {
  let result : Array[ModuleMetrics] = []
  for mod in self.modules {
    match self.module_metrics(mod.name) {
      Some(metrics) => result.push(metrics)
      None => ()
    }
  }
  result
}

///|
pub fn SemanticIndex::dependency_nodes(self : SemanticIndex) -> Array[String] {
  let names : Array[String] = []
  for mod in self.modules {
    if !names.contains(mod.name) {
      names.push(mod.name)
    }
  }
  for edge in self.graph.edges() {
    let (parent, child) = edge
    if !names.contains(parent) {
      names.push(parent)
    }
    if !names.contains(child) {
      names.push(child)
    }
  }
  names
}

///|
pub fn SemanticIndex::reverse_dependencies(
  self : SemanticIndex,
  child_name : String,
) -> Array[String] {
  let result : Array[String] = []
  for edge in self.graph.edges() {
    let (parent, child) = edge
    if child == child_name && !result.contains(parent) {
      result.push(parent)
    }
  }
  result
}

///|
pub fn SemanticIndex::children_of(
  self : SemanticIndex,
  parent_name : String,
) -> Array[String] {
  let result : Array[String] = []
  for edge in self.graph.edges() {
    let (parent, child) = edge
    if parent == parent_name && !result.contains(child) {
      result.push(child)
    }
  }
  result
}

///|
fn add_reachable(
  index : SemanticIndex,
  name : String,
  seen : Array[String],
) -> Unit {
  if seen.contains(name) {
    return
  }
  seen.push(name)
  for child in index.children_of(name) {
    add_reachable(index, child, seen)
  }
}

///|
pub fn SemanticIndex::reachable_modules(
  self : SemanticIndex,
  root : String,
) -> Array[String] {
  let result : Array[String] = []
  add_reachable(self, root, result)
  result
}

///|
pub fn SemanticIndex::is_recursive(self : SemanticIndex, root : String) -> Bool {
  let reachable = self.reachable_modules(root)
  for child in self.children_of(root) {
    if child == root ||
      (reachable.contains(child) && self.children_of(child).contains(root)) {
      return true
    }
  }
  false
}

///|
pub fn SemanticIndex::graph_depth(self : SemanticIndex, root : String) -> Int {
  let mut frontier : Array[String] = [root]
  let seen : Array[String] = [root]
  let mut depth = 0
  while frontier.length() > 0 {
    let next : Array[String] = []
    for name in frontier {
      for child in self.children_of(name) {
        if !seen.contains(child) {
          seen.push(child)
          next.push(child)
        }
      }
    }
    if next.length() > 0 {
      depth += 1
    }
    frontier = next
  }
  depth
}

///|
pub fn SemanticIndex::edge_count(self : SemanticIndex) -> Int {
  self.graph.edges().length()
}

///|
pub fn SemanticIndex::duplicate_edge_count(self : SemanticIndex) -> Int {
  let mut duplicates = 0
  let edges = self.graph.edges()
  for i, edge in edges {
    for j in 0.. Int {
  let mut count = 0
  for mod in self.modules {
    for signal in mod.signals {
      if signal.width is Known(value) && value == width {
        count += 1
      }
    }
  }
  count
}

///|
pub fn SemanticIndex::unknown_width_count(self : SemanticIndex) -> Int {
  let mut count = 0
  for mod in self.modules {
    for signal in mod.signals {
      if signal.width is Unknown {
        count += 1
      }
    }
  }
  count
}

///|
pub fn SemanticIndex::driver_kind_count(
  self : SemanticIndex,
  kind : DriveKind,
) -> Int {
  let mut count = 0
  for mod in self.modules {
    for signal in mod.signals {
      for driver in signal.drivers {
        if driver.kind == kind {
          count += 1
        }
      }
    }
  }
  count
}

///|
pub fn SemanticIndex::contract_status_count(
  self : SemanticIndex,
  status : ContractStatus,
) -> Int {
  let mut count = 0
  for contract in self.port_contracts() {
    if contract.status == status {
      count += 1
    }
  }
  count
}

///|
pub fn SemanticIndex::analytics_summary(self : SemanticIndex) -> String {
  "modules=" +
  self.module_count().to_string() +
  ", edges=" +
  self.edge_count().to_string() +
  ", roots=" +
  self.hierarchy_roots().length().to_string() +
  ", unknown_widths=" +
  self.unknown_width_count().to_string() +
  ", undriven=" +
  self.undriven_total().to_string()
}

///|
pub fn SemanticIndex::undriven_total(self : SemanticIndex) -> Int {
  let mut count = 0
  for mod in self.modules {
    count += self.undriven_signals(mod.name).length()
  }
  count
}