///|
pub struct HarBucket {
  label_value : String
  count_value : Int
  bytes_value : Double
  duration_value : Double
} derive(Eq, Debug)

///|
pub fn HarBucket::label(self : HarBucket) -> String {
  self.label_value
}

///|
pub fn HarBucket::count(self : HarBucket) -> Int {
  self.count_value
}

///|
pub fn HarBucket::bytes(self : HarBucket) -> Double {
  self.bytes_value
}

///|
pub fn HarBucket::duration(self : HarBucket) -> Double {
  self.duration_value
}

///|
pub fn HarBucket::average_duration(self : HarBucket) -> Double {
  if self.count_value == 0 {
    0.0
  } else {
    self.duration_value / self.count_value.to_double()
  }
}

///|
priv struct MutableBucket {
  label_value : String
  mut count_value : Int
  mut bytes_value : Double
  mut duration_value : Double
}

///|
fn add_bucket(
  buckets : Array[MutableBucket],
  label : String,
  bytes : Double,
  duration : Double,
) -> Unit {
  for bucket in buckets {
    if bucket.label_value == label {
      bucket.count_value += 1
      bucket.bytes_value += bytes
      bucket.duration_value += duration
      return
    }
  }
  buckets.push({
    label_value: label,
    count_value: 1,
    bytes_value: bytes,
    duration_value: duration,
  })
}

///|
fn freeze_buckets(buckets : Array[MutableBucket]) -> Array[HarBucket] {
  let result : Array[HarBucket] = []
  for bucket in buckets {
    result.push({
      label_value: bucket.label_value,
      count_value: bucket.count_value,
      bytes_value: bucket.bytes_value,
      duration_value: bucket.duration_value,
    })
  }
  result
}

///|
fn owned_parts(value : String, separator : String) -> Array[String] {
  value.split(separator).map(part => part.to_owned()).collect()
}

///|
fn last_string(values : Array[String]) -> String {
  if values.is_empty() {
    ""
  } else {
    values[values.length() - 1]
  }
}

///|
pub fn url_host(url : String) -> String {
  let scheme_parts = owned_parts(url, "://")
  let remainder = if scheme_parts.length() >= 2 {
    scheme_parts[1]
  } else {
    scheme_parts[0]
  }
  let authority_parts = owned_parts(remainder, "/")
  let authority = if authority_parts.is_empty() {
    ""
  } else {
    authority_parts[0]
  }
  let user_parts = owned_parts(authority, "@")
  let host_port = last_string(user_parts)
  if host_port.has_prefix("[") {
    let bracket_parts = owned_parts(host_port, "]")
    if bracket_parts.is_empty() {
      host_port
    } else {
      bracket_parts[0] + "]"
    }
  } else {
    let colon_parts = owned_parts(host_port, ":")
    if colon_parts.is_empty() {
      ""
    } else {
      colon_parts[0].to_lower()
    }
  }
}

///|
pub fn url_scheme(url : String) -> String {
  let parts = owned_parts(url, "://")
  if parts.length() >= 2 {
    parts[0].to_lower()
  } else {
    ""
  }
}

///|
pub struct HarStatistics {
  entry_count_value : Int
  page_count_value : Int
  successful_count_value : Int
  redirect_count_value : Int
  error_count_value : Int
  secure_count_value : Int
  cached_count_value : Int
  unknown_body_size_count_value : Int
  request_bytes_value : Double
  response_bytes_value : Double
  decoded_bytes_value : Double
  duration_value : Double
  hosts_value : Array[HarBucket]
  status_classes_value : Array[HarBucket]
  content_types_value : Array[HarBucket]
} derive(Eq, Debug)

///|
pub fn HarStatistics::entry_count(self : HarStatistics) -> Int {
  self.entry_count_value
}

///|
pub fn HarStatistics::page_count(self : HarStatistics) -> Int {
  self.page_count_value
}

///|
pub fn HarStatistics::successful_count(self : HarStatistics) -> Int {
  self.successful_count_value
}

///|
pub fn HarStatistics::redirect_count(self : HarStatistics) -> Int {
  self.redirect_count_value
}

///|
pub fn HarStatistics::error_count(self : HarStatistics) -> Int {
  self.error_count_value
}

///|
pub fn HarStatistics::secure_count(self : HarStatistics) -> Int {
  self.secure_count_value
}

///|
pub fn HarStatistics::cached_count(self : HarStatistics) -> Int {
  self.cached_count_value
}

///|
pub fn HarStatistics::unknown_body_size_count(self : HarStatistics) -> Int {
  self.unknown_body_size_count_value
}

///|
pub fn HarStatistics::request_bytes(self : HarStatistics) -> Double {
  self.request_bytes_value
}

///|
pub fn HarStatistics::response_bytes(self : HarStatistics) -> Double {
  self.response_bytes_value
}

///|
pub fn HarStatistics::decoded_bytes(self : HarStatistics) -> Double {
  self.decoded_bytes_value
}

///|
pub fn HarStatistics::duration(self : HarStatistics) -> Double {
  self.duration_value
}

///|
pub fn HarStatistics::hosts(self : HarStatistics) -> Array[HarBucket] {
  self.hosts_value.copy()
}

///|
pub fn HarStatistics::status_classes(self : HarStatistics) -> Array[HarBucket] {
  self.status_classes_value.copy()
}

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

///|
pub fn HarStatistics::average_duration(self : HarStatistics) -> Double {
  if self.entry_count_value == 0 {
    0.0
  } else {
    self.duration_value / self.entry_count_value.to_double()
  }
}

///|
pub fn HarStatistics::cache_ratio(self : HarStatistics) -> Double {
  if self.entry_count_value == 0 {
    0.0
  } else {
    self.cached_count_value.to_double() / self.entry_count_value.to_double()
  }
}

///|
pub fn HarStatistics::secure_ratio(self : HarStatistics) -> Double {
  if self.entry_count_value == 0 {
    0.0
  } else {
    self.secure_count_value.to_double() / self.entry_count_value.to_double()
  }
}

///|
pub fn HarStatistics::find_host(
  self : HarStatistics,
  host : String,
) -> HarBucket? {
  for bucket in self.hosts_value {
    if bucket.label_value == host.to_lower() {
      return Some(bucket)
    }
  }
  None
}

///|
pub fn HarStatistics::find_status_class(
  self : HarStatistics,
  status_class : String,
) -> HarBucket? {
  for bucket in self.status_classes_value {
    if bucket.label_value == status_class {
      return Some(bucket)
    }
  }
  None
}

///|
pub fn HarStatistics::find_content_type(
  self : HarStatistics,
  content_type : String,
) -> HarBucket? {
  for bucket in self.content_types_value {
    if bucket.label_value == content_type {
      return Some(bucket)
    }
  }
  None
}

///|
pub fn analyze_har(document : HarDocument) -> HarStatistics {
  let mut successful = 0
  let mut redirects = 0
  let mut errors = 0
  let mut secure = 0
  let mut cached = 0
  let mut unknown_body = 0
  let mut request_bytes = 0.0
  let mut response_bytes = 0.0
  let mut decoded_bytes = 0.0
  let mut duration = 0.0
  let hosts : Array[MutableBucket] = []
  let statuses : Array[MutableBucket] = []
  let content_types : Array[MutableBucket] = []
  for entry in document.entries_value {
    let request = entry.request_value
    let response = entry.response_value
    if response.is_success() {
      successful += 1
    } else if response.is_redirect() {
      redirects += 1
    } else if response.is_error() {
      errors += 1
    }
    if request.is_secure() {
      secure += 1
    }
    if entry.cache_value.was_cached() {
      cached += 1
    }
    match request.known_headers_size() {
      Some(value) => request_bytes += value
      None => ()
    }
    match request.known_body_size() {
      Some(value) => request_bytes += value
      None => ()
    }
    let transferred = match response.known_body_size() {
      Some(value) => {
        response_bytes += value
        value
      }
      None => {
        unknown_body += 1
        0.0
      }
    }
    match response.known_headers_size() {
      Some(value) => response_bytes += value
      None => ()
    }
    match response.content_value.known_size() {
      Some(value) => decoded_bytes += value
      None => ()
    }
    duration += entry.time_value
    let host = url_host(request.url_value)
    add_bucket(
      hosts,
      if host == "" {
        "(unknown)"
      } else {
        host
      },
      transferred,
      entry.time_value,
    )
    add_bucket(statuses, response.status_class(), transferred, entry.time_value)
    add_bucket(
      content_types,
      response.content_value.type_group(),
      transferred,
      entry.time_value,
    )
  }
  {
    entry_count_value: document.entry_count(),
    page_count_value: document.page_count(),
    successful_count_value: successful,
    redirect_count_value: redirects,
    error_count_value: errors,
    secure_count_value: secure,
    cached_count_value: cached,
    unknown_body_size_count_value: unknown_body,
    request_bytes_value: request_bytes,
    response_bytes_value: response_bytes,
    decoded_bytes_value: decoded_bytes,
    duration_value: duration,
    hosts_value: freeze_buckets(hosts),
    status_classes_value: freeze_buckets(statuses),
    content_types_value: freeze_buckets(content_types),
  }
}