///|
fn dbc_escape(value : String) -> String {
  let mut output = ""
  for character in value {
    let code = character.to_int()
    if code == 34 {
      output = output + "\\\""
    } else if code == 92 {
      output = output + "\\\\"
    } else if code == 10 {
      output = output + "\\n"
    } else if code == 13 {
      output = output + "\\r"
    } else if code == 9 {
      output = output + "\\t"
    } else if code < 32 {
      output = output + "?"
    } else {
      output = output + character.to_string()
    }
  }
  output
}

///|
fn dbc_quoted(value : String) -> String {
  "\"" + dbc_escape(value) + "\""
}

///|
fn render_double(value : Double) -> String {
  let text = value.to_string()
  if text.has_suffix(".0") {
    text[:text.length() - 2].to_owned()
  } else {
    text
  }
}

///|
fn render_receivers(receivers : Array[String]) -> String {
  let mut output = ""
  for index, receiver in receivers {
    if index > 0 {
      output = output + ","
    }
    output = output + receiver
  }
  output
}

///|
fn render_signal_line(signal : DbcSignal) -> String {
  let byte_order = match signal.byte_order() {
    Motorola => "0"
    Intel => "1"
  }
  let value_type = match signal.value_type() {
    Unsigned => "+"
    Signed => "-"
  }
  " SG_ \{signal.name()} : \{signal.start_bit()}|\{signal.bit_length()}@\{byte_order}\{value_type} (\{render_double(signal.factor())},\{render_double(signal.offset())}) [\{render_double(signal.minimum())}|\{render_double(signal.maximum())}] \{dbc_quoted(signal.unit())} \{render_receivers(signal.receivers())}\n"
}

///|
fn render_message_block(message : DbcMessage) -> String {
  let mut output = "BO_ \{message.id()} \{message.name()}: \{message.size()} \{message.transmitter()}\n"
  for signal in message.signals() {
    output = output + render_signal_line(signal)
  }
  output + "\n"
}

///|
fn render_nodes(nodes : Array[DbcNode]) -> String {
  let mut output = "BU_:"
  for node in nodes {
    output = output + " " + node.name()
  }
  output + "\n\n"
}

///|
fn render_comments(messages : Array[DbcMessage]) -> String {
  let mut output = ""
  for message in messages {
    if message.comment() != "" {
      output = output +
        "CM_ BO_ \{message.id()} \{dbc_quoted(message.comment())};\n"
    }
    for signal in message.signals() {
      if signal.comment() != "" {
        output = output +
          "CM_ SG_ \{message.id()} \{signal.name()} \{dbc_quoted(signal.comment())};\n"
      }
    }
  }
  if output != "" {
    output + "\n"
  } else {
    output
  }
}

///|
fn render_value_tables(messages : Array[DbcMessage]) -> String {
  let mut output = ""
  for message in messages {
    for signal in message.signals() {
      let values = signal.values()
      if values.is_empty() {
        continue
      }
      output = output + "VAL_ \{message.id()} \{signal.name()}"
      for value in values {
        output = output + " \{value.raw()} \{dbc_quoted(value.label())}"
      }
      output = output + ";\n"
    }
  }
  output
}

///|
fn has_duplicate_database_identity(database : DbcDatabase) -> Bool {
  let messages = database.messages()
  for index, message in messages {
    for previous in 0.. Result[String, DbcError] {
  if has_duplicate_database_identity(database) {
    return Err(
      DbcError::new(
        InvalidMessage,
        "database contains duplicate message or signal identities",
        1,
        1,
      ),
    )
  }
  let mut output = "VERSION " + dbc_quoted(database.version()) + "\n\n"
  output = output + "NS_ :\n\nBS_:\n\n"
  output = output + render_nodes(database.nodes())
  for message in database.messages() {
    output = output + render_message_block(message)
  }
  output = output + render_comments(database.messages())
  output = output + render_value_tables(database.messages())
  Ok(output)
}

///|
fn signal_round_trip_equal(left : DbcSignal, right : DbcSignal) -> Bool {
  left.name() == right.name() &&
  left.start_bit() == right.start_bit() &&
  left.bit_length() == right.bit_length() &&
  left.byte_order() == right.byte_order() &&
  left.value_type() == right.value_type() &&
  round_trip_double_equal(left.factor(), right.factor()) &&
  round_trip_double_equal(left.offset(), right.offset()) &&
  round_trip_double_equal(left.minimum(), right.minimum()) &&
  round_trip_double_equal(left.maximum(), right.maximum()) &&
  left.unit() == right.unit() &&
  left.receivers() == right.receivers() &&
  left.values() == right.values() &&
  left.comment() == right.comment()
}

///|
fn round_trip_double_equal(left : Double, right : Double) -> Bool {
  if left == right {
    return true
  }
  let difference = if left > right { left - right } else { right - left }
  let left_magnitude = if left < 0.0 { -left } else { left }
  let right_magnitude = if right < 0.0 { -right } else { right }
  let mut scale = if left_magnitude > right_magnitude {
    left_magnitude
  } else {
    right_magnitude
  }
  if scale < 1.0 {
    scale = 1.0
  }
  difference <= scale * 0.000000000001
}

///|
fn message_round_trip_equal(left : DbcMessage, right : DbcMessage) -> Bool {
  if left.id() != right.id() ||
    left.name() != right.name() ||
    left.size() != right.size() ||
    left.transmitter() != right.transmitter() ||
    left.comment() != right.comment() {
    return false
  }
  let left_signals = left.signals()
  let right_signals = right.signals()
  if left_signals.length() != right_signals.length() {
    return false
  }
  for index, signal in left_signals {
    if !signal_round_trip_equal(signal, right_signals[index]) {
      return false
    }
  }
  true
}

///|
pub fn database_content_equal(left : DbcDatabase, right : DbcDatabase) -> Bool {
  if left.version() != right.version() || left.nodes() != right.nodes() {
    return false
  }
  let left_messages = left.messages()
  let right_messages = right.messages()
  if left_messages.length() != right_messages.length() {
    return false
  }
  for index, message in left_messages {
    if !message_round_trip_equal(message, right_messages[index]) {
      return false
    }
  }
  true
}