///|
pub(all) struct RequestEvent {
  minute : Int
  succeeded : Bool
  latency_ms : Int
} derive(Eq, Debug)

///|
pub fn RequestEvent::new(
  minute : Int,
  succeeded : Bool,
  latency_ms : Int,
) -> RequestEvent {
  {
    minute,
    succeeded,
    latency_ms: if latency_ms >= 0 {
      latency_ms
    } else {
      0
    },
  }
}

///|
pub fn RequestEvent::failed(self : RequestEvent) -> Bool {
  !self.succeeded
}

///|
pub fn RequestEvent::latency_band(
  self : RequestEvent,
  slow_after_ms : Int,
) -> String {
  if self.latency_ms >= slow_after_ms {
    "slow"
  } else {
    "normal"
  }
}

///|
pub(all) struct LatencySummary {
  count : Int
  average_ms : Int
  min_ms : Int
  max_ms : Int
  slow_count : Int
} derive(Eq, Debug)

///|
pub fn LatencySummary::empty() -> LatencySummary {
  { count: 0, average_ms: 0, min_ms: 0, max_ms: 0, slow_count: 0 }
}

///|
pub fn LatencySummary::healthy(self : LatencySummary, budget_ms : Int) -> Bool {
  self.count == 0 || self.average_ms <= budget_ms
}

///|
pub fn LatencySummary::to_json(self : LatencySummary) -> String {
  "{\"count\":\{self.count},\"average_ms\":\{self.average_ms},\"min_ms\":\{self.min_ms},\"max_ms\":\{self.max_ms},\"slow_count\":\{self.slow_count}}"
}

///|
pub(all) struct RequestBatch {
  name : String
  events : Array[RequestEvent]
} derive(Eq, Debug)

///|
pub fn RequestBatch::new(name : String) -> RequestBatch {
  { name, events: [] }
}

///|
pub fn RequestBatch::add(
  self : RequestBatch,
  event : RequestEvent,
) -> RequestBatch {
  self.events.push(event)
  self
}

///|
pub fn RequestBatch::total(self : RequestBatch) -> Int {
  self.events.length()
}

///|
pub fn RequestBatch::succeeded(self : RequestBatch) -> Int {
  let mut count = 0
  for event in self.events {
    if event.succeeded {
      count = count + 1
    }
  }
  count
}

///|
pub fn RequestBatch::failed(self : RequestBatch) -> Int {
  self.total() - self.succeeded()
}

///|
pub fn RequestBatch::minutes(self : RequestBatch) -> Int {
  let mut first = 0
  let mut last = 0
  let mut initialized = false
  for event in self.events {
    if !initialized {
      first = event.minute
      last = event.minute
      initialized = true
    } else {
      if event.minute < first {
        first = event.minute
      }
      if event.minute > last {
        last = event.minute
      }
    }
  }
  if initialized {
    last - first + 1
  } else {
    0
  }
}

///|
pub fn RequestBatch::slow_count(
  self : RequestBatch,
  slow_after_ms : Int,
) -> Int {
  let mut count = 0
  for event in self.events {
    if event.latency_ms >= slow_after_ms {
      count = count + 1
    }
  }
  count
}

///|
pub fn RequestBatch::latency(
  self : RequestBatch,
  slow_after_ms : Int,
) -> LatencySummary {
  if self.events.length() == 0 {
    LatencySummary::empty()
  } else {
    let mut total = 0
    let mut min_ms = self.events[0].latency_ms
    let mut max_ms = self.events[0].latency_ms
    for event in self.events {
      total = total + event.latency_ms
      if event.latency_ms < min_ms {
        min_ms = event.latency_ms
      }
      if event.latency_ms > max_ms {
        max_ms = event.latency_ms
      }
    }
    {
      count: self.events.length(),
      average_ms: total / self.events.length(),
      min_ms,
      max_ms,
      slow_count: self.slow_count(slow_after_ms),
    }
  }
}

///|
pub fn RequestBatch::to_window(
  self : RequestBatch,
  minutes : Int,
) -> RequestWindow {
  RequestWindow::new(self.name, minutes, self.total(), self.succeeded())
}

///|
pub fn RequestBatch::to_series(
  self : RequestBatch,
  window_minutes : Int,
) -> ObservationSeries {
  let series = ObservationSeries::new(self.name, window_minutes)
  for event in self.events {
    let good = if event.succeeded { 1 } else { 0 }
    let _ = series.add(TrafficSample::new(event.minute, 1, good))
  }
  series
}

///|
pub fn RequestBatch::to_json(self : RequestBatch) -> String {
  let result = StringBuilder()
  result.write_string("{\"name\":\"\{escape_json(self.name)}\",\"events\":[")
  for i, event in self.events {
    if i > 0 {
      result.write_string(",")
    }
    result.write_string(
      "{\"minute\":\{event.minute},\"succeeded\":\{event.succeeded},\"latency_ms\":\{event.latency_ms}}",
    )
  }
  result.write_string("]}")
  result.to_string()
}

///|
pub fn merge_batches(left : RequestBatch, right : RequestBatch) -> RequestBatch {
  let result = RequestBatch::new(left.name)
  for event in left.events {
    let _ = result.add(event)
  }
  for event in right.events {
    let _ = result.add(event)
  }
  result
}

///|
pub fn batch_availability_bp(batch : RequestBatch) -> Int {
  if batch.total() == 0 {
    10000
  } else {
    batch.succeeded() * 10000 / batch.total()
  }
}

///|
pub fn batch_error_rate_bp(batch : RequestBatch) -> Int {
  10000 - batch_availability_bp(batch)
}