///|
pub fn collect_uris(playlist : HlsPlaylist) -> Array[UriEntry] {
  let entries : Array[UriEntry] = []
  for segment in playlist.segments {
    entries.push({ role: SegmentUri, uri: segment.uri, line: segment.line })
    match segment.key {
      Some(key) =>
        match key.uri {
          Some(uri) => entries.push({ role: KeyUri, uri, line: key.line })
          None => ()
        }
      None => ()
    }
    match segment.map {
      Some(map) => entries.push({ role: MapUri, uri: map.uri, line: map.line })
      None => ()
    }
  }
  for variant in playlist.variants {
    entries.push({ role: VariantUri, uri: variant.uri, line: variant.line })
  }
  for rendition in playlist.renditions {
    match rendition.uri {
      Some(uri) => entries.push({ role: RenditionUri, uri, line: rendition.line })
      None => ()
    }
  }
  entries
}

///|
pub fn audit_uris(playlist : HlsPlaylist) -> UriAudit {
  let entries = collect_uris(playlist)
  let mut absolute_uris = 0
  let mut relative_uris = 0
  let mut data_uris = 0
  let mut empty_uris = 0
  let mut parent_traversal_uris = 0
  for entry in entries {
    if entry.uri == "" {
      empty_uris += 1
    } else if entry.uri.has_prefix("data:") {
      data_uris += 1
    } else if uri_is_absolute(entry.uri) {
      absolute_uris += 1
    } else {
      relative_uris += 1
    }
    if uri_has_parent_traversal(entry.uri) {
      parent_traversal_uris += 1
    }
  }
  {
    total: entries.length(),
    absolute_uris,
    relative_uris,
    data_uris,
    empty_uris,
    parent_traversal_uris,
    entries,
  }
}

///|
fn uri_is_absolute(uri : String) -> Bool {
  uri.contains("://") || uri.has_prefix("//")
}

///|
fn uri_has_parent_traversal(uri : String) -> Bool {
  uri == ".." || uri.has_prefix("../") || uri.contains("/../")
}

///|
pub fn uri_role_to_string(role : UriRole) -> String {
  match role {
    SegmentUri => "segment"
    VariantUri => "variant"
    RenditionUri => "rendition"
    KeyUri => "key"
    MapUri => "map"
  }
}

///|
pub fn format_uri_audit(audit : UriAudit) -> String {
  "uris=\{audit.total}, relative=\{audit.relative_uris}, absolute=\{audit.absolute_uris}, data=\{audit.data_uris}, empty=\{audit.empty_uris}, parent_traversal=\{audit.parent_traversal_uris}"
}

///|
pub fn ladder_report(playlist : HlsPlaylist) -> LadderReport {
  let mut lowest = 0
  let mut highest = 0
  let mut monotonic = true
  let mut previous = -1
  let mut duplicate_bandwidths = 0
  let mut has_resolution_gap = false
  for i, variant in playlist.variants {
    if i == 0 {
      lowest = variant.bandwidth
      highest = variant.bandwidth
    } else {
      if variant.bandwidth < lowest {
        lowest = variant.bandwidth
      }
      if variant.bandwidth > highest {
        highest = variant.bandwidth
      }
    }
    if previous > variant.bandwidth {
      monotonic = false
    }
    previous = variant.bandwidth
    for j in 0.. String {
  "variants=\{report.variants}, lowest=\{report.lowest_bandwidth}, highest=\{report.highest_bandwidth}, monotonic=\{report.monotonic}, duplicates=\{report.duplicate_bandwidths}, resolution_gap=\{report.has_resolution_gap}"
}

///|
pub fn select_variant_by_bandwidth(
  playlist : HlsPlaylist,
  max_bandwidth : Int,
) -> VariantStream? {
  let mut best_under : VariantStream? = None
  let mut lowest : VariantStream? = None
  for variant in playlist.variants {
    match lowest {
      Some(current) =>
        if variant.bandwidth < current.bandwidth {
          lowest = Some(variant)
        }
      None => lowest = Some(variant)
    }
    if variant.bandwidth <= max_bandwidth {
      match best_under {
        Some(current) =>
          if variant.bandwidth > current.bandwidth {
            best_under = Some(variant)
          }
        None => best_under = Some(variant)
      }
    }
  }
  match best_under {
    Some(value) => Some(value)
    None => lowest
  }
}

///|
pub fn filter_variants_by_height(
  playlist : HlsPlaylist,
  max_height : Int,
) -> Array[VariantStream] {
  let out : Array[VariantStream] = []
  for variant in playlist.variants {
    match variant.resolution {
      Some(resolution) =>
        if resolution.height <= max_height {
          out.push(variant)
        }
      None => ()
    }
  }
  out
}

///|
pub fn rewrite_uri_prefix(
  playlist : HlsPlaylist,
  old_prefix : String,
  new_prefix : String,
) -> HlsPlaylist {
  let segments : Array[Segment] = []
  let variants : Array[VariantStream] = []
  let renditions : Array[Rendition] = []
  for segment in playlist.segments {
    segments.push({
      ..segment,
      uri: rewrite_uri(segment.uri, old_prefix, new_prefix),
      key: rewrite_key(segment.key, old_prefix, new_prefix),
      map: rewrite_map(segment.map, old_prefix, new_prefix),
    })
  }
  for variant in playlist.variants {
    variants.push({
      ..variant,
      uri: rewrite_uri(variant.uri, old_prefix, new_prefix),
    })
  }
  for rendition in playlist.renditions {
    renditions.push({
      ..rendition,
      uri: rewrite_optional_uri(rendition.uri, old_prefix, new_prefix),
    })
  }
  { ..playlist, segments, variants, renditions }
}

///|
fn rewrite_key(
  key : KeyInfo?,
  old_prefix : String,
  new_prefix : String,
) -> KeyInfo? {
  match key {
    Some(value) =>
      Some({
        ..value,
        uri: rewrite_optional_uri(value.uri, old_prefix, new_prefix),
      })
    None => None
  }
}

///|
fn rewrite_map(
  map : SegmentMap?,
  old_prefix : String,
  new_prefix : String,
) -> SegmentMap? {
  match map {
    Some(value) =>
      Some({ ..value, uri: rewrite_uri(value.uri, old_prefix, new_prefix) })
    None => None
  }
}

///|
fn rewrite_optional_uri(
  uri : String?,
  old_prefix : String,
  new_prefix : String,
) -> String? {
  match uri {
    Some(value) => Some(rewrite_uri(value, old_prefix, new_prefix))
    None => None
  }
}

///|
fn rewrite_uri(uri : String, old_prefix : String, new_prefix : String) -> String {
  if old_prefix == "" {
    new_prefix + uri
  } else {
    match uri.strip_prefix(old_prefix) {
      Some(rest) => new_prefix + rest.to_owned()
      None => uri
    }
  }
}