///|
/// Accessible powder group; intensity SUMS members, multiplicity counts supplied reflections.
pub struct Peak {
  angle : Double
  intensity : Double
  members : Array[Hkl]
  width : Double
} derive(Debug)

///|
pub fn Peak::multiplicity(self : Peak) -> Int {
  self.members.length()
}

///| Stable grouping against first angle, not transitive chaining. Tolerance in degrees.

///|
/// Caller must supply one wavelength and each hkl once; duplicates and mixed wavelengths are rejected.
pub fn powder(
  reflections : Array[Reflection],
  tolerance? : Double = 1.0e-7,
) -> Result[Array[Peak], Problem] {
  if !finite(tolerance) || tolerance < 0.0 || tolerance > 1.0 {
    return Err(Invalid("group tolerance outside [0,1] degree"))
  }
  if reflections.length() > 100000 {
    return Err(Budget("powder list exceeds 100000"))
  }
  let seen : Map[String, Bool] = Map([])
  let ordered = []
  for r in reflections {
    if r.wavelength != reflections[0].wavelength {
      return Err(Invalid("mixed wavelengths in powder input"))
    }
    let key = "\{r.index.h},\{r.index.k},\{r.index.l}"
    if seen.contains(key) {
      return Err(Invalid("duplicate hkl in powder input"))
    }
    seen[key] = true
    match r.angle {
      Some(_) => ordered.push(r)
      None => ()
    }
  }
  ordered.sort_by((a, b) => {
    let angle = a.angle.unwrap().compare(b.angle.unwrap())
    if angle != 0 {
      angle
    } else if a.index.h != b.index.h {
      a.index.h.compare(b.index.h)
    } else if a.index.k != b.index.k {
      a.index.k.compare(b.index.k)
    } else {
      a.index.l.compare(b.index.l)
    }
  })
  let peaks = []
  let mut start = 0
  while start < ordered.length() {
    let anchor = ordered[start].angle.unwrap()
    let members = []
    let mut intensity = 0.0
    let mut end = start
    let mut last = anchor
    while end < ordered.length() &&
          ordered[end].angle.unwrap() - anchor <= tolerance {
      let r = ordered[end]
      members.push(r.index)
      intensity = intensity + r.intensity
      last = r.angle.unwrap()
      end = end + 1
    }
    peaks.push(Peak::{
      angle: anchor,
      intensity,
      members,
      width: last - anchor,
    })
    start = end
  }
  Ok(peaks)
}