///|
/// One analysis band in a configurable filter bank.
pub struct FilterBankBand {
  name : String
  low_frequency : Double
  high_frequency : Double
  filter : FirFilter?
}

///|
/// Configuration for a multi-band seismic filter bank.
pub struct FilterBankConfig {
  sample_rate : Double
  bands : Array[(String, Double, Double)]
  taps : Int
  window : FirWindow
}

///|
/// A bank of filtered trace outputs.
pub struct FilterBankResult {
  station : String
  channel : String
  bands : Array[FilterBankBand]
  traces : Array[Trace]
}

///|
/// Build standard monitoring bands from a Nyquist frequency.
pub fn standard_filter_bank(sample_rate : Double) -> FilterBankConfig {
  let nyquist = sample_rate / 2.0
  let high = nyquist.max(0.2)
  {
    sample_rate,
    bands: [
      ("low", 0.1.min(high * 0.5), high * 0.25),
      ("mid", high * 0.2, high * 0.55),
      ("high", high * 0.5, high * 0.9),
    ],
    taps: 81,
    window: fir_hann(),
  }
}

///|
/// Design all bands in a filter-bank configuration.
pub fn design_filter_bank(
  config : FilterBankConfig,
) -> Result[Array[FilterBankBand], String] {
  let result : Array[FilterBankBand] = Array::new()
  for band in config.bands {
    let filter = if band.1 <= 0.0 {
      design_fir_lowpass(config.sample_rate, band.2, config.taps, config.window)
    } else {
      design_fir_bandpass(
        config.sample_rate,
        band.1,
        band.2,
        config.taps,
        config.window,
      )
    }
    match filter {
      Ok(value) =>
        result.push({
          name: band.0,
          low_frequency: band.1,
          high_frequency: band.2,
          filter: Some(value),
        })
      Err(message) => return Err(message)
    }
  }
  Ok(result)
}

///|
/// Apply a designed filter bank to a trace.
pub fn apply_filter_bank(
  trace : Trace,
  bands : Array[FilterBankBand],
) -> FilterBankResult {
  let traces : Array[Trace] = Array::new()
  for band in bands {
    match band.filter {
      None => ()
      Some(filter) =>
        traces.push(
          trace_with_data(trace, convolve_same(trace.data, filter.coefficients)),
        )
    }
  }
  {
    station: trace.station,
    channel: trace.channel,
    bands: bands.copy(),
    traces,
  }
}

///|
/// Compute RMS values for all filter-bank outputs.
pub fn filter_bank_rms(result : FilterBankResult) -> Array[Double] {
  result.traces.map(trace => rms_samples(trace.data))
}

///|
/// Return the strongest bank band by RMS amplitude.
pub fn strongest_filter_band(result : FilterBankResult) -> (String, Double)? {
  let rms = filter_bank_rms(result)
  if rms.is_empty() {
    return None
  }
  let mut best = 0
  for index = 1; index < rms.length(); index = index + 1 {
    if rms[index] > rms[best] {
      best = index
    }
  }
  if best >= result.bands.length() {
    None
  } else {
    Some((result.bands[best].name, rms[best]))
  }
}

///|
/// Export filter-bank measurements as a CSV table.
pub fn filter_bank_to_csv(result : FilterBankResult) -> String {
  let rms = filter_bank_rms(result)
  let mut output = "station,channel,band,low_hz,high_hz,rms\n"
  for index = 0
      index < result.bands.length() && index < rms.length()
      index = index + 1 {
    let band = result.bands[index]
    output = output +
      result.station +
      "," +
      result.channel +
      "," +
      band.name +
      "," +
      band.low_frequency.to_string() +
      "," +
      band.high_frequency.to_string() +
      "," +
      rms[index].to_string() +
      "\n"
  }
  output
}

///|
/// Format filter-bank configuration.
pub fn filter_bank_config_text(config : FilterBankConfig) -> String {
  "sample_rate=" +
  config.sample_rate.to_string() +
  " bands=" +
  config.bands.length().to_string() +
  " taps=" +
  config.taps.to_string()
}