///|
/// Stores validated dense numeric rows and integer class labels.
pub(all) struct ClassificationDataset {
  rows : Array[Array[Double]]
  labels : Array[Int]
  feature_total : Int
  class_total : Int
} derive(Eq, Debug)

///|
/// Stores validated dense numeric rows and continuous targets.
pub(all) struct RegressionDataset {
  rows : Array[Array[Double]]
  targets : Array[Double]
  feature_total : Int
} derive(Eq, Debug)

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

///|
fn validate_rows(
  rows : Array[Array[Double]],
) -> Result[(Array[Array[Double]], Int), TreeError] {
  if rows.is_empty() {
    return Err(EmptyDataset)
  }
  let expected = rows[0].length()
  if expected == 0 {
    return Err(EmptyFeatureRow(0))
  }
  let copied : Array[Array[Double]] = []
  for row_index, row in rows {
    if row.length() == 0 {
      return Err(EmptyFeatureRow(row_index))
    }
    if row.length() != expected {
      return Err(InconsistentFeatureCount(row_index, expected, row.length()))
    }
    for feature_index, value in row {
      if !finite_number(value) {
        return Err(NonFiniteFeature(row_index, feature_index))
      }
    }
    copied.push(row.copy())
  }
  Ok((copied, expected))
}

///|
/// Validates and copies a classification dataset.
pub fn classification_dataset(
  rows : Array[Array[Double]],
  labels : Array[Int],
  class_count : Int,
) -> Result[ClassificationDataset, TreeError] {
  if class_count <= 0 {
    return Err(InvalidClassCount(class_count))
  }
  let (copied_rows, feature_count) = match validate_rows(rows) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if labels.length() != copied_rows.length() {
    return Err(TargetCountMismatch(copied_rows.length(), labels.length()))
  }
  for row_index, label in labels {
    if label < 0 || label >= class_count {
      return Err(InvalidClassLabel(row_index, label, class_count))
    }
  }
  Ok({
    rows: copied_rows,
    labels: labels.copy(),
    feature_total: feature_count,
    class_total: class_count,
  })
}

///|
/// Validates and copies a regression dataset.
pub fn regression_dataset(
  rows : Array[Array[Double]],
  targets : Array[Double],
) -> Result[RegressionDataset, TreeError] {
  let (copied_rows, feature_count) = match validate_rows(rows) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if targets.length() != copied_rows.length() {
    return Err(TargetCountMismatch(copied_rows.length(), targets.length()))
  }
  for row_index, target in targets {
    if !finite_number(target) {
      return Err(NonFiniteTarget(row_index))
    }
  }
  Ok({
    rows: copied_rows,
    targets: targets.copy(),
    feature_total: feature_count,
  })
}

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

///|
pub fn ClassificationDataset::feature_count(
  self : ClassificationDataset,
) -> Int {
  self.feature_total
}

///|
pub fn ClassificationDataset::class_count(self : ClassificationDataset) -> Int {
  self.class_total
}

///|
pub fn ClassificationDataset::row(
  self : ClassificationDataset,
  index : Int,
) -> Array[Double] {
  self.rows[index].copy()
}

///|
pub fn ClassificationDataset::label(
  self : ClassificationDataset,
  index : Int,
) -> Int {
  self.labels[index]
}

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

///|
pub fn RegressionDataset::feature_count(self : RegressionDataset) -> Int {
  self.feature_total
}

///|
pub fn RegressionDataset::row(
  self : RegressionDataset,
  index : Int,
) -> Array[Double] {
  self.rows[index].copy()
}

///|
pub fn RegressionDataset::target(
  self : RegressionDataset,
  index : Int,
) -> Double {
  self.targets[index]
}