///|
fn hex_address(value : UInt64) -> String {
  "0x" + value.to_string(radix=16)
}

///|
fn optional_address_json(value : UInt64?) -> Json {
  match value {
    Some(address) => Json::string(hex_address(address))
    None => Json::null()
  }
}

///|
fn audit_severity_name(value : AuditSeverity) -> String {
  match value {
    AuditInfo => "info"
    AuditWarning => "warning"
    AuditError => "error"
  }
}

///|
fn difference_kind_name(value : ByteDifferenceKind) -> String {
  match value {
    ByteAdded => "added"
    ByteRemoved => "removed"
    ByteChanged => "changed"
  }
}

///|
fn optional_byte_json(value : Byte?) -> Json {
  match value {
    Some(byte) => Json::number(byte.to_double())
    None => Json::null()
  }
}

///|
/// Render a compact deterministic human-readable image audit.
pub fn render_image_text(image : FirmwareImage, audit : ImageAudit) -> String {
  let mut result = "firmware-image segments=" +
    audit.segment_count().to_string() +
    " bytes=" +
    audit.total_bytes().to_string() +
    " gaps=" +
    audit.gap_count().to_string() +
    " errors=" +
    audit.error_count().to_string() +
    " warnings=" +
    audit.warning_count().to_string()
  match image.lowest_address() {
    Some(value) => result = result + " lowest=" + hex_address(value)
    None => ()
  }
  match image.highest_address() {
    Some(value) => result = result + " highest=" + hex_address(value)
    None => ()
  }
  match image.entry_point() {
    Some(value) => result = result + " entry=" + hex_address(value)
    None => ()
  }
  for finding in audit.findings() {
    result = result +
      "\n" +
      audit_severity_name(finding.severity()) +
      " " +
      finding.code() +
      ": " +
      finding.message()
    match finding.address() {
      Some(value) => result = result + " address=" + hex_address(value)
      None => ()
    }
  }
  result
}

///|
/// Render image structure and audit findings using the standard JSON type.
pub fn render_image_json(image : FirmwareImage, audit : ImageAudit) -> String {
  let segments : Array[Json] = []
  for segment in image.segments() {
    segments.push(
      Json::object({
        "address": Json::string(hex_address(segment.address())),
        "end_exclusive": Json::string(hex_address(segment.end_exclusive())),
        "length": Json::number(segment.length().to_double()),
      }),
    )
  }
  let gaps : Array[Json] = []
  for gap in image.gaps() {
    gaps.push(
      Json::object({
        "start": Json::string(hex_address(gap.start())),
        "end_exclusive": Json::string(hex_address(gap.end_exclusive())),
        "length": Json::string(gap.length().to_string()),
      }),
    )
  }
  let findings : Array[Json] = []
  for finding in audit.findings() {
    findings.push(
      Json::object({
        "code": Json::string(finding.code()),
        "severity": Json::string(audit_severity_name(finding.severity())),
        "message": Json::string(finding.message()),
        "address": optional_address_json(finding.address()),
      }),
    )
  }
  Json::object({
    "type": Json::string("firmware_image"),
    "segment_count": Json::number(audit.segment_count().to_double()),
    "total_bytes": Json::number(audit.total_bytes().to_double()),
    "gap_count": Json::number(audit.gap_count().to_double()),
    "lowest_address": optional_address_json(image.lowest_address()),
    "highest_address": optional_address_json(image.highest_address()),
    "entry_point": optional_address_json(image.entry_point()),
    "accepted": Json::boolean(audit.accepted()),
    "segments": Json::array(segments),
    "gaps": Json::array(gaps),
    "findings": Json::array(findings),
  }).stringify()
}

///|
/// Render byte-level comparison evidence using the standard JSON type.
pub fn render_comparison_json(comparison : ImageComparison) -> String {
  let differences : Array[Json] = []
  for difference in comparison.differences() {
    differences.push(
      Json::object({
        "address": Json::string(hex_address(difference.address())),
        "kind": Json::string(difference_kind_name(difference.kind())),
        "left": optional_byte_json(difference.left()),
        "right": optional_byte_json(difference.right()),
      }),
    )
  }
  Json::object({
    "type": Json::string("firmware_image_comparison"),
    "identical": Json::boolean(comparison.identical()),
    "added": Json::number(comparison.added_count().to_double()),
    "removed": Json::number(comparison.removed_count().to_double()),
    "changed": Json::number(comparison.changed_count().to_double()),
    "differences": Json::array(differences),
  }).stringify()
}