use crate::SpikefitSample;
use crate::err::SpikefitError;
use crate::prominence::peak_prominences_impl;
use crate::widths::peak_widths_impl;
use num_traits::AsPrimitive;
pub(crate) fn find_peaks_impl<T: SpikefitSample>(
x: &[T],
height: Option<f64>,
threshold: Option<f64>,
distance: Option<usize>,
prominence: Option<f64>,
width: Option<f64>,
) -> Result<Vec<usize>, SpikefitError>
where
f64: AsPrimitive<T>,
usize: AsPrimitive<T>,
{
if x.len() < 3 {
return Err(SpikefitError::SignalTooShort { len: 3 }); }
let mut peak_indices = Vec::new();
for i in 1..x.len() - 1 {
if x[i] > x[i - 1] && x[i] > x[i + 1] {
peak_indices.push(i);
}
}
if x.len() >= 2 && x[x.len() - 1] > x[x.len() - 2] {
peak_indices.push(x.len() - 1);
}
if let Some(h) = height {
let h_f64 = h.as_();
peak_indices.retain(|&idx| x[idx] >= h_f64);
}
if let Some(th) = threshold {
let th_f64 = th.as_();
peak_indices.retain(|&idx| {
if idx == x.len() - 1 {
return idx > 0 && x[idx] - x[idx - 1] >= th_f64;
}
x[idx] - x[idx - 1] >= th_f64 && x[idx] - x[idx + 1] >= th_f64
});
}
if let Some(dist) = distance
&& dist > 0 {
let mut filtered_peaks = Vec::new();
let mut peaks_with_height: Vec<(usize, T)> =
peak_indices.iter().map(|&idx| (idx, x[idx])).collect();
peaks_with_height
.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let mut excluded = vec![false; x.len()];
for &(idx, _) in &peaks_with_height {
if !excluded[idx] {
filtered_peaks.push(idx);
let start = idx.saturating_sub(dist);
let end = (idx + dist + 1).min(x.len());
for (j, exclude) in excluded.iter_mut().enumerate().take(end).skip(start) {
if j != idx {
*exclude = true;
}
}
}
}
filtered_peaks.sort_unstable();
peak_indices = filtered_peaks;
}
if let Some(prom) = prominence {
let prom_f64 = prom.as_();
let prominences = peak_prominences_impl(x, &peak_indices)?;
let mut filtered_peaks = Vec::new();
for (&prominence, &idx) in prominences.iter().zip(peak_indices.iter()) {
if prominence >= prom_f64 {
filtered_peaks.push(idx);
}
}
peak_indices = filtered_peaks;
}
if let Some(w) = width {
let w_f64 = w.as_();
let widths = peak_widths_impl(x, &peak_indices, None)?;
let mut filtered_peaks = Vec::new();
for (&width, &idx) in widths.widths.iter().zip(peak_indices.iter()) {
if width >= w_f64 {
filtered_peaks.push(idx);
}
}
peak_indices = filtered_peaks;
}
Ok(peak_indices)
}