///|
pub(all) struct SpectrumBin {
  mode : Int
  frequency : Double
  real : Double
  imaginary : Double
  power : Double
} derive(Debug, ToJson)

///|
pub fn discrete_spectrum(signal : ArrayView[Double]) -> Array[SpectrumBin] {
  let n = signal.length()
  let output : Array[SpectrumBin] = []
  for mode in 0.. Int {
  if spectrum.length() == 0 {
    0
  } else {
    let mut index = 0
    let mut power = spectrum[0].power
    let upper = spectrum.length() / 2 + 1
    for i in 1.. power {
        power = spectrum[i].power
        index = i
      }
    }
    index
  }
}

///|
pub fn power_spectrum(signal : ArrayView[Double]) -> Array[Double] {
  discrete_spectrum(signal).map(fn(bin) { bin.power })
}

///|
pub fn autocorrelation(signal : ArrayView[Double], lag : Int) -> Double {
  if signal.length() == 0 || lag < 0 || lag >= signal.length() {
    0.0
  } else {
    let mut total = 0.0
    let mut count = 0
    for i in 0..<(signal.length() - lag) {
      total = total + signal[i] * signal[i + lag]
      count = count + 1
    }
    total / count.to_double()
  }
}

///|
pub fn autocorrelation_series(signal : ArrayView[Double]) -> Array[Double] {
  Array::makei(signal.length(), fn(lag) { autocorrelation(signal, lag) })
}

///|
pub fn mean_subtract(signal : ArrayView[Double]) -> Array[Double] {
  let average = mean(signal)
  signal.map(fn(value) { value - average })
}

///|
pub fn hann_window(signal : ArrayView[Double]) -> Array[Double] {
  let n = signal.length()
  Array::makei(n, fn(i) {
    let factor = if n <= 1 {
      1.0
    } else {
      0.5 - 0.5 * @math.cos(2.0 * pi * i.to_double() / (n - 1).to_double())
    }
    signal[i] * factor
  })
}

///|
pub fn spectral_centroid(spectrum : ArrayView[SpectrumBin]) -> Double {
  let denominator = spectrum.fold(init=0.0, fn(acc, bin) { acc + bin.power })
  if denominator == 0.0 {
    0.0
  } else {
    spectrum.fold(init=0.0, fn(acc, bin) { acc + bin.frequency * bin.power }) /
    denominator
  }
}

///|
pub fn spectrum_band_power(
  spectrum : ArrayView[SpectrumBin],
  low : Double,
  high : Double,
) -> Double {
  spectrum.fold(init=0.0, fn(acc, bin) {
    if bin.frequency >= low && bin.frequency <= high {
      acc + bin.power
    } else {
      acc
    }
  })
}

///|
pub fn spectrum_to_csv(spectrum : ArrayView[SpectrumBin]) -> String {
  let output = StringBuilder()
  output.write_string("mode,frequency,real,imaginary,power\n")
  for bin in spectrum {
    output.write_string(
      "\{bin.mode},\{bin.frequency},\{bin.real},\{bin.imaginary},\{bin.power}\n",
    )
  }
  output.to_string()
}