///|
/// Online principal-component analysis using Oja's rule.
pub struct OnlinePCA {
  components : Array[Array[Double]]
  mean : Array[Double]
  learning_rate : Double
  mut count : Double
  explained : Array[Double]
}

///|
pub fn OnlinePCA::new(
  components : Int,
  dimension : Int,
  learning_rate? : Double = 0.01,
) -> OnlinePCA {
  let component_count = if components < 0 { 0 } else { components }
  let size = if dimension < 0 { 0 } else { dimension }
  {
    components: Array::makei(component_count, component => {
      Array::makei(size, feature => {
        if feature == component % (size + 1) {
          1.0
        } else {
          0.0
        }
      })
    }),
    mean: Array::make(size, 0.0),
    learning_rate: clamp(learning_rate, 1.0e-6, 1.0),
    count: 0.0,
    explained: Array::make(component_count, 0.0),
  }
}

///|
pub fn OnlinePCA::dimension(self : OnlinePCA) -> Int {
  self.mean.length()
}

///|
pub fn OnlinePCA::components(self : OnlinePCA) -> Int {
  self.components.length()
}

///|
pub fn OnlinePCA::component_vectors(self : OnlinePCA) -> Array[Array[Double]] {
  self.components.map(row => copy_vector(row))
}

///|
pub fn OnlinePCA::update(self : OnlinePCA, values : Array[Double]) -> Unit {
  self.count += 1.0
  let centered = Array::makei(self.dimension(), i => {
    let value = values.get(i).unwrap_or(0.0)
    let delta = value - self.mean[i]
    self.mean[i] += delta / self.count
    value - self.mean[i]
  })
  for component_index in 0.. 1.0e-15 {
      for i in 0.. Array[Double] {
  let centered = Array::makei(self.dimension(), i => {
    values.get(i).unwrap_or(0.0) - self.mean[i]
  })
  self.components.map(component => dot_product(component, centered))
}

///|
pub fn OnlinePCA::reconstruct(
  self : OnlinePCA,
  transformed : Array[Double],
) -> Array[Double] {
  let result = copy_vector(self.mean)
  let limit = if transformed.length() < self.components.length() {
    transformed.length()
  } else {
    self.components.length()
  }
  for component_index in 0.. Array[Double] {
  copy_vector(self.explained)
}

///|
pub fn OnlinePCA::mean(self : OnlinePCA) -> Array[Double] {
  copy_vector(self.mean)
}

///|
pub fn OnlinePCA::count(self : OnlinePCA) -> Double {
  self.count
}

///|
pub fn OnlinePCA::reset(self : OnlinePCA) -> Unit {
  self.mean.fill(0.0)
  self.explained.fill(0.0)
  self.count = 0.0
}