///|
fn find_message_index(messages : Array[DbcMessage], id : Int) -> Int {
  for index, message in messages {
    if message.id() == id {
      return index
    }
  }
  -1
}

///|
fn find_signal_index(signals : Array[DbcSignal], name : String) -> Int {
  for index, signal in signals {
    if signal.name() == name {
      return index
    }
  }
  -1
}

///|
fn parse_message_comment(
  line : String,
  number : Int,
  messages : Array[DbcMessage],
) -> Result[Unit, DbcError] {
  let cursor = TextCursor::new(line, number)
  ignore(cursor.consume_text("CM_"))
  cursor.skip_spaces()
  if !cursor.consume_text("BO_") {
    return Err(
      cursor_error(
        cursor,
        UnsupportedConstruct,
        "only BO_ and SG_ comments are supported",
      ),
    )
  }
  let id = match cursor.integer() {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let comment = match cursor.quoted() {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  match cursor.finish(semicolon=true) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let index = find_message_index(messages, id)
  if index < 0 {
    return Err(
      DbcError::new(
        UnknownMessage,
        "comment references an unknown message",
        number,
        1,
        context=line,
      ),
    )
  }
  messages[index] = messages[index].with_comment(comment)
  Ok(())
}

///|
fn parse_signal_comment(
  line : String,
  number : Int,
  messages : Array[DbcMessage],
) -> Result[Unit, DbcError] {
  let cursor = TextCursor::new(line, number)
  ignore(cursor.consume_text("CM_"))
  cursor.skip_spaces()
  if !cursor.consume_text("SG_") {
    return Err(
      cursor_error(
        cursor,
        UnsupportedConstruct,
        "only BO_ and SG_ comments are supported",
      ),
    )
  }
  let id = match cursor.integer() {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let name = match cursor.identifier() {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let comment = match cursor.quoted() {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  match cursor.finish(semicolon=true) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let message_index = find_message_index(messages, id)
  if message_index < 0 {
    return Err(
      DbcError::new(
        UnknownMessage,
        "comment references an unknown message",
        number,
        1,
        context=line,
      ),
    )
  }
  let message = messages[message_index]
  let signals = message.signals()
  let signal_index = find_signal_index(signals, name)
  if signal_index < 0 {
    return Err(
      DbcError::new(
        UnknownSignal,
        "comment references an unknown signal",
        number,
        1,
        context=line,
      ),
    )
  }
  signals[signal_index] = signals[signal_index].with_comment(comment)
  messages[message_index] = message.with_signals(signals)
  Ok(())
}

///|
fn parse_comment_annotation(
  line : String,
  number : Int,
  messages : Array[DbcMessage],
) -> Result[Bool, DbcError] {
  let cursor = TextCursor::new(line, number)
  ignore(cursor.consume_text("CM_"))
  cursor.skip_spaces()
  if cursor.remaining().has_prefix("BO_") {
    match parse_message_comment(line, number, messages) {
      Ok(_) => Ok(true)
      Err(error) => Err(error)
    }
  } else if cursor.remaining().has_prefix("SG_") {
    match parse_signal_comment(line, number, messages) {
      Ok(_) => Ok(true)
      Err(error) => Err(error)
    }
  } else {
    Ok(false)
  }
}

///|
fn contains_raw_value(values : Array[DbcValueDescription], raw : Int) -> Bool {
  for value in values {
    if value.raw() == raw {
      return true
    }
  }
  false
}

///|
fn parse_value_annotation(
  line : String,
  number : Int,
  messages : Array[DbcMessage],
) -> Result[Unit, DbcError] {
  let cursor = TextCursor::new(line, number)
  ignore(cursor.consume_text("VAL_"))
  let id = match cursor.integer() {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let name = match cursor.identifier() {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let values : Array[DbcValueDescription] = []
  cursor.skip_spaces()
  while !cursor.at_end() && cursor.peek() != 59 {
    let raw = match cursor.integer() {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    let label = match cursor.quoted() {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    if contains_raw_value(values, raw) {
      return Err(
        DbcError::new(
          InvalidValueTable,
          "value table repeats a raw value",
          number,
          1,
          context=line,
        ),
      )
    }
    values.push(DbcValueDescription::new(raw, label))
    cursor.skip_spaces()
  }
  if values.is_empty() {
    return Err(
      DbcError::new(
        InvalidValueTable,
        "value table must contain at least one entry",
        number,
        1,
        context=line,
      ),
    )
  }
  match cursor.finish(semicolon=true) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let message_index = find_message_index(messages, id)
  if message_index < 0 {
    return Err(
      DbcError::new(
        UnknownMessage,
        "value table references an unknown message",
        number,
        1,
        context=line,
      ),
    )
  }
  let message = messages[message_index]
  let signals = message.signals()
  let signal_index = find_signal_index(signals, name)
  if signal_index < 0 {
    return Err(
      DbcError::new(
        UnknownSignal,
        "value table references an unknown signal",
        number,
        1,
        context=line,
      ),
    )
  }
  signals[signal_index] = signals[signal_index].with_values(values)
  messages[message_index] = message.with_signals(signals)
  Ok(())
}