///|
/// Streaming ridge regression with a diagonal preconditioner.
pub struct OnlineRidgeRegression {
  weights : Array[Double]
  diagonal : Array[Double]
  learning_rate : Double
  l2 : Double
  mut intercept : Double
  mut intercept_diagonal : Double
  mut steps : Int
}

///|
pub fn OnlineRidgeRegression::new(
  dimension : Int,
  learning_rate? : Double = 0.01,
  l2? : Double = 1.0e-4,
) -> OnlineRidgeRegression {
  let size = if dimension < 0 { 0 } else { dimension }
  {
    weights: Array::make(size, 0.0),
    diagonal: Array::make(size, 1.0),
    learning_rate,
    l2,
    intercept: 0.0,
    intercept_diagonal: 1.0,
    steps: 0,
  }
}

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

///|
pub fn OnlineRidgeRegression::weights(
  self : OnlineRidgeRegression,
) -> Array[Double] {
  copy_vector(self.weights)
}

///|
pub fn OnlineRidgeRegression::intercept(self : OnlineRidgeRegression) -> Double {
  self.intercept
}

///|
pub fn OnlineRidgeRegression::steps(self : OnlineRidgeRegression) -> Int {
  self.steps
}

///|
pub fn OnlineRidgeRegression::predict(
  self : OnlineRidgeRegression,
  features : Array[Double],
) -> Double {
  self.intercept + dot_product(self.weights, features)
}

///|
pub fn OnlineRidgeRegression::update(
  self : OnlineRidgeRegression,
  features : Array[Double],
  label : Double,
) -> Unit {
  self.update_weighted(features, label, 1.0)
}

///|
pub fn OnlineRidgeRegression::update_weighted(
  self : OnlineRidgeRegression,
  features : Array[Double],
  label : Double,
  sample_weight : Double,
) -> Unit {
  let error = (self.predict(features) - label) * sample_weight
  let limit = if features.length() < self.weights.length() {
    features.length()
  } else {
    self.weights.length()
  }
  for i in 0.. Double {
  0.5 * squared_error(self.predict(features), label) +
  0.5 * self.l2 * squared_norm(self.weights)
}

///|
pub fn OnlineRidgeRegression::residual(
  self : OnlineRidgeRegression,
  features : Array[Double],
  label : Double,
) -> Double {
  label - self.predict(features)
}

///|
pub fn OnlineRidgeRegression::rmse(
  self : OnlineRidgeRegression,
  samples : Array[Array[Double]],
  labels : Array[Double],
) -> Double {
  let size = if samples.length() < labels.length() {
    samples.length()
  } else {
    labels.length()
  }
  if size == 0 {
    0.0
  } else {
    let mut total = 0.0
    for i in 0.. Unit {
  self.weights.fill(0.0)
  self.diagonal.fill(1.0)
  self.intercept = 0.0
  self.intercept_diagonal = 1.0
  self.steps = 0
}

///|
/// Robust online regression using the Huber loss.
pub struct OnlineHuberRegression {
  weights : Array[Double]
  learning_rate : Double
  delta : Double
  l2 : Double
  mut steps : Int
}

///|
pub fn OnlineHuberRegression::new(
  dimension : Int,
  learning_rate? : Double = 0.01,
  delta? : Double = 1.0,
  l2? : Double = 0.0,
) -> OnlineHuberRegression {
  {
    weights: Array::make(if dimension < 0 { 0 } else { dimension }, 0.0),
    learning_rate,
    delta: if delta <= 0.0 {
      1.0
    } else {
      delta
    },
    l2,
    steps: 0,
  }
}

///|
pub fn OnlineHuberRegression::predict(
  self : OnlineHuberRegression,
  features : Array[Double],
) -> Double {
  dot_product(self.weights, features)
}

///|
pub fn OnlineHuberRegression::weights(
  self : OnlineHuberRegression,
) -> Array[Double] {
  copy_vector(self.weights)
}

///|
pub fn OnlineHuberRegression::update(
  self : OnlineHuberRegression,
  features : Array[Double],
  label : Double,
) -> Unit {
  let residual = self.predict(features) - label
  let magnitude = if residual < 0.0 { -residual } else { residual }
  let gradient_residual = if magnitude <= self.delta {
    residual
  } else if residual < 0.0 {
    -self.delta
  } else {
    self.delta
  }
  let limit = if features.length() < self.weights.length() {
    features.length()
  } else {
    self.weights.length()
  }
  for i in 0.. Double {
  smooth_l1_loss(self.predict(features) - label, beta=self.delta) +
  0.5 * self.l2 * squared_norm(self.weights)
}

///|
pub fn OnlineHuberRegression::steps(self : OnlineHuberRegression) -> Int {
  self.steps
}

///|
/// Online quantile regression for prediction intervals and tail forecasting.
pub struct OnlineQuantileRegression {
  weights : Array[Double]
  learning_rate : Double
  quantile : Double
  l2 : Double
  mut steps : Int
}

///|
pub fn OnlineQuantileRegression::new(
  dimension : Int,
  quantile? : Double = 0.5,
  learning_rate? : Double = 0.01,
  l2? : Double = 0.0,
) -> OnlineQuantileRegression {
  {
    weights: Array::make(if dimension < 0 { 0 } else { dimension }, 0.0),
    learning_rate,
    quantile: clamp(quantile, 1.0e-6, 1.0 - 1.0e-6),
    l2,
    steps: 0,
  }
}

///|
pub fn OnlineQuantileRegression::quantile(
  self : OnlineQuantileRegression,
) -> Double {
  self.quantile
}

///|
pub fn OnlineQuantileRegression::predict(
  self : OnlineQuantileRegression,
  features : Array[Double],
) -> Double {
  dot_product(self.weights, features)
}

///|
pub fn OnlineQuantileRegression::update(
  self : OnlineQuantileRegression,
  features : Array[Double],
  label : Double,
) -> Unit {
  let residual = label - self.predict(features)
  let gradient = if residual >= 0.0 {
    -self.quantile
  } else {
    1.0 - self.quantile
  }
  let limit = if features.length() < self.weights.length() {
    features.length()
  } else {
    self.weights.length()
  }
  for i in 0.. Double {
  let error = label - self.predict(features)
  if error >= 0.0 {
    self.quantile * error
  } else {
    (self.quantile - 1.0) * error
  }
}

///|
pub fn OnlineQuantileRegression::weights(
  self : OnlineQuantileRegression,
) -> Array[Double] {
  copy_vector(self.weights)
}

///|
pub struct RunningMean {
  mut count : Double
  mut mean : Double
}

///|
pub fn RunningMean::new() -> RunningMean {
  { count: 0.0, mean: 0.0 }
}

///|
pub fn RunningMean::update(self : RunningMean, value : Double) -> Unit {
  self.count += 1.0
  self.mean += (value - self.mean) / self.count
}

///|
pub fn RunningMean::merge(self : RunningMean, other : RunningMean) -> Unit {
  if other.count > 0.0 {
    let total = self.count + other.count
    self.mean = (self.mean * self.count + other.mean * other.count) / total
    self.count = total
  }
}

///|
pub fn RunningMean::value(self : RunningMean) -> Double {
  self.mean
}

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