///|
pub struct HarMetricDelta {
  name_value : String
  before_value : Double
  after_value : Double
  delta_value : Double
} derive(Eq, Debug)

///|
pub fn HarMetricDelta::name(self : HarMetricDelta) -> String {
  self.name_value
}

///|
pub fn HarMetricDelta::before(self : HarMetricDelta) -> Double {
  self.before_value
}

///|
pub fn HarMetricDelta::after(self : HarMetricDelta) -> Double {
  self.after_value
}

///|
pub fn HarMetricDelta::delta(self : HarMetricDelta) -> Double {
  self.delta_value
}

///|
pub fn HarMetricDelta::increased(self : HarMetricDelta) -> Bool {
  self.delta_value > 0.0
}

///|
pub fn HarMetricDelta::decreased(self : HarMetricDelta) -> Bool {
  self.delta_value < 0.0
}

///|
pub fn HarMetricDelta::percent_change(self : HarMetricDelta) -> Double? {
  if self.before_value == 0.0 {
    None
  } else {
    Some(self.delta_value / self.before_value * 100.0)
  }
}

///|
pub struct HarBucketDelta {
  label_value : String
  before_count_value : Int
  after_count_value : Int
  count_delta_value : Int
  before_bytes_value : Double
  after_bytes_value : Double
  bytes_delta_value : Double
  duration_delta_value : Double
} derive(Eq, Debug)

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

///|
pub fn HarBucketDelta::before_count(self : HarBucketDelta) -> Int {
  self.before_count_value
}

///|
pub fn HarBucketDelta::after_count(self : HarBucketDelta) -> Int {
  self.after_count_value
}

///|
pub fn HarBucketDelta::count_delta(self : HarBucketDelta) -> Int {
  self.count_delta_value
}

///|
pub fn HarBucketDelta::before_bytes(self : HarBucketDelta) -> Double {
  self.before_bytes_value
}

///|
pub fn HarBucketDelta::after_bytes(self : HarBucketDelta) -> Double {
  self.after_bytes_value
}

///|
pub fn HarBucketDelta::bytes_delta(self : HarBucketDelta) -> Double {
  self.bytes_delta_value
}

///|
pub fn HarBucketDelta::duration_delta(self : HarBucketDelta) -> Double {
  self.duration_delta_value
}

///|
pub struct HarComparison {
  metrics_value : Array[HarMetricDelta]
  hosts_value : Array[HarBucketDelta]
} derive(Eq, Debug)

///|
pub fn HarComparison::metrics(self : HarComparison) -> Array[HarMetricDelta] {
  self.metrics_value.copy()
}

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

///|
pub fn HarComparison::find_metric(
  self : HarComparison,
  name : String,
) -> HarMetricDelta? {
  for metric in self.metrics_value {
    if metric.name_value == name {
      return Some(metric)
    }
  }
  None
}

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

///|
fn metric_delta(
  name : String,
  before : Double,
  after : Double,
) -> HarMetricDelta {
  {
    name_value: name,
    before_value: before,
    after_value: after,
    delta_value: after - before,
  }
}

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

///|
fn bucket_or_empty(values : Array[HarBucket], label : String) -> HarBucket {
  for value in values {
    if value.label_value == label {
      return value
    }
  }
  { label_value: label, count_value: 0, bytes_value: 0.0, duration_value: 0.0 }
}

///|
fn compare_host_buckets(
  before : Array[HarBucket],
  after : Array[HarBucket],
) -> Array[HarBucketDelta] {
  let labels : Array[String] = []
  for item in before {
    if !contains_label(labels, item.label_value) {
      labels.push(item.label_value)
    }
  }
  for item in after {
    if !contains_label(labels, item.label_value) {
      labels.push(item.label_value)
    }
  }
  let result : Array[HarBucketDelta] = []
  for label in labels {
    let left = bucket_or_empty(before, label)
    let right = bucket_or_empty(after, label)
    result.push({
      label_value: label,
      before_count_value: left.count_value,
      after_count_value: right.count_value,
      count_delta_value: right.count_value - left.count_value,
      before_bytes_value: left.bytes_value,
      after_bytes_value: right.bytes_value,
      bytes_delta_value: right.bytes_value - left.bytes_value,
      duration_delta_value: right.duration_value - left.duration_value,
    })
  }
  result
}

///|
pub fn compare_har(before : HarDocument, after : HarDocument) -> HarComparison {
  let left = analyze_har(before)
  let right = analyze_har(after)
  {
    metrics_value: [
      metric_delta(
        "entries",
        left.entry_count_value.to_double(),
        right.entry_count_value.to_double(),
      ),
      metric_delta(
        "errors",
        left.error_count_value.to_double(),
        right.error_count_value.to_double(),
      ),
      metric_delta(
        "request_bytes",
        left.request_bytes_value,
        right.request_bytes_value,
      ),
      metric_delta(
        "response_bytes",
        left.response_bytes_value,
        right.response_bytes_value,
      ),
      metric_delta(
        "decoded_bytes",
        left.decoded_bytes_value,
        right.decoded_bytes_value,
      ),
      metric_delta("duration", left.duration_value, right.duration_value),
    ],
    hosts_value: compare_host_buckets(left.hosts_value, right.hosts_value),
  }
}