///|
fn patch_kind_name(kind : PatchKind) -> String {
  match kind {
    PatchAdd => "add"
    PatchRemove => "remove"
    PatchReplace => "replace"
  }
}

///|
fn join_region_names(names : Array[String]) -> String {
  let mut result = ""
  for index, name in names {
    if index > 0 {
      result = result + ","
    }
    result = result + name
  }
  if result.length() == 0 {
    "unmapped"
  } else {
    result
  }
}

///|
pub fn render_layout_text(layout : ImageLayout) -> String {
  let mut result = "firmware-layout segments=" +
    layout.segment_count().to_string() +
    " mapped-bytes=" +
    layout.mapped_bytes().to_string() +
    " unmapped-bytes=" +
    layout.unmapped_bytes().to_string() +
    " fully-mapped=" +
    layout.fully_mapped_segments().to_string() +
    " partially-mapped=" +
    layout.partially_mapped_segments().to_string() +
    " unmapped=" +
    layout.unmapped_segments().to_string()
  match layout.entry_region() {
    Some(value) => result = result + " entry-region=" + value
    None => ()
  }
  for placement in layout.placements() {
    result = result +
      "\nsegment start=" +
      hex_address(placement.start()) +
      " end=" +
      hex_address(placement.end_exclusive()) +
      " mapped=" +
      placement.mapped_bytes().to_string() +
      " unmapped=" +
      placement.unmapped_bytes().to_string() +
      " regions=" +
      join_region_names(placement.region_names())
  }
  result
}

///|
pub fn render_layout_json(layout : ImageLayout) -> String {
  let placements : Array[Json] = []
  for placement in layout.placements() {
    let regions : Array[Json] = []
    for name in placement.region_names() {
      regions.push(Json::string(name))
    }
    placements.push(
      Json::object({
        "start": Json::string(hex_address(placement.start())),
        "end_exclusive": Json::string(hex_address(placement.end_exclusive())),
        "mapped_bytes": Json::string(placement.mapped_bytes().to_string()),
        "unmapped_bytes": Json::string(placement.unmapped_bytes().to_string()),
        "regions": Json::array(regions),
      }),
    )
  }
  Json::object({
    "type": Json::string("firmware_image_layout"),
    "accepted": Json::boolean(layout.accepted()),
    "segment_count": Json::number(layout.segment_count().to_double()),
    "mapped_bytes": Json::string(layout.mapped_bytes().to_string()),
    "unmapped_bytes": Json::string(layout.unmapped_bytes().to_string()),
    "fully_mapped_segments": Json::number(
      layout.fully_mapped_segments().to_double(),
    ),
    "partially_mapped_segments": Json::number(
      layout.partially_mapped_segments().to_double(),
    ),
    "unmapped_segments": Json::number(layout.unmapped_segments().to_double()),
    "entry_region": match layout.entry_region() {
      Some(value) => Json::string(value)
      None => Json::null()
    },
    "placements": Json::array(placements),
  }).stringify()
}

///|
pub fn render_patch_text(patch : ImagePatch) -> String {
  let mut result = "firmware-patch hunks=" +
    patch.hunks().length().to_string() +
    " added=" +
    patch.added_bytes().to_string() +
    " removed=" +
    patch.removed_bytes().to_string() +
    " changed=" +
    patch.changed_bytes().to_string()
  for hunk in patch.hunks() {
    result = result +
      "\n" +
      patch_kind_name(hunk.kind()) +
      " address=" +
      hex_address(hunk.address()) +
      " before=" +
      encode_hex_bytes(hunk.before()) +
      " after=" +
      encode_hex_bytes(hunk.after())
  }
  result
}

///|
pub fn render_patch_json(patch : ImagePatch) -> String {
  let hunks : Array[Json] = []
  for hunk in patch.hunks() {
    hunks.push(
      Json::object({
        "kind": Json::string(patch_kind_name(hunk.kind())),
        "address": Json::string(hex_address(hunk.address())),
        "length": Json::number(hunk.length().to_double()),
        "before": Json::string(encode_hex_bytes(hunk.before())),
        "after": Json::string(encode_hex_bytes(hunk.after())),
      }),
    )
  }
  Json::object({
    "type": Json::string("firmware_image_patch"),
    "old_entry": optional_address_json(patch.old_entry()),
    "new_entry": optional_address_json(patch.new_entry()),
    "added_bytes": Json::number(patch.added_bytes().to_double()),
    "removed_bytes": Json::number(patch.removed_bytes().to_double()),
    "changed_bytes": Json::number(patch.changed_bytes().to_double()),
    "hunks": Json::array(hunks),
  }).stringify()
}