///|
pub(all) enum ContractStatus {
  Match
  WidthMismatch
  DirectionMismatch
  MissingPort
  UnknownModule
  UnknownWidth
} derive(Eq, Debug)

///|
pub(all) struct PortContract {
  parent_module : String
  instance_name : String
  port_name : String
  expression_name : String?
  status : ContractStatus
  span : @source.Span
} derive(Eq, Debug)

///|
fn connection_port(
  child : ModuleInfo,
  connection : @syntax.Connection,
  index : Int,
) -> @syntax.Port? {
  match connection.port_name {
    Some(name) => {
      let mut result : @syntax.Port? = None
      for port in child.ports {
        if port.name == name {
          result = Some(port)
        }
      }
      result
    }
    None => child.ports.get(index)
  }
}

///|
fn contract_status(parent_width : Width, child_width : Width) -> ContractStatus {
  match (parent_width, child_width) {
    (Known(left), Known(right)) =>
      if left == right {
        Match
      } else {
        WidthMismatch
      }
    _ => UnknownWidth
  }
}

///|
fn contracts_for_instance(
  modules : Array[ModuleInfo],
  parent_name : String,
  instance : @syntax.Instance,
) -> Array[PortContract] {
  let contracts : Array[PortContract] = []
  let mut child : ModuleInfo? = None
  for mod in modules {
    if mod.name == instance.module_name {
      child = Some(mod)
    }
  }
  match child {
    None =>
      for connection in instance.connections {
        contracts.push({
          parent_module: parent_name,
          instance_name: instance.instance_name,
          port_name: match connection.port_name {
            Some(name) => name
            None => ""
          },
          expression_name: expression_name(connection.expression),
          status: UnknownModule,
          span: connection.span,
        })
      }
    Some(child_module) =>
      for index, connection in instance.connections {
        match connection_port(child_module, connection, index) {
          None =>
            contracts.push({
              parent_module: parent_name,
              instance_name: instance.instance_name,
              port_name: match connection.port_name {
                Some(name) => name
                None => ""
              },
              expression_name: expression_name(connection.expression),
              status: MissingPort,
              span: connection.span,
            })
          Some(port) =>
            contracts.push({
              parent_module: parent_name,
              instance_name: instance.instance_name,
              port_name: port.name,
              expression_name: expression_name(connection.expression),
              status: contract_status(
                expression_width(modules, parent_name, connection.expression),
                width_of_range(port.width),
              ),
              span: connection.span,
            })
        }
      }
  }
  contracts
}

///|
/// Return one stable contract result for every instance connection.
pub fn SemanticIndex::port_contracts(
  self : SemanticIndex,
) -> Array[PortContract] {
  let contracts : Array[PortContract] = []
  for mod in self.modules {
    for item in mod.instances {
      match self.source_instance(mod.name, item.instance_name) {
        Some(instance) =>
          for
            contract in contracts_for_instance(self.modules, mod.name, instance) {
            contracts.push(contract)
          }
        None => ()
      }
    }
  }
  contracts
}

///|
fn SemanticIndex::source_instance(
  self : SemanticIndex,
  parent_name : String,
  instance_name : String,
) -> @syntax.Instance? {
  for mod in self.modules {
    if mod.name == parent_name {
      for item in mod.instances {
        if item.instance_name == instance_name {
          for syntax_mod in self.syntax_modules {
            if syntax_mod.name == parent_name {
              for syntax_item in syntax_mod.items {
                match syntax_item {
                  @syntax.Instance(value) =>
                    if value.instance_name == instance_name {
                      return Some(value)
                    }
                  _ => ()
                }
              }
            }
          }
        }
      }
    }
  }
  None
}