///|
pub fn safe_grid(cells : Int, length : Double) -> Grid1D? {
  if cells <= 0 || length <= 0.0 {
    None
  } else {
    Some(Grid1D::new(cells, length))
  }
}

///|
pub fn clamp_index(index : Int, length : Int) -> Int {
  if length <= 0 {
    0
  } else if index < 0 {
    0
  } else if index >= length {
    length - 1
  } else {
    index
  }
}

///|
pub fn periodic_index(index : Int, length : Int) -> Int {
  if length <= 0 {
    0
  } else {
    let value = index % length
    if value < 0 {
      value + length
    } else {
      value
    }
  }
}

///|
fn periodic_value(values : ArrayView[Double], index : Int) -> Double {
  values[periodic_index(index, values.length())]
}

///|
pub fn periodic_gradient(
  grid : Grid1D,
  values : ArrayView[Double],
) -> Array[Double] {
  let result = zeros(values.length())
  if values.length() == 0 {
    result
  } else {
    for i in 0.. Array[Double] {
  let result = zeros(values.length())
  if values.length() == 0 {
    result
  } else {
    for i in 0.. Double {
  values.fold(init=0.0, fn(acc, value) { acc + value * grid.dx })
}

///|
pub fn weighted_mean(
  values : ArrayView[Double],
  weights : ArrayView[Double],
) -> Double {
  let totals = values.fold(init=(0.0, 0.0), fn(acc, value) {
    let (sum, weight_sum) = acc
    let index = if weight_sum.to_int() < weights.length() {
      weight_sum.to_int()
    } else {
      weights.length() - 1
    }
    (sum + value * weights[index], weight_sum + 1.0)
  })
  let (sum, _) = totals
  let weight_total = weights.fold(init=0.0, fn(acc, value) { acc + value })
  if weight_total == 0.0 {
    0.0
  } else {
    sum / weight_total
  }
}

///|
pub fn rms(values : ArrayView[Double]) -> Double {
  if values.length() == 0 {
    0.0
  } else {
    (values.fold(init=0.0, fn(acc, value) { acc + value * value }) /
    values.length().to_double()).sqrt()
  }
}

///|
pub fn min_value(values : ArrayView[Double]) -> Double {
  if values.length() == 0 {
    0.0
  } else {
    let mut result = values[0]
    for value in values {
      if value < result {
        result = value
      }
    }
    result
  }
}

///|
pub fn max_value(values : ArrayView[Double]) -> Double {
  if values.length() == 0 {
    0.0
  } else {
    let mut result = values[0]
    for value in values {
      if value > result {
        result = value
      }
    }
    result
  }
}

///|
pub fn normalize_sum(values : ArrayView[Double]) -> Array[Double] {
  let total = values.fold(init=0.0, fn(acc, value) { acc + value })
  if total == 0.0 {
    zeros(values.length())
  } else {
    values.map(fn(value) { value / total })
  }
}

///|
pub fn l2_distance(
  left : ArrayView[Double],
  right : ArrayView[Double],
) -> Double {
  let length = if left.length() < right.length() {
    left.length()
  } else {
    right.length()
  }
  (for i = 0, total = 0.0; i < length; {
    continue i + 1, total + (left[i] - right[i]) * (left[i] - right[i])
  } nobreak {
    total
  }).sqrt()
}

///|
pub fn relative_l2_error(
  reference : ArrayView[Double],
  actual : ArrayView[Double],
) -> Double {
  let denominator = l2_norm(reference)
  if denominator == 0.0 {
    l2_distance(reference, actual)
  } else {
    l2_distance(reference, actual) / denominator
  }
}

///|
pub fn interpolate_periodic(
  grid : Grid1D,
  values : ArrayView[Double],
  x : Double,
) -> Double {
  sample_linear(grid, values, x)
}