///|
enum Section {
  HeaderSection
  NameMapSection
  PortsSection
  ConnSection
  CapSection
  ResSection
} derive(Eq, Debug)

///|
fn parse_int_text(text : String) -> Int? {
  try @string.parse_int(text) catch {
    _ => None
  } noraise {
    value => Some(value)
  }
}

///|
fn parse_int_token(token : Token) -> Int? {
  parse_int_text(token.text)
}

///|
fn parse_double_token(token : Token) -> Double? {
  try @string.parse_double(token.text) catch {
    _ => None
  } noraise {
    value => Some(value)
  }
}

///|
fn parse_reference_index(text : String) -> Int? {
  guard text.length() > 1 && text.unsafe_get(0) == '*' else { return None }
  parse_int_text(text[1:].to_owned())
}

///|
fn line_error(
  diagnostics : Array[@diagnostic.Diagnostic],
  code : String,
  message : String,
  token : Token,
  suggestion? : String,
) -> Unit {
  diagnostics.push({
    code,
    severity: @diagnostic.Error,
    message,
    suggestion,
    span: Some(token.span),
    net: None,
  })
}

///|
fn parse_connection(
  tokens : Array[Token],
  diagnostics : Array[@diagnostic.Diagnostic],
) -> AstConnection? {
  guard tokens.length() >= 3 else {
    if tokens.length() > 0 {
      line_error(
        diagnostics,
        "SPEF-P020",
        "connection requires kind, node name and direction",
        tokens[0],
      )
    }
    return None
  }
  let mut coordinate : (Double, Double)? = None
  let mut load : Double? = None
  let mut driving_cell : String? = None
  let mut index = 3
  while index < tokens.length() {
    match tokens[index].text {
      "*C" =>
        if index + 2 < tokens.length() {
          match
            (
              parse_double_token(tokens[index + 1]),
              parse_double_token(tokens[index + 2]),
            ) {
            (Some(x), Some(y)) => coordinate = Some((x, y))
            _ =>
              line_error(
                diagnostics,
                "SPEF-P021",
                "invalid connection coordinate",
                tokens[index],
              )
          }
          index += 3
        } else {
          line_error(
            diagnostics,
            "SPEF-P021",
            "connection coordinate requires x and y",
            tokens[index],
          )
          index = tokens.length()
        }
      "*L" =>
        if index + 1 < tokens.length() {
          load = parse_double_token(tokens[index + 1])
          if load is None {
            line_error(
              diagnostics,
              "SPEF-P022",
              "invalid connection load capacitance",
              tokens[index + 1],
            )
          }
          index += 2
        } else {
          index += 1
        }
      "*D" =>
        if index + 1 < tokens.length() {
          driving_cell = Some(tokens[index + 1].text)
          index += 2
        } else {
          index += 1
        }
      _ => index += 1
    }
  }
  Some({
    kind: tokens[0].text,
    name: tokens[1].text,
    direction: tokens[2].text,
    coordinate,
    load,
    driving_cell,
    span: tokens[0].span,
  })
}

///|
fn parse_port(
  tokens : Array[Token],
  diagnostics : Array[@diagnostic.Diagnostic],
) -> AstPort? {
  guard tokens.length() >= 2 else {
    if tokens.length() > 0 {
      line_error(
        diagnostics,
        "SPEF-P015",
        "port requires a name and direction",
        tokens[0],
      )
    }
    return None
  }
  let mut coordinate : (Double, Double)? = None
  let mut index = 2
  while index < tokens.length() {
    if tokens[index].text == "*C" && index + 2 < tokens.length() {
      match
        (
          parse_double_token(tokens[index + 1]),
          parse_double_token(tokens[index + 2]),
        ) {
        (Some(x), Some(y)) => coordinate = Some((x, y))
        _ =>
          line_error(
            diagnostics,
            "SPEF-P016",
            "invalid port coordinate",
            tokens[index],
          )
      }
      index += 3
    } else {
      index += 1
    }
  }
  Some({
    name: tokens[0].text,
    direction: tokens[1].text,
    coordinate,
    span: tokens[0].span,
  })
}

///|
fn parse_capacitor(
  tokens : Array[Token],
  diagnostics : Array[@diagnostic.Diagnostic],
) -> AstCapacitor? {
  guard tokens.length() == 3 || tokens.length() == 4 else {
    if tokens.length() > 0 {
      line_error(
        diagnostics,
        "SPEF-P030",
        "capacitor requires id, one or two nodes, and value",
        tokens[0],
      )
    }
    return None
  }
  guard parse_int_token(tokens[0]) is Some(id) else {
    line_error(diagnostics, "SPEF-P031", "invalid capacitor id", tokens[0])
    return None
  }
  let value_index = tokens.length() - 1
  guard parse_double_token(tokens[value_index]) is Some(value) else {
    line_error(
      diagnostics,
      "SPEF-P032",
      "invalid capacitance value",
      tokens[value_index],
    )
    return None
  }
  Some({
    id,
    first: tokens[1].text,
    second: if tokens.length() == 4 {
      Some(tokens[2].text)
    } else {
      None
    },
    value,
    span: tokens[0].span,
  })
}

///|
fn parse_resistor(
  tokens : Array[Token],
  diagnostics : Array[@diagnostic.Diagnostic],
) -> AstResistor? {
  guard tokens.length() == 4 else {
    if tokens.length() > 0 {
      line_error(
        diagnostics,
        "SPEF-P040",
        "resistor requires id, two nodes, and value",
        tokens[0],
      )
    }
    return None
  }
  guard parse_int_token(tokens[0]) is Some(id) else {
    line_error(diagnostics, "SPEF-P041", "invalid resistor id", tokens[0])
    return None
  }
  guard parse_double_token(tokens[3]) is Some(value) else {
    line_error(diagnostics, "SPEF-P042", "invalid resistance value", tokens[3])
    return None
  }
  Some({
    id,
    first: tokens[1].text,
    second: tokens[2].text,
    value,
    span: tokens[0].span,
  })
}

///|
fn parse_ast(lexed : LexResult) -> (SpefAst, Array[@diagnostic.Diagnostic]) {
  let headers : Array[AstHeader] = []
  let name_map : Array[AstNameMapEntry] = []
  let ports : Array[AstPort] = []
  let nets : Array[AstNet] = []
  let diagnostics = lexed.diagnostics.copy()
  let mut section = HeaderSection
  let mut current_name : String? = None
  let mut current_cap = 0.0
  let mut current_span : @diagnostic.SourceSpan? = None
  let mut connections : Array[AstConnection] = []
  let mut capacitors : Array[AstCapacitor] = []
  let mut resistors : Array[AstResistor] = []

  for line in lexed.lines {
    if line.tokens.length() == 0 {
      continue
    }
    let tokens = line.tokens
    let first = tokens[0]
    match first.text {
      "*R_NET" | "*R_PNET" | "*D_PNET" | "*INDUC" => {
        diagnostics.push(
          @diagnostic.Diagnostic::error(
            "SPEF-P060",
            "unsupported SPEF structural section \{first.text}",
            span=first.span,
            suggestion="convert the input to a *D_NET resistance/capacitance representation",
          ),
        )
        section = HeaderSection
        continue
      }
      "*NAME_MAP" => {
        section = NameMapSection
        continue
      }
      "*PORTS" => {
        section = PortsSection
        continue
      }
      "*D_NET" => {
        if current_name is Some(previous) {
          diagnostics.push(
            @diagnostic.Diagnostic::error(
              "SPEF-P010",
              "net \{previous} is missing *END",
              span=current_span.unwrap(),
              suggestion="terminate each *D_NET block with *END",
              net=previous,
            ),
          )
          nets.push({
            name: previous,
            declared_capacitance: current_cap,
            connections,
            capacitors,
            resistors,
            span: current_span.unwrap(),
          })
        }
        guard tokens.length() >= 3 else {
          line_error(
            diagnostics, "SPEF-P011", "*D_NET requires a name and total capacitance",
            first,
          )
          continue
        }
        current_name = Some(tokens[1].text)
        current_span = Some(first.span)
        current_cap = match parse_double_token(tokens[2]) {
          Some(value) => value
          None => {
            line_error(
              diagnostics,
              "SPEF-P012",
              "invalid *D_NET total capacitance",
              tokens[2],
            )
            0.0
          }
        }
        connections = []
        capacitors = []
        resistors = []
        section = ConnSection
        continue
      }
      "*CONN" => {
        section = ConnSection
        continue
      }
      "*CAP" => {
        section = CapSection
        continue
      }
      "*RES" => {
        section = ResSection
        continue
      }
      "*END" => {
        match current_name {
          Some(name) => {
            nets.push({
              name,
              declared_capacitance: current_cap,
              connections,
              capacitors,
              resistors,
              span: current_span.unwrap(),
            })
            current_name = None
            current_span = None
            connections = []
            capacitors = []
            resistors = []
            section = HeaderSection
          }
          None =>
            line_error(
              diagnostics, "SPEF-P013", "*END appears outside a net", first,
            )
        }
        continue
      }
      _ => ()
    }

    match section {
      HeaderSection => {
        let values : Array[String] = []
        for index in 1..
        match parse_reference_index(first.text) {
          Some(index) =>
            if tokens.length() >= 2 {
              name_map.push({ index, name: tokens[1].text, span: first.span })
            } else {
              line_error(
                diagnostics, "SPEF-P014", "name-map entry requires a mapped name",
                first,
              )
            }
          None => {
            let values : Array[String] = []
            for index in 1..
        if parse_port(tokens, diagnostics) is Some(port) {
          ports.push(port)
        }
      ConnSection =>
        if parse_connection(tokens, diagnostics) is Some(connection) {
          connections.push(connection)
        }
      CapSection =>
        if parse_capacitor(tokens, diagnostics) is Some(capacitor) {
          capacitors.push(capacitor)
        }
      ResSection =>
        if parse_resistor(tokens, diagnostics) is Some(resistor) {
          resistors.push(resistor)
        }
    }
  }
  if current_name is Some(name) {
    diagnostics.push(
      @diagnostic.Diagnostic::error(
        "SPEF-P010",
        "net \{name} is missing *END",
        span=current_span.unwrap(),
        suggestion="terminate each *D_NET block with *END",
        net=name,
      ),
    )
    nets.push({
      name,
      declared_capacitance: current_cap,
      connections,
      capacitors,
      resistors,
      span: current_span.unwrap(),
    })
  }
  ({ headers, name_map, ports, nets }, diagnostics)
}

///|
fn first_value(values : Array[String]) -> String? {
  if values.length() > 0 {
    Some(values[0])
  } else {
    None
  }
}

///|
fn parse_unit(
  header : AstHeader,
  quantity : @model.Quantity,
  fallback : @model.UnitScale,
  diagnostics : Array[@diagnostic.Diagnostic],
) -> @model.UnitScale {
  guard header.values.length() >= 2 else {
    diagnostics.push(
      @diagnostic.Diagnostic::error(
        "SPEF-P050",
        "unit declaration requires magnitude and symbol",
        span=header.span,
      ),
    )
    return fallback
  }
  let magnitude = @string.parse_double(header.values[0]) catch {
    _ => {
      diagnostics.push(
        @diagnostic.Diagnostic::error(
          "SPEF-P051",
          "invalid unit magnitude",
          span=header.span,
        ),
      )
      return fallback
    }
  }
  match @model.UnitScale::new(quantity, magnitude, header.values[1]) {
    Some(unit) => unit
    None => {
      diagnostics.push(
        @diagnostic.Diagnostic::error(
          "SPEF-P052",
          "unsupported unit symbol \{header.values[1]}",
          span=header.span,
        ),
      )
      fallback
    }
  }
}

///|
fn expand_name(
  raw : String,
  name_map : Array[AstNameMapEntry],
  delimiter : String,
) -> String {
  guard raw.length() > 1 && raw.unsafe_get(0) == '*' else { return raw }
  let mut split = raw.length()
  if delimiter.length() == 1 {
    let delimiter_char = delimiter.unsafe_get(0)
    for index in 1.. Int? {
  for node in nodes {
    if node.name == name {
      return Some(node.id)
    }
  }
  None
}

///|
fn ensure_node(
  nodes : Array[@model.Node],
  name : String,
  kind : @model.NodeKind,
  span : @diagnostic.SourceSpan,
) -> Int {
  match find_node_id(nodes, name) {
    Some(id) => id
    None => {
      let id = nodes.length()
      nodes.push({ id, name, kind, span: Some(span) })
      id
    }
  }
}

///|
fn build_network(
  ast : SpefAst,
  diagnostics : Array[@diagnostic.Diagnostic],
) -> @model.RCNetwork {
  let mut spef_version : String? = None
  let mut design_name : String? = None
  let mut date : String? = None
  let mut vendor : String? = None
  let mut program : String? = None
  let mut program_version : String? = None
  let design_flow : Array[String] = []
  let mut divider = "/"
  let mut delimiter = ":"
  let mut bus_open = "["
  let mut bus_close = "]"
  let mut time_unit = @model.default_time_unit()
  let mut cap_unit = @model.default_capacitance_unit()
  let mut res_unit = @model.default_resistance_unit()
  let mut ind_unit = @model.default_inductance_unit()
  for header in ast.headers {
    match header.keyword {
      "*SPEF" => spef_version = first_value(header.values)
      "*DESIGN" => design_name = first_value(header.values)
      "*DATE" => date = first_value(header.values)
      "*VENDOR" => vendor = first_value(header.values)
      "*PROGRAM" => program = first_value(header.values)
      "*VERSION" => program_version = first_value(header.values)
      "*DESIGN_FLOW" =>
        for value in header.values {
          design_flow.push(value)
        }
      "*DIVIDER" =>
        if first_value(header.values) is Some(value) {
          divider = value
        }
      "*DELIMITER" =>
        if first_value(header.values) is Some(value) {
          delimiter = value
        }
      "*BUS_DELIMITER" => {
        if header.values.length() >= 1 {
          bus_open = header.values[0]
        }
        if header.values.length() >= 2 {
          bus_close = header.values[1]
        }
      }
      "*T_UNIT" =>
        time_unit = parse_unit(header, @model.Time, time_unit, diagnostics)
      "*C_UNIT" =>
        cap_unit = parse_unit(header, @model.Capacitance, cap_unit, diagnostics)
      "*R_UNIT" =>
        res_unit = parse_unit(header, @model.Resistance, res_unit, diagnostics)
      "*L_UNIT" =>
        ind_unit = parse_unit(header, @model.Inductance, ind_unit, diagnostics)
      _ => ()
    }
  }
  let header : @model.DesignHeader = {
    spef_version,
    design_name,
    date,
    vendor,
    program,
    program_version,
    design_flow,
    divider,
    delimiter,
    bus_delimiter_open: bus_open,
    bus_delimiter_close: bus_close,
    time_unit,
    capacitance_unit: cap_unit,
    resistance_unit: res_unit,
    inductance_unit: ind_unit,
  }
  let mapped : Array[@model.NameMapEntry] = []
  for entry in ast.name_map {
    mapped.push({ index: entry.index, name: entry.name, span: Some(entry.span) })
  }
  let ports : Array[@model.PortDecl] = []
  for port in ast.ports {
    let coordinate = match port.coordinate {
      Some((x, y)) => Some(@model.Point::{ x, y })
      None => None
    }
    ports.push({
      name: expand_name(port.name, ast.name_map, delimiter),
      direction: @model.Direction::from_spef(port.direction),
      coordinate,
      span: Some(port.span),
    })
  }
  let nets : Array[@model.Net] = []
  for ast_net in ast.nets {
    let nodes : Array[@model.Node] = []
    let connections : Array[@model.Connection] = []
    let capacitors : Array[@model.Capacitor] = []
    let resistors : Array[@model.Resistor] = []
    for connection in ast_net.connections {
      let name = expand_name(connection.name, ast.name_map, delimiter)
      let kind = if connection.kind == "*P" {
        @model.Port
      } else {
        @model.InstancePin
      }
      let node_id = ensure_node(nodes, name, kind, connection.span)
      let coordinate = match connection.coordinate {
        Some((x, y)) => Some(@model.Point::{ x, y })
        None => None
      }
      connections.push({
        node_id,
        direction: @model.Direction::from_spef(connection.direction),
        coordinate,
        load_capacitance: match connection.load {
          Some(value) => Some(cap_unit.to_si(value))
          None => None
        },
        driving_cell: connection.driving_cell,
        span: Some(connection.span),
      })
    }
    for capacitor in ast_net.capacitors {
      let first_name = expand_name(capacitor.first, ast.name_map, delimiter)
      let first_id = ensure_node(
        nodes,
        first_name,
        @model.Internal,
        capacitor.span,
      )
      let second_id = match capacitor.second {
        Some(raw) => {
          let name = expand_name(raw, ast.name_map, delimiter)
          Some(ensure_node(nodes, name, @model.External, capacitor.span))
        }
        None => None
      }
      capacitors.push({
        id: capacitor.id,
        from_node: first_id,
        to_node: second_id,
        capacitance: cap_unit.to_si(capacitor.value),
        span: Some(capacitor.span),
      })
    }
    for resistor in ast_net.resistors {
      let first_name = expand_name(resistor.first, ast.name_map, delimiter)
      let second_name = expand_name(resistor.second, ast.name_map, delimiter)
      let first_id = ensure_node(
        nodes,
        first_name,
        @model.Internal,
        resistor.span,
      )
      let second_id = ensure_node(
        nodes,
        second_name,
        @model.Internal,
        resistor.span,
      )
      resistors.push({
        id: resistor.id,
        from_node: first_id,
        to_node: second_id,
        resistance: res_unit.to_si(resistor.value),
        span: Some(resistor.span),
      })
    }
    nets.push({
      id: nets.length(),
      name: expand_name(ast_net.name, ast.name_map, delimiter),
      declared_capacitance: cap_unit.to_si(ast_net.declared_capacitance),
      nodes,
      connections,
      resistors,
      capacitors,
      span: Some(ast_net.span),
    })
  }
  { header, name_map: mapped, ports, nets, diagnostics }
}

///|
/// Parse SPEF text into both a syntax tree and normalized RC model.
pub fn parse(source : String, file? : String = "") -> ParseResult {
  let lexed = lex(source, file~)
  let (ast, diagnostics) = parse_ast(lexed)
  let network = build_network(ast, diagnostics)
  { ast, network, diagnostics }
}

///|
pub fn has_errors(result : ParseResult) -> Bool {
  result.diagnostics.iter().any(fn(diag) { diag.severity == @diagnostic.Error })
}