///|
pub struct HarFilter {
  host_value : String?
  minimum_status_value : Double?
  maximum_status_value : Double?
  minimum_duration_value : Double?
  maximum_duration_value : Double?
  page_ref_value : String?
  secure_only_value : Bool
  methods_value : Array[String]
  content_types_value : Array[String]
} derive(Eq, Debug)

///|
pub fn HarFilter::all() -> HarFilter {
  {
    host_value: None,
    minimum_status_value: None,
    maximum_status_value: None,
    minimum_duration_value: None,
    maximum_duration_value: None,
    page_ref_value: None,
    secure_only_value: false,
    methods_value: [],
    content_types_value: [],
  }
}

///|
pub fn HarFilter::with_host(self : HarFilter, host : String) -> HarFilter {
  { ..self, host_value: Some(host.to_lower()) }
}

///|
pub fn HarFilter::with_status_range(
  self : HarFilter,
  minimum : Double,
  maximum : Double,
) -> Result[HarFilter, HarError] {
  if minimum < 0.0 || maximum > 999.0 || minimum > maximum {
    return Err(
      HarError::new(
        InvalidField,
        "$.filter.status",
        "status range must be ordered within 0 through 999",
      ),
    )
  }
  Ok({
    ..self,
    minimum_status_value: Some(minimum),
    maximum_status_value: Some(maximum),
  })
}

///|
pub fn HarFilter::with_duration_range(
  self : HarFilter,
  minimum : Double,
  maximum : Double,
) -> Result[HarFilter, HarError] {
  if minimum < 0.0 || maximum < minimum {
    return Err(
      HarError::new(
        InvalidField,
        "$.filter.duration",
        "duration range must be ordered and non-negative",
      ),
    )
  }
  Ok({
    ..self,
    minimum_duration_value: Some(minimum),
    maximum_duration_value: Some(maximum),
  })
}

///|
pub fn HarFilter::with_page(self : HarFilter, page_ref : String) -> HarFilter {
  { ..self, page_ref_value: Some(page_ref) }
}

///|
pub fn HarFilter::with_secure_only(self : HarFilter) -> HarFilter {
  { ..self, secure_only_value: true }
}

///|
pub fn HarFilter::with_method(self : HarFilter, verb : String) -> HarFilter {
  let methods = self.methods_value.copy()
  methods.push(verb.to_upper())
  { ..self, methods_value: methods }
}

///|
pub fn HarFilter::with_content_type(
  self : HarFilter,
  content_type : String,
) -> HarFilter {
  let types = self.content_types_value.copy()
  types.push(content_type.to_lower())
  { ..self, content_types_value: types }
}

///|
pub fn HarFilter::host(self : HarFilter) -> String? {
  self.host_value
}

///|
pub fn HarFilter::methods(self : HarFilter) -> Array[String] {
  self.methods_value.copy()
}

///|
pub fn HarFilter::content_types(self : HarFilter) -> Array[String] {
  self.content_types_value.copy()
}

///|
fn string_array_contains(values : Array[String], expected : String) -> Bool {
  for value in values {
    if value == expected {
      return true
    }
  }
  false
}

///|
pub fn HarFilter::matches(self : HarFilter, entry : HarEntry) -> Bool {
  match self.host_value {
    Some(expected) =>
      if url_host(entry.request_value.url_value) != expected {
        return false
      }
    None => ()
  }
  let status = entry.response_value.status_value
  match self.minimum_status_value {
    Some(minimum) => if status < minimum { return false }
    None => ()
  }
  match self.maximum_status_value {
    Some(maximum) => if status > maximum { return false }
    None => ()
  }
  match self.minimum_duration_value {
    Some(minimum) => if entry.time_value < minimum { return false }
    None => ()
  }
  match self.maximum_duration_value {
    Some(maximum) => if entry.time_value > maximum { return false }
    None => ()
  }
  match self.page_ref_value {
    Some(expected) => if !entry.is_page_entry(expected) { return false }
    None => ()
  }
  if self.secure_only_value && !entry.request_value.is_secure() {
    return false
  }
  if !self.methods_value.is_empty() &&
    !string_array_contains(
      self.methods_value,
      entry.request_value.method_value.to_upper(),
    ) {
    return false
  }
  if !self.content_types_value.is_empty() &&
    !string_array_contains(
      self.content_types_value,
      entry.response_value.content_value.type_group().to_lower(),
    ) {
    return false
  }
  true
}

///|
pub fn select_entries(
  document : HarDocument,
  filter : HarFilter,
) -> Array[HarEntry] {
  let result : Array[HarEntry] = []
  for entry in document.entries_value {
    if filter.matches(entry) {
      result.push(entry)
    }
  }
  result
}