///|
/// Common scalar targets used by value-based and policy-gradient examples.
pub struct TargetBatch {
  values : Array[Double]
  mask : Array[Bool]
}

///|
pub fn TargetBatch::values(self : TargetBatch) -> Array[Double] {
  self.values
}

///|
pub fn TargetBatch::mask(self : TargetBatch) -> Array[Bool] {
  self.mask
}

///|
pub fn TargetBatch::length(self : TargetBatch) -> Int {
  self.values.length()
}

///|
pub fn discounted_sum(rewards : Array[Double], gamma : Double) -> Double {
  let mut result = 0.0
  let mut power = 1.0
  for reward in rewards {
    result = result + power * reward
    power = power * gamma
  }
  result
}

///|
pub fn discounted_prefixes(
  rewards : Array[Double],
  gamma : Double,
) -> Array[Double] {
  let result : Array[Double] = []
  let mut running = 0.0
  let mut i = rewards.length()
  while i > 0 {
    i = i - 1
    running = rewards[i] + gamma * running
    result.push(running)
  }
  let ordered : Array[Double] = []
  let mut j = result.length()
  while j > 0 {
    j = j - 1
    ordered.push(result[j])
  }
  ordered
}

///|
pub fn td_targets(
  rewards : Array[Double],
  next_values : Array[Double],
  terminals : Array[Bool],
  gamma : Double,
) -> Array[Double] {
  let result : Array[Double] = []
  let mut i = 0
  while i < rewards.length() {
    let bootstrap = if terminals[i] { 0.0 } else { gamma * next_values[i] }
    result.push(rewards[i] + bootstrap)
    i = i + 1
  }
  result
}

///|
pub fn generalized_advantages(
  rewards : Array[Double],
  values : Array[Double],
  next_values : Array[Double],
  terminals : Array[Bool],
  gamma : Double,
  lambda : Double,
) -> Array[Double] {
  let result : Array[Double] = []
  let mut running = 0.0
  let mut i = rewards.length()
  while i > 0 {
    i = i - 1
    let continuation = if terminals[i] { 0.0 } else { gamma * next_values[i] }
    let delta = rewards[i] + continuation - values[i]
    running = delta +
      (if terminals[i] { 0.0 } else { gamma * lambda * running })
    result.push(running)
  }
  let ordered : Array[Double] = []
  let mut j = result.length()
  while j > 0 {
    j = j - 1
    ordered.push(result[j])
  }
  ordered
}

///|
pub fn normalize_values(values : Array[Double]) -> Array[Double] {
  let result : Array[Double] = []
  if values.length() == 0 {
    return result
  }
  let mut sum = 0.0
  for value in values {
    sum = sum + value
  }
  let mean = sum / values.length().to_double()
  let mut squared = 0.0
  for value in values {
    let distance = value - mean
    squared = squared + distance * distance
  }
  let variance = squared / values.length().to_double()
  let scale = if variance <= 1.0e-12 { 1.0 } else { variance }
  for value in values {
    result.push((value - mean) / scale)
  }
  result
}

///|
pub fn clip_values(
  values : Array[Double],
  minimum : Double,
  maximum : Double,
) -> Array[Double] {
  let result : Array[Double] = []
  for value in values {
    if value < minimum {
      result.push(minimum)
    } else if value > maximum {
      result.push(maximum)
    } else {
      result.push(value)
    }
  }
  result
}

///|
pub fn scale_rewards(rewards : Array[Double], scale : Double) -> Array[Double] {
  let result : Array[Double] = []
  for reward in rewards {
    result.push(reward * scale)
  }
  result
}

///|
pub fn target_batches(
  values : Array[Double],
  batch_size : Int,
) -> Array[TargetBatch] {
  let result : Array[TargetBatch] = []
  if batch_size <= 0 {
    return result
  }
  let mut start = 0
  while start < values.length() {
    let batch : Array[Double] = []
    let mask : Array[Bool] = []
    let mut offset = 0
    while offset < batch_size {
      let index = start + offset
      if index < values.length() {
        batch.push(values[index])
        mask.push(true)
      } else {
        batch.push(0.0)
        mask.push(false)
      }
      offset = offset + 1
    }
    result.push({ values: batch, mask })
    start = start + batch_size
  }
  result
}