///|
fn finite_double(value : Double) -> Bool {
  !value.is_nan() && value != @double.infinity && value != @double.neg_infinity
}

///|
fn copy_matrix(rows : Array[Array[Double]]) -> Array[Array[Double]] {
  let copied : Array[Array[Double]] = []
  for row in rows {
    copied.push(row.copy())
  }
  copied
}

///|
fn sorted_distinct_labels(labels : Array[Int]) -> Array[Int] {
  let result : Array[Int] = []
  for label in labels {
    let mut position = 0
    while position < result.length() && result[position] < label {
      position = position + 1
    }
    if position == result.length() || result[position] != label {
      result.push(label)
      let mut index = result.length() - 1
      while index > position {
        result[index] = result[index - 1]
        index = index - 1
      }
      result[position] = label
    }
  }
  result
}

///|
/// Validates and defensively copies a dense numeric classification dataset.
pub fn dataset(
  features : Array[Array[Double]],
  labels : Array[Int],
  name : String,
) -> Result[Dataset, SvmError] {
  if features.is_empty() {
    return Err(EmptyDataset)
  }
  if features.length() != labels.length() {
    return Err(LabelLengthMismatch(features.length(), labels.length()))
  }
  let columns = features[0].length()
  if columns == 0 {
    return Err(EmptyFeatureSet)
  }
  for row_index, row in features {
    if row.length() != columns {
      return Err(RaggedRow(row_index, columns, row.length()))
    }
    for column_index, value in row {
      if !finite_double(value) {
        return Err(NonFiniteFeature(row_index, column_index))
      }
    }
  }
  Ok({
    feature_rows: copy_matrix(features),
    class_labels: labels.copy(),
    dataset_name: name,
    column_count: columns,
    sorted_classes: sorted_distinct_labels(labels),
  })
}

///|
pub fn Dataset::row_count(self : Dataset) -> Int {
  self.feature_rows.length()
}

///|
pub fn Dataset::feature_count(self : Dataset) -> Int {
  self.column_count
}

///|
pub fn Dataset::name(self : Dataset) -> String {
  self.dataset_name
}

///|
pub fn Dataset::features(self : Dataset) -> Array[Array[Double]] {
  copy_matrix(self.feature_rows)
}

///|
pub fn Dataset::labels(self : Dataset) -> Array[Int] {
  self.class_labels.copy()
}

///|
pub fn Dataset::classes(self : Dataset) -> Array[Int] {
  self.sorted_classes.copy()
}

///|
pub fn Dataset::class_count(self : Dataset, label : Int) -> Int {
  let mut count = 0
  for value in self.class_labels {
    if value == label {
      count = count + 1
    }
  }
  count
}

///|
pub fn Dataset::row(
  self : Dataset,
  index : Int,
) -> Result[Array[Double], SvmError] {
  if index < 0 || index >= self.row_count() {
    return Err(InvalidSubsetIndex(index, self.row_count()))
  }
  Ok(self.feature_rows[index].copy())
}

///|
/// Selects unique rows in caller-provided order and gives the result a new name.
pub fn Dataset::subset(
  self : Dataset,
  indices : Array[Int],
  name : String,
) -> Result[Dataset, SvmError] {
  if indices.is_empty() {
    return Err(EmptyDataset)
  }
  let selected_features : Array[Array[Double]] = []
  let selected_labels : Array[Int] = []
  for position, index in indices {
    if index < 0 || index >= self.row_count() {
      return Err(InvalidSubsetIndex(index, self.row_count()))
    }
    for previous = 0; previous < position; previous = previous + 1 {
      if indices[previous] == index {
        return Err(DuplicateSubsetIndex(index))
      }
    }
    selected_features.push(self.feature_rows[index].copy())
    selected_labels.push(self.class_labels[index])
  }
  dataset(selected_features, selected_labels, name)
}