///|
pub(all) struct DesignMetrics {
  mut modules : Int
  mut ports : Int
  mut signals : Int
  mut assignments : Int
  mut always_blocks : Int
  mut instances : Int
  mut expressions : Int
  mut statements : Int
  mut maximum_control_depth : Int
} derive(Eq, Debug)

///|
fn append_unique(names : Array[String], name : String) -> Unit {
  if !names.contains(name) {
    names.push(name)
  }
}

///|
pub fn Expression::referenced_names(self : Expression) -> Array[String] {
  let names : Array[String] = []
  match self {
    Name(name, _) => names.push(name)
    Unary(_, operand, _) =>
      for name in operand.referenced_names() {
        append_unique(names, name)
      }
    Binary(left, _, right, _) => {
      for name in left.referenced_names() {
        append_unique(names, name)
      }
      for name in right.referenced_names() {
        append_unique(names, name)
      }
    }
    Index(base, index, _) => {
      for name in base.referenced_names() {
        append_unique(names, name)
      }
      for name in index.referenced_names() {
        append_unique(names, name)
      }
    }
    Number(_, _) | StringLiteral(_, _) | Unknown(_) => ()
  }
  names
}

///|
pub fn Statement::assigned_names(self : Statement) -> Array[String] {
  let names : Array[String] = []
  match self {
    Assignment(target, _, _, _) =>
      for name in target.referenced_names() {
        append_unique(names, name)
      }
    Block(_, _) => ()
    If(_, then_branch, else_branch, _) => {
      for name in then_branch.assigned_names() {
        append_unique(names, name)
      }
      match else_branch {
        Some(branch) =>
          for name in branch.assigned_names() {
            append_unique(names, name)
          }
        None => ()
      }
    }
    Case(_, arms, _) =>
      for arm in arms {
        for name in arm.body.assigned_names() {
          append_unique(names, name)
        }
      }
    Empty(_) | Unknown(_) => ()
  }
  match self {
    Block(statements, _) =>
      for statement in statements {
        for name in statement.assigned_names() {
          append_unique(names, name)
        }
      }
    _ => ()
  }
  names
}

///|
pub fn Statement::read_names(self : Statement) -> Array[String] {
  let names : Array[String] = []
  match self {
    Assignment(target, value, _, _) => {
      for name in value.referenced_names() {
        append_unique(names, name)
      }
      for name in target.referenced_names() {
        append_unique(names, name)
      }
    }
    Block(statements, _) =>
      for statement in statements {
        for name in statement.read_names() {
          append_unique(names, name)
        }
      }
    If(condition, then_branch, else_branch, _) => {
      for name in condition.referenced_names() {
        append_unique(names, name)
      }
      for name in then_branch.read_names() {
        append_unique(names, name)
      }
      match else_branch {
        Some(branch) =>
          for name in branch.read_names() {
            append_unique(names, name)
          }
        None => ()
      }
    }
    Case(expression, arms, _) => {
      for name in expression.referenced_names() {
        append_unique(names, name)
      }
      for arm in arms {
        for choice in arm.choices {
          for name in choice.referenced_names() {
            append_unique(names, name)
          }
        }
        for name in arm.body.read_names() {
          append_unique(names, name)
        }
      }
    }
    Empty(_) | Unknown(_) => ()
  }
  names
}

///|
pub fn Statement::control_depth(self : Statement) -> Int {
  match self {
    Empty(_) | Unknown(_) | Assignment(_, _, _, _) => 0
    Block(statements, _) => {
      let mut depth = 0
      for statement in statements {
        let value = statement.control_depth()
        if value > depth {
          depth = value
        }
      }
      depth
    }
    If(_, then_branch, else_branch, _) => {
      let mut depth = 1 + then_branch.control_depth()
      match else_branch {
        Some(branch) => {
          let value = 1 + branch.control_depth()
          if value > depth {
            depth = value
          }
        }
        None => ()
      }
      depth
    }
    Case(_, arms, _) => {
      let mut depth = 1
      for arm in arms {
        let value = 1 + arm.body.control_depth()
        if value > depth {
          depth = value
        }
      }
      depth
    }
  }
}

///|
pub fn Module::port_names(self : Module) -> Array[String] {
  self.ports.map(fn(port) { port.name })
}

///|
pub fn Module::signal_count(self : Module) -> Int {
  self.signals.length()
}

///|
pub fn Module::assignment_count(self : Module) -> Int {
  let mut count = 0
  for item in self.items {
    match item {
      Assign(_) => count += 1
      Statement(statement) => count += statement.assigned_names().length()
      Always(always) => count += always.body.assigned_names().length()
      _ => ()
    }
  }
  count
}

///|
pub fn Design::metrics(self : Design) -> DesignMetrics {
  let metrics : DesignMetrics = {
    modules: self.modules.length(),
    ports: 0,
    signals: 0,
    assignments: 0,
    always_blocks: 0,
    instances: 0,
    expressions: 0,
    statements: 0,
    maximum_control_depth: 0,
  }
  for mod in self.modules {
    metrics.ports += mod.ports.length()
    metrics.signals += mod.signals.length()
    metrics.assignments += mod.assignment_count()
    metrics.instances += mod.instance_names().length()
    for item in mod.items {
      match item {
        Assign(assign) => {
          metrics.expressions += assign.target.node_count()
          metrics.expressions += assign.value.node_count()
        }
        Always(always) => {
          metrics.always_blocks += 1
          metrics.statements += always.body.node_count()
          let depth = always.body.control_depth()
          if depth > metrics.maximum_control_depth {
            metrics.maximum_control_depth = depth
          }
        }
        Statement(statement) => {
          metrics.statements += statement.node_count()
          let depth = statement.control_depth()
          if depth > metrics.maximum_control_depth {
            metrics.maximum_control_depth = depth
          }
        }
        Instance(_) | Unknown(_) => ()
      }
    }
  }
  metrics
}