///|
/// Descriptive statistics for a replay buffer without exposing storage details.
pub struct ReplayReport {
  size : Int
  capacity : Int
  occupancy : Double
  terminal_count : Int
  total_reward : Double
  mean_reward : Double
  minimum_reward : Double
  maximum_reward : Double
}

///|
pub fn ReplayReport::size(self : ReplayReport) -> Int {
  self.size
}

///|
pub fn ReplayReport::capacity(self : ReplayReport) -> Int {
  self.capacity
}

///|
pub fn ReplayReport::occupancy(self : ReplayReport) -> Double {
  self.occupancy
}

///|
pub fn ReplayReport::terminal_count(self : ReplayReport) -> Int {
  self.terminal_count
}

///|
pub fn ReplayReport::total_reward(self : ReplayReport) -> Double {
  self.total_reward
}

///|
pub fn ReplayReport::mean_reward(self : ReplayReport) -> Double {
  self.mean_reward
}

///|
pub fn ReplayReport::minimum_reward(self : ReplayReport) -> Double {
  self.minimum_reward
}

///|
pub fn ReplayReport::maximum_reward(self : ReplayReport) -> Double {
  self.maximum_reward
}

///|
pub fn[S, A] ReplayBuffer::report(self : ReplayBuffer[S, A]) -> ReplayReport {
  let items = self.to_array()
  if items.length() == 0 {
    return {
      size: 0,
      capacity: self.capacity(),
      occupancy: 0.0,
      terminal_count: 0,
      total_reward: 0.0,
      mean_reward: 0.0,
      minimum_reward: 0.0,
      maximum_reward: 0.0,
    }
  }
  let mut total_reward = 0.0
  let mut minimum_reward = items[0].reward
  let mut maximum_reward = items[0].reward
  let mut terminal_count = 0
  for item in items {
    total_reward = total_reward + item.reward
    if item.reward < minimum_reward {
      minimum_reward = item.reward
    }
    if item.reward > maximum_reward {
      maximum_reward = item.reward
    }
    if item.done {
      terminal_count = terminal_count + 1
    }
  }
  let occupancy = if self.capacity() == 0 {
    0.0
  } else {
    self.len().to_double() / self.capacity().to_double()
  }
  {
    size: self.len(),
    capacity: self.capacity(),
    occupancy,
    terminal_count,
    total_reward,
    mean_reward: total_reward / items.length().to_double(),
    minimum_reward,
    maximum_reward,
  }
}

///|
pub fn[S, A] ReplayBuffer::terminal_indices(
  self : ReplayBuffer[S, A],
) -> Array[Int] {
  let result : Array[Int] = []
  let mut i = 0
  for item in self.to_array() {
    if item.done {
      result.push(i)
    }
    i = i + 1
  }
  result
}

///|
pub fn[S, A] ReplayBuffer::reward_histogram(
  self : ReplayBuffer[S, A],
  lower : Double,
  upper : Double,
  buckets : Int,
) -> Array[Int] {
  let result : Array[Int] = []
  if buckets <= 0 {
    return result
  }
  let mut i = 0
  while i < buckets {
    result.push(0)
    i = i + 1
  }
  if upper <= lower {
    return result
  }
  let width = (upper - lower) / buckets.to_double()
  for item in self.to_array() {
    let raw = ((item.reward - lower) / width).to_int()
    let index = if raw < 0 {
      0
    } else if raw >= buckets {
      buckets - 1
    } else {
      raw
    }
    result[index] = result[index] + 1
  }
  result
}

///|
pub fn[S, A] ReplayBuffer::sample_all(
  self : ReplayBuffer[S, A],
) -> Array[ReplaySample[S, A]] {
  let result : Array[ReplaySample[S, A]] = []
  let mut i = 0
  while i < self.len() {
    match self.get(i) {
      Some(transition) => result.push({ index: i, transition })
      None => ()
    }
    i = i + 1
  }
  result
}

///|
pub fn[S, A] ReplayBuffer::count_reward_at_least(
  self : ReplayBuffer[S, A],
  threshold : Double,
) -> Int {
  let mut count = 0
  for item in self.to_array() {
    if item.reward >= threshold {
      count = count + 1
    }
  }
  count
}

///|
pub fn[S, A] ReplayBuffer::count_reward_below(
  self : ReplayBuffer[S, A],
  threshold : Double,
) -> Int {
  let mut count = 0
  for item in self.to_array() {
    if item.reward < threshold {
      count = count + 1
    }
  }
  count
}

///|
pub fn[S, A] PrioritizedReplayBuffer::effective_priority_sum(
  self : PrioritizedReplayBuffer[S, A],
) -> Double {
  let mut total = 0.0
  let mut i = 0
  while i < self.len() {
    match self.get(i) {
      Some((_, priority)) => total = total + normalize_positive(priority)
      None => ()
    }
    i = i + 1
  }
  total
}

///|
pub fn[S, A] PrioritizedReplayBuffer::priority_at(
  self : PrioritizedReplayBuffer[S, A],
  index : Int,
) -> Double? {
  match self.get(index) {
    Some((_, priority)) => Some(priority)
    None => None
  }
}

///|
pub fn[S, A] PrioritizedReplayBuffer::priority_distribution(
  self : PrioritizedReplayBuffer[S, A],
) -> Array[Double] {
  let result : Array[Double] = []
  let total = self.priority_sum()
  let mut i = 0
  while i < self.len() {
    match self.get(i) {
      Some((_, priority)) =>
        result.push(if total == 0.0 { 0.0 } else { priority / total })
      None => result.push(0.0)
    }
    i = i + 1
  }
  result
}

///|
pub fn[S, A] PrioritizedReplayBuffer::count_priority_at_least(
  self : PrioritizedReplayBuffer[S, A],
  threshold : Double,
) -> Int {
  let mut count = 0
  let mut i = 0
  while i < self.len() {
    match self.priority_at(i) {
      Some(priority) => if priority >= threshold { count = count + 1 }
      None => ()
    }
    i = i + 1
  }
  count
}