///|
/// Request filter used by query helpers.
pub(all) struct EntryFilter {
  domain : String?
  http_method : String?
  status_min : Int?
  status_max : Int?
  resource : String?
  slow_ms : Int?
  url_contains : String?
  failed_only : Bool
} derive(Eq, Debug)

///|
pub fn EntryFilter::new() -> EntryFilter {
  EntryFilter::{
    domain: None,
    http_method: None,
    status_min: None,
    status_max: None,
    resource: None,
    slow_ms: None,
    url_contains: None,
    failed_only: false,
  }
}

///|
pub fn EntryFilter::for_domain(domain : String) -> EntryFilter {
  let f = EntryFilter::new()
  EntryFilter::{ ..f, domain: Some(domain) }
}

///|
pub fn EntryFilter::for_method(http_method : String) -> EntryFilter {
  let f = EntryFilter::new()
  EntryFilter::{ ..f, http_method: Some(http_method) }
}

///|
pub fn EntryFilter::for_resource(kind : String) -> EntryFilter {
  let f = EntryFilter::new()
  EntryFilter::{ ..f, resource: Some(kind) }
}

///|
pub fn EntryFilter::slow(ms : Int) -> EntryFilter {
  let f = EntryFilter::new()
  EntryFilter::{ ..f, slow_ms: Some(ms) }
}

///|
pub fn EntryFilter::failed() -> EntryFilter {
  let f = EntryFilter::new()
  EntryFilter::{ ..f, failed_only: true }
}

///|
pub fn filter_entries(
  archive : HarArchive,
  filter : EntryFilter,
) -> Array[HarEntry] {
  let out : Array[HarEntry] = []
  for i = 0; i < archive.log.entries.length(); i = i + 1 {
    let entry = archive.log.entries[i]
    if entry_matches(entry, filter) {
      out.push(entry)
    }
  }
  out
}

///|
pub fn entry_matches(entry : HarEntry, filter : EntryFilter) -> Bool {
  if filter.failed_only && entry.response.status < 400 {
    return false
  }
  match filter.domain {
    Some(domain) =>
      if extract_domain(entry.request.url) != domain {
        return false
      }
    None => ()
  }
  match filter.http_method {
    Some(http_method) =>
      if entry.request.http_method != http_method {
        return false
      }
    None => ()
  }
  match filter.status_min {
    Some(min) => if entry.response.status < min { return false }
    None => ()
  }
  match filter.status_max {
    Some(max) => if entry.response.status > max { return false }
    None => ()
  }
  match filter.resource {
    Some(kind) =>
      if resource_kind(entry.response.content.mime_type, entry.request.url) !=
        kind {
        return false
      }
    None => ()
  }
  match filter.slow_ms {
    Some(ms) => if entry.time < ms { return false }
    None => ()
  }
  match filter.url_contains {
    Some(part) => if !entry.request.url.contains(part) { return false }
    None => ()
  }
  true
}

///|
pub fn count_failed(entries : Array[HarEntry]) -> Int {
  entries.count_if(e => e.response.status >= 400)
}

///|
pub fn count_redirects(entries : Array[HarEntry]) -> Int {
  entries.count_if(e => e.response.status >= 300 && e.response.status < 400)
}

///|
pub fn count_method(entries : Array[HarEntry], http_method : String) -> Int {
  entries.count_if(e => e.request.http_method == http_method)
}

///|
pub fn count_resource(entries : Array[HarEntry], kind : String) -> Int {
  entries.count_if(e => {
    resource_kind(e.response.content.mime_type, e.request.url) == kind
  })
}

///|
pub fn total_entry_time(entries : Array[HarEntry]) -> Int {
  let mut total = 0
  for i = 0; i < entries.length(); i = i + 1 {
    total = total + entries[i].time
  }
  total
}

///|
pub fn total_entry_bytes(entries : Array[HarEntry]) -> Int {
  let mut total = 0
  for i = 0; i < entries.length(); i = i + 1 {
    total = total + entry_bytes(entries[i])
  }
  total
}

///|
pub fn fastest_entry(entries : Array[HarEntry]) -> HarEntry? {
  if entries.is_empty() {
    return None
  }
  let mut best = entries[0]
  for i = 1; i < entries.length(); i = i + 1 {
    if entries[i].time < best.time {
      best = entries[i]
    }
  }
  Some(best)
}

///|
pub fn slowest_entry(entries : Array[HarEntry]) -> HarEntry? {
  if entries.is_empty() {
    return None
  }
  let mut best = entries[0]
  for i = 1; i < entries.length(); i = i + 1 {
    if entries[i].time > best.time {
      best = entries[i]
    }
  }
  Some(best)
}

///|
pub fn entries_over_time(
  entries : Array[HarEntry],
  ms : Int,
) -> Array[HarEntry] {
  let out : Array[HarEntry] = []
  for i = 0; i < entries.length(); i = i + 1 {
    if entries[i].time >= ms {
      out.push(entries[i])
    }
  }
  out
}

///|
pub fn entries_for_status_group(
  entries : Array[HarEntry],
  group : String,
) -> Array[HarEntry] {
  let out : Array[HarEntry] = []
  for i = 0; i < entries.length(); i = i + 1 {
    if status_group(entries[i].response.status) == group {
      out.push(entries[i])
    }
  }
  out
}

///|
pub fn entries_for_domain(
  entries : Array[HarEntry],
  domain : String,
) -> Array[HarEntry] {
  let out : Array[HarEntry] = []
  for i = 0; i < entries.length(); i = i + 1 {
    if extract_domain(entries[i].request.url) == domain {
      out.push(entries[i])
    }
  }
  out
}

///|
pub fn entries_with_header(
  entries : Array[HarEntry],
  name : String,
) -> Array[HarEntry] {
  let lower = name.to_lower()
  let out : Array[HarEntry] = []
  for i = 0; i < entries.length(); i = i + 1 {
    if has_header(entries[i], lower) {
      out.push(entries[i])
    }
  }
  out
}

///|
fn has_header(entry : HarEntry, lower : String) -> Bool {
  entry.request.headers.any(h => h.name.to_lower() == lower) ||
  entry.response.headers.any(h => h.name.to_lower() == lower)
}