///|
fn json_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 == 8 {
      output = output + "\\b"
    } else if code == 9 {
      output = output + "\\t"
    } else if code == 10 {
      output = output + "\\n"
    } else if code == 12 {
      output = output + "\\f"
    } else if code == 13 {
      output = output + "\\r"
    } else if code < 32 {
      let digits = "0123456789ABCDEF"
      output = output +
        "\\u00" +
        digits[(code >> 4) & 15].to_char().unwrap().to_string() +
        digits[code & 15].to_char().unwrap().to_string()
    } else {
      output = output + character.to_string()
    }
  }
  output
}

///|
fn json_string(value : String) -> String {
  "\"" + json_escape(value) + "\""
}

///|
fn severity_text(severity : ValidationSeverity) -> String {
  match severity {
    Error => "error"
    Warning => "warning"
  }
}

///|
fn change_kind_text(kind : ChangeKind) -> String {
  match kind {
    Added => "added"
    Removed => "removed"
    Modified => "modified"
  }
}

///|
fn optional_int_json(value : Int?) -> String {
  match value {
    Some(number) => number.to_string()
    None => "null"
  }
}

///|
fn optional_string_json(value : String?) -> String {
  match value {
    Some(text) => json_string(text)
    None => "null"
  }
}

///|
pub fn render_statistics_text(statistics : DbcStatistics) -> String {
  let mut output = "DBC statistics\n"
  output = output + "nodes: \{statistics.node_count()}\n"
  output = output + "messages: \{statistics.message_count()}\n"
  output = output + "signals: \{statistics.signal_count()}\n"
  output = output + "classic_messages: \{statistics.classic_message_count()}\n"
  output = output + "fd_messages: \{statistics.fd_message_count()}\n"
  output = output + "intel_signals: \{statistics.intel_signal_count()}\n"
  output = output + "motorola_signals: \{statistics.motorola_signal_count()}\n"
  output = output + "signed_signals: \{statistics.signed_signal_count()}\n"
  output = output + "described_values: \{statistics.described_value_count()}\n"
  output = output + "payload_bytes: \{statistics.payload_bytes()}\n"
  if !statistics.node_traffic().is_empty() {
    output = output + "node_traffic:\n"
    for traffic in statistics.node_traffic() {
      output = output +
        "  \{traffic.node()}: tx=\{traffic.transmitted_messages()}, rx_messages=\{traffic.received_messages()}, rx_signals=\{traffic.received_signals()}\n"
    }
  }
  output
}

///|
pub fn render_statistics_json(statistics : DbcStatistics) -> String {
  let mut output = "{"
  output = output + "\"node_count\":\{statistics.node_count()},"
  output = output + "\"message_count\":\{statistics.message_count()},"
  output = output + "\"signal_count\":\{statistics.signal_count()},"
  output = output +
    "\"classic_message_count\":\{statistics.classic_message_count()},"
  output = output + "\"fd_message_count\":\{statistics.fd_message_count()},"
  output = output + "\"intel_signal_count\":\{statistics.intel_signal_count()},"
  output = output +
    "\"motorola_signal_count\":\{statistics.motorola_signal_count()},"
  output = output +
    "\"signed_signal_count\":\{statistics.signed_signal_count()},"
  output = output +
    "\"described_value_count\":\{statistics.described_value_count()},"
  output = output + "\"payload_bytes\":\{statistics.payload_bytes()},"
  output = output + "\"node_traffic\":["
  for index, traffic in statistics.node_traffic() {
    if index > 0 {
      output = output + ","
    }
    output = output +
      "{\"node\":" +
      json_string(traffic.node()) +
      ",\"transmitted_messages\":\{traffic.transmitted_messages()}" +
      ",\"received_messages\":\{traffic.received_messages()}" +
      ",\"received_signals\":\{traffic.received_signals()}}"
  }
  output + "]}"
}

///|
pub fn render_validation_text(result : DbcValidationResult) -> String {
  let mut output = "DBC validation: errors=\{result.error_count()}, warnings=\{result.warning_count()}\n"
  for issue in result.issues() {
    output = output + "[\{severity_text(issue.severity())}] \{issue.code()}"
    match issue.message_id() {
      Some(id) => output = output + " message=\{id}"
      None => ()
    }
    match issue.signal_name() {
      Some(name) => output = output + " signal=\{name}"
      None => ()
    }
    output = output + " line=\{issue.source_line()}: \{issue.message()}\n"
  }
  output
}

///|
pub fn render_validation_json(result : DbcValidationResult) -> String {
  let mut output = "{\"valid\":\{result.is_valid()},\"error_count\":\{result.error_count()},\"warning_count\":\{result.warning_count()},\"issues\":["
  for index, issue in result.issues() {
    if index > 0 {
      output = output + ","
    }
    output = output +
      "{\"severity\":" +
      json_string(severity_text(issue.severity())) +
      ",\"code\":" +
      json_string(issue.code()) +
      ",\"message\":" +
      json_string(issue.message()) +
      ",\"message_id\":" +
      optional_int_json(issue.message_id()) +
      ",\"signal_name\":" +
      optional_string_json(issue.signal_name()) +
      ",\"source_line\":\{issue.source_line()}}"
  }
  output + "]}"
}

///|
pub fn render_comparison_text(comparison : DbcComparison) -> String {
  let mut output = "DBC comparison: breaking_changes=\{comparison.breaking_change_count()}\n"
  for change in comparison.message_changes() {
    output = output +
      "[message \{change_kind_text(change.kind())}] id=\{change.message_id()} name=\{change.message_name()} breaking=\{change.is_breaking()}: \{change.detail()}\n"
  }
  for change in comparison.signal_changes() {
    output = output +
      "[signal \{change_kind_text(change.kind())}] message=\{change.message_id()} signal=\{change.signal_name()} breaking=\{change.is_breaking()}: \{change.detail()}\n"
  }
  output
}

///|
pub fn render_comparison_json(comparison : DbcComparison) -> String {
  let mut output = "{\"compatible\":\{comparison.is_compatible()},\"breaking_change_count\":\{comparison.breaking_change_count()},\"message_changes\":["
  for index, change in comparison.message_changes() {
    if index > 0 {
      output = output + ","
    }
    output = output +
      "{\"kind\":" +
      json_string(change_kind_text(change.kind())) +
      ",\"message_id\":\{change.message_id()}" +
      ",\"message_name\":" +
      json_string(change.message_name()) +
      ",\"detail\":" +
      json_string(change.detail()) +
      ",\"breaking\":\{change.is_breaking()}}"
  }
  output = output + "],\"signal_changes\":["
  for index, change in comparison.signal_changes() {
    if index > 0 {
      output = output + ","
    }
    output = output +
      "{\"kind\":" +
      json_string(change_kind_text(change.kind())) +
      ",\"message_id\":\{change.message_id()}" +
      ",\"signal_name\":" +
      json_string(change.signal_name()) +
      ",\"detail\":" +
      json_string(change.detail()) +
      ",\"breaking\":\{change.is_breaking()}}"
  }
  output + "]}"
}

///|
pub fn render_decoded_text(
  message : DbcMessage,
  decoded : Array[DecodedSignal],
) -> String {
  let mut output = "Decoded message \{message.name()} (\{message.id()})\n"
  for value in decoded {
    output = output +
      "\{value.name()}: raw=\{value.signed_raw()}, physical=\{value.physical()}"
    match value.label() {
      Some(label) => output = output + ", label=\{label}"
      None => ()
    }
    output = output + "\n"
  }
  output
}

///|
pub fn render_decoded_json(
  message : DbcMessage,
  decoded : Array[DecodedSignal],
) -> String {
  let mut output = "{\"message_id\":\{message.id()},\"message_name\":" +
    json_string(message.name()) +
    ",\"signals\":["
  for index, value in decoded {
    if index > 0 {
      output = output + ","
    }
    output = output +
      "{\"name\":" +
      json_string(value.name()) +
      ",\"raw_bits\":" +
      json_string(value.raw_bits().to_string()) +
      ",\"signed_raw\":\{value.signed_raw()}" +
      ",\"physical\":\{value.physical()}" +
      ",\"label\":" +
      optional_string_json(value.label()) +
      "}"
  }
  output + "]}"
}