///|
pub struct Replacement {
  values : Array[Double]
  count : Int
  mass : Double
}

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

///|
pub fn Replacement::zero_count(self : Replacement) -> Int {
  self.count
}

///|
pub fn Replacement::replaced_mass(self : Replacement) -> Double {
  self.mass
}

///|
/// Replace each zero by delta after closure, scaling nonzero parts by 1-z*delta.
/// Delta is a modeling choice, not an estimate of a structural zero.
pub fn multiplicative_replace(
  x : Array[Double],
  delta? : Double = 0.001,
) -> Replacement raise CodaError {
  let c = closure(x)
  if !finite(delta) || delta <= 0.0 || delta >= 1.0 {
    raise Invalid("replacement delta must be in (0,1)")
  }
  let mut count = 0
  for v in c {
    if v == 0.0 {
      count += 1
    }
  }
  let mass = count.to_double() * delta
  if mass >= 1.0 {
    raise Invalid("replacement consumes all mass")
  }
  let values = c.map(v => if v == 0.0 { delta } else { v * (1.0 - mass) })
  positive(values)
  { values, count, mass, }
}

///|
/// Add an explicit pseudocount in the original count unit, then close.
pub fn add_pseudocount(
  x : Array[Double],
  amount : Double,
) -> Array[Double] raise CodaError {
  vector_size(x.length())
  real_vector(x)
  if !finite(amount) || amount <= 0.0 {
    raise Invalid("pseudocount must be finite and positive")
  }
  for v in x {
    if v < 0.0 {
      raise Invalid("negative count")
    }
  }
  closure(x.map(v => checked(v + amount)))
}