///|
/// Online absolute-correlation tracker for feature monitoring and pruning.
pub struct OnlineFeatureSelector {
  moments : VectorMoments
  target : RunningMoments
  cross : Array[Double]
  mut observations : Double
}

///|
pub fn OnlineFeatureSelector::new(dimension : Int) -> OnlineFeatureSelector {
  let size = if dimension < 0 { 0 } else { dimension }
  {
    moments: VectorMoments::new(size),
    target: RunningMoments::new(),
    cross: Array::make(size, 0.0),
    observations: 0.0,
  }
}

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

///|
pub fn OnlineFeatureSelector::update(
  self : OnlineFeatureSelector,
  features : Array[Double],
  target : Double,
) -> Unit {
  let old_mean = self.moments.mean()
  let old_target = self.target.mean()
  self.moments.update(features)
  self.target.update(target)
  self.observations += 1.0
  let limit = if features.length() < self.cross.length() {
    features.length()
  } else {
    self.cross.length()
  }
  for i in 0.. Array[Double] {
  let feature_variance = self.moments.variance()
  let target_variance = self.target.population_variance()
  Array::makei(self.dimension(), i => {
    let denominator = (feature_variance[i] * target_variance).sqrt()
    if self.observations <= 1.0 || denominator <= 1.0e-15 {
      0.0
    } else {
      self.cross[i] / (self.observations * denominator)
    }
  })
}

///|
pub fn OnlineFeatureSelector::absolute_importance(
  self : OnlineFeatureSelector,
) -> Array[Double] {
  self.correlations().map(value => if value < 0.0 { -value } else { value })
}

///|
pub fn OnlineFeatureSelector::rank(
  self : OnlineFeatureSelector,
  top_k : Int,
) -> Array[Int] {
  let importance = self.absolute_importance()
  let order = Array::makei(importance.length(), i => i)
  order.sort_by((left, right) => {
    if importance[left] > importance[right] {
      -1
    } else if importance[left] < importance[right] {
      1
    } else {
      left - right
    }
  })
  let limit = if top_k < 0 {
    0
  } else if top_k > order.length() {
    order.length()
  } else {
    top_k
  }
  Array::makei(limit, i => order[i])
}

///|
pub fn OnlineFeatureSelector::select(
  self : OnlineFeatureSelector,
  threshold : Double,
) -> Array[Int] {
  let importance = self.absolute_importance()
  let result = Array::make(0, 0)
  for i in 0..= threshold {
      result.push(i)
    }
  }
  result
}

///|
pub fn OnlineFeatureSelector::observations(
  self : OnlineFeatureSelector,
) -> Double {
  self.observations
}

///|
pub fn OnlineFeatureSelector::reset(self : OnlineFeatureSelector) -> Unit {
  self.moments.reset()
  self.target.reset()
  self.cross.fill(0.0)
  self.observations = 0.0
}

///|
pub struct ExponentialImportance {
  scores : Array[Double]
  alpha : Double
  mut observations : Int
}

///|
pub fn ExponentialImportance::new(
  dimension : Int,
  alpha? : Double = 0.05,
) -> ExponentialImportance {
  {
    scores: Array::make(if dimension < 0 { 0 } else { dimension }, 0.0),
    alpha: clamp(alpha, 1.0e-6, 1.0),
    observations: 0,
  }
}

///|
pub fn ExponentialImportance::update(
  self : ExponentialImportance,
  gradients : Array[Double],
) -> Unit {
  let limit = if gradients.length() < self.scores.length() {
    gradients.length()
  } else {
    self.scores.length()
  }
  for i in 0.. Array[Double] {
  copy_vector(self.scores)
}

///|
pub fn ExponentialImportance::top_k(
  self : ExponentialImportance,
  k : Int,
) -> Array[Int] {
  let order = Array::makei(self.scores.length(), i => i)
  order.sort_by((left, right) => {
    if self.scores[left] > self.scores[right] {
      -1
    } else if self.scores[left] < self.scores[right] {
      1
    } else {
      left - right
    }
  })
  let limit = if k < 0 {
    0
  } else if k > order.length() {
    order.length()
  } else {
    k
  }
  Array::makei(limit, i => order[i])
}

///|
pub fn ExponentialImportance::observations(self : ExponentialImportance) -> Int {
  self.observations
}

///|
pub fn ExponentialImportance::reset(self : ExponentialImportance) -> Unit {
  self.scores.fill(0.0)
  self.observations = 0
}

///|
pub struct FeatureDriftSummary {
  changed : Array[Bool]
  scores : Array[Double]
}

///|
pub fn FeatureDriftSummary::new(
  scores : Array[Double],
  threshold : Double,
) -> FeatureDriftSummary {
  {
    changed: scores.map(value => value.abs() >= threshold),
    scores: copy_vector(scores),
  }
}

///|
pub fn FeatureDriftSummary::changed_count(self : FeatureDriftSummary) -> Int {
  self.changed.count_if(value => value)
}

///|
pub fn FeatureDriftSummary::changed(
  self : FeatureDriftSummary,
  index : Int,
) -> Bool {
  self.changed.get(index).unwrap_or(false)
}

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

///|
/// A bounded vocabulary selector that keeps the most useful hashed features.
pub struct HashedFeatureSelector {
  dimension : Int
  importance : ExponentialImportance
  threshold : Double
}

///|
pub fn HashedFeatureSelector::new(
  dimension : Int,
  threshold? : Double = 0.0,
) -> HashedFeatureSelector {
  {
    dimension: if dimension < 0 {
      0
    } else {
      dimension
    },
    importance: ExponentialImportance::new(dimension),
    threshold,
  }
}

///|
pub fn HashedFeatureSelector::observe(
  self : HashedFeatureSelector,
  vector : SparseVector,
  gradient : Double,
) -> Unit {
  let dense = vector.to_dense()
  self.importance.update(dense.map(value => value * gradient))
}

///|
pub fn HashedFeatureSelector::selected(
  self : HashedFeatureSelector,
) -> Array[Int] {
  let scores = self.importance.scores()
  let result = Array::make(0, 0)
  for i in 0..= self.threshold {
      result.push(i)
    }
  }
  result
}

///|
pub fn HashedFeatureSelector::selected_top_k(
  self : HashedFeatureSelector,
  k : Int,
) -> Array[Int] {
  self.importance.top_k(k)
}

///|
pub fn HashedFeatureSelector::dimension(self : HashedFeatureSelector) -> Int {
  self.dimension
}

///|
pub fn HashedFeatureSelector::scores(
  self : HashedFeatureSelector,
) -> Array[Double] {
  self.importance.scores()
}

///|
pub fn HashedFeatureSelector::reset(self : HashedFeatureSelector) -> Unit {
  self.importance.reset()
}