///|
/// Only finite samples participate in numeric aggregates.
pub(all) struct Summary {
  finite_count : Int32
  nan_count : Int32
  positive_infinity_count : Int32
  negative_infinity_count : Int32
  minimum : Double?
  maximum : Double?
  mean : Double?
} derive(Debug, Eq)

///|
pub fn Tensor::summary(self : Tensor) -> Summary {
  let mut finite_count = 0
  let mut nan_count = 0
  let mut positive_infinity_count = 0
  let mut negative_infinity_count = 0
  let mut minimum : Double? = None
  let mut maximum : Double? = None
  let mut mean = 0.0
  for value in self.data {
    if value.is_nan() {
      nan_count = nan_count + 1
    } else if value.is_pos_inf() {
      positive_infinity_count = positive_infinity_count + 1
    } else if value.is_neg_inf() {
      negative_infinity_count = negative_infinity_count + 1
    } else {
      finite_count = finite_count + 1
      // Weighted terms avoid overflow of an intermediate running sum.
      let n = finite_count.to_double()
      mean = mean * ((n - 1.0) / n) + value / n
      minimum = Some(
        match minimum {
          None => value
          Some(v) => v.min(value)
        },
      )
      maximum = Some(
        match maximum {
          None => value
          Some(v) => v.max(value)
        },
      )
    }
  }
  {
    finite_count,
    nan_count,
    positive_infinity_count,
    negative_infinity_count,
    minimum,
    maximum,
    mean: if finite_count == 0 {
      None
    } else {
      Some(mean)
    },
  }
}

///|
pub(all) struct Histogram {
  counts : Array[Int32]
  below : Int32
  above : Int32
  non_finite : Int32
} derive(Debug, Eq)

///|
/// Equal-width bins over [lower, upper], with the upper endpoint in the last bin.
pub fn Tensor::histogram(
  self : Tensor,
  lower : Double,
  upper : Double,
  bins : Int32,
) -> Histogram raise NrrdError {
  if !finite(lower) ||
    !finite(upper) ||
    lower >= upper ||
    !finite(upper - lower) {
    raise Invalid("histogram finite increasing range")
  }
  if bins < 1 || bins > 65536 {
    raise Limit("histogram bins must be 1..65536")
  }
  let counts = Array::make(bins, 0)
  let mut below = 0
  let mut above = 0
  let mut non_finite = 0
  for value in self.data {
    if !finite(value) {
      non_finite = non_finite + 1
    } else if value < lower {
      below = below + 1
    } else if value > upper {
      above = above + 1
    } else {
      let bin = ((value - lower) / (upper - lower) * bins.to_double())
        .to_int()
        .min(bins - 1)
      counts[bin] = counts[bin] + 1
    }
  }
  { counts, below, above, non_finite, }
}