///|
pub fn ranks(values : Array[Double]) -> Array[Double] {
  let sorted = sorted_copy(values)
  let result : Array[Double] = []
  for value in values {
    let mut first = 0
    let mut last = sorted.length() - 1
    while first <= last {
      let middle = (first + last) / 2
      if sorted[middle] < value {
        first = middle + 1
      } else {
        last = middle - 1
      }
    }
    result.push(first.to_double() + 1.0)
  }
  result
}

///|
pub fn spearman_correlation(
  left : Array[Double],
  right : Array[Double],
) -> Double {
  correlation(ranks(left), ranks(right))
}

///|
pub fn mann_whitney_u(left : Array[Double], right : Array[Double]) -> Double {
  let ranks_right = sorted_copy(right)
  let mut u = 0.0
  for value in left {
    for other in ranks_right {
      if value > other {
        u += 1.0
      } else if value == other {
        u += 0.5
      }
    }
  }
  u
}

///|
pub fn rank_biserial_effect(
  left : Array[Double],
  right : Array[Double],
) -> Double {
  if left.length() == 0 || right.length() == 0 {
    return 0.0
  }
  let u = mann_whitney_u(left, right)
  2.0 * u / (left.length() * right.length()).to_double() - 1.0
}

///|
pub fn sign_change_rate(values : Array[Double]) -> Double {
  if values.length() < 2 {
    return 0.0
  }
  let mut changes = 0
  let mut previous = sign(values[0])
  for i in 1.. Double {
  if values.length() < 2 {
    return 0.0
  }
  let center = median(values)
  let mut runs = 1
  let mut previous = values[0] >= center
  for i in 1..= center
    if current != previous {
      runs += 1
      previous = current
    }
  }
  runs.to_double() / values.length().to_double()
}

///|
pub fn slope_confidence(values : Array[Double]) -> Double {
  let slope = absolute(linear_slope(values))
  let noise = mean_absolute_difference(values) + 1.0e-12
  clamp_probability(slope / (slope + noise))
}

///|
pub struct RankShiftDetector {
  left : DoubleWindow
  right : DoubleWindow
  threshold : Double
  mut index : Int
}

///|
pub fn RankShiftDetector::new(
  window_size? : Int = 16,
  threshold? : Double = 0.25,
) -> RankShiftDetector {
  let size = if window_size < 2 { 2 } else { window_size }
  {
    left: DoubleWindow::new(size),
    right: DoubleWindow::new(size),
    threshold: if threshold <= 0.0 {
      0.25
    } else {
      threshold
    },
    index: 0,
  }
}

///|
pub fn RankShiftDetector::update(
  self : RankShiftDetector,
  value : Double,
) -> DetectionResult {
  self.index += 1
  if !is_finite(value) {
    return DetectionResult::quiet(index=self.index)
  }
  if self.right.is_full() {
    self.left.clear()
    for item in self.right.to_array() {
      ignore(self.left.push(item))
    }
    self.right.clear()
  }
  ignore(self.right.push(value))
  if !self.left.is_full() || !self.right.is_full() {
    return DetectionResult::quiet(index=self.index)
  }
  let effect = absolute(
    rank_biserial_effect(self.left.to_array(), self.right.to_array()),
  )
  DetectionResult::new(
    effect >= self.threshold,
    effect / self.threshold,
    clamp_probability(effect),
    DistributionShift,
    self.index,
    evidence=effect,
  )
}