///|
/// Checks that all parameters define a finite, usable kernel.
pub fn validate_kernel(kernel : Kernel) -> Result[Unit, SvmError] {
  match kernel {
    Linear => Ok(())
    Rbf(gamma) =>
      if !finite_double(gamma) || gamma <= 0.0 {
        Err(InvalidKernelParameter("gamma", gamma))
      } else {
        Ok(())
      }
    Polynomial(gamma, coefficient, degree) => {
      if !finite_double(gamma) || gamma <= 0.0 {
        return Err(InvalidKernelParameter("gamma", gamma))
      }
      if !finite_double(coefficient) {
        return Err(InvalidKernelParameter("coefficient", coefficient))
      }
      if degree <= 0 {
        return Err(InvalidKernelParameter("degree", degree.to_double()))
      }
      Ok(())
    }
  }
}

///|
fn checked_vectors(
  left : Array[Double],
  right : Array[Double],
) -> Result[Unit, SvmError] {
  if left.length() != right.length() {
    return Err(KernelDimensionMismatch(left.length(), right.length()))
  }
  for index, value in left {
    if !finite_double(value) {
      return Err(NonFiniteFeature(0, index))
    }
  }
  for index, value in right {
    if !finite_double(value) {
      return Err(NonFiniteFeature(1, index))
    }
  }
  Ok(())
}

///|
fn dot_product(left : Array[Double], right : Array[Double]) -> Double {
  let mut total = 0.0
  for index = 0; index < left.length(); index = index + 1 {
    total = total + left[index] * right[index]
  }
  total
}

///|
fn squared_distance(left : Array[Double], right : Array[Double]) -> Double {
  let mut total = 0.0
  for index = 0; index < left.length(); index = index + 1 {
    let difference = left[index] - right[index]
    total = total + difference * difference
  }
  total
}

///|
fn unchecked_kernel_value(
  kernel : Kernel,
  left : Array[Double],
  right : Array[Double],
) -> Double {
  match kernel {
    Linear => dot_product(left, right)
    Rbf(gamma) => @math.exp(-gamma * squared_distance(left, right))
    Polynomial(gamma, coefficient, degree) =>
      @math.pow(
        gamma * dot_product(left, right) + coefficient,
        degree.to_double(),
      )
  }
}

///|
/// Evaluates a checked kernel on two finite vectors of equal length.
pub fn kernel_value(
  kernel : Kernel,
  left : Array[Double],
  right : Array[Double],
) -> Result[Double, SvmError] {
  match validate_kernel(kernel) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  match checked_vectors(left, right) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let value = unchecked_kernel_value(kernel, left, right)
  if !finite_double(value) {
    return Err(NumericFailure("kernel evaluation"))
  }
  Ok(value)
}

///|
/// Builds a symmetric kernel matrix for a validated dataset.
pub fn kernel_matrix(
  kernel : Kernel,
  data : Dataset,
) -> Result[Array[Array[Double]], SvmError] {
  match validate_kernel(kernel) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  let rows = data.features()
  let count = rows.length()
  let matrix : Array[Array[Double]] = Array::makei(count, fn(_) {
    Array::make(count, 0.0)
  })
  for left = 0; left < count; left = left + 1 {
    for right = left; right < count; right = right + 1 {
      let value = unchecked_kernel_value(kernel, rows[left], rows[right])
      if !finite_double(value) {
        return Err(NumericFailure("kernel matrix construction"))
      }
      matrix[left][right] = value
      matrix[right][left] = value
    }
  }
  Ok(matrix)
}