///|
pub(all) struct NumericTable {
  columns : Array[String]
  rows : Array[Array[Double]]
} derive(Debug, ToJson)

///|
pub fn NumericTable::new(columns : Array[String]) -> NumericTable {
  { columns, rows: [] }
}

///|
pub fn NumericTable::add_row(table : NumericTable, row : Array[Double]) -> Unit {
  table.rows.push(row)
}

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

///|
pub fn NumericTable::column_count(table : NumericTable) -> Int {
  table.columns.length()
}

///|
pub fn NumericTable::column(table : NumericTable, index : Int) -> String? {
  if index < 0 || index >= table.columns.length() {
    None
  } else {
    Some(table.columns[index])
  }
}

///|
pub fn NumericTable::row(table : NumericTable, index : Int) -> Array[Double]? {
  if index < 0 || index >= table.rows.length() {
    None
  } else {
    Some(table.rows[index])
  }
}

///|
fn csv_number(value : Double) -> String {
  "\{value}"
}

///|
pub fn NumericTable::to_csv(table : NumericTable) -> String {
  let output = StringBuilder()
  for i in 0.. 0 {
      output.write_string(",")
    }
    output.write_string(table.columns[i])
  }
  output.write_string("\n")
  for row in table.rows {
    for i in 0.. 0 {
        output.write_string(",")
      }
      if i < row.length() {
        output.write_string(csv_number(row[i]))
      }
    }
    output.write_string("\n")
  }
  output.to_string()
}

///|
pub fn NumericTable::transpose(table : NumericTable) -> NumericTable {
  let output_columns = Array::makei(table.rows.length(), fn(i) { "row_\{i}" })
  let output = NumericTable::new(output_columns)
  for column in 0.. NumericTable {
  let table = NumericTable::new(names)
  for row in series {
    table.add_row(row)
  }
  table
}

///|
pub fn table_column_values(table : NumericTable, index : Int) -> Array[Double] {
  let output : Array[Double] = []
  for row in table.rows {
    if index >= 0 && index < row.length() {
      output.push(row[index])
    }
  }
  output
}

///|
pub fn table_mean(table : NumericTable, index : Int) -> Double {
  mean(table_column_values(table, index))
}

///|
pub fn table_minimum(table : NumericTable, index : Int) -> Double {
  min_value(table_column_values(table, index), 0.0)
}

///|
pub fn table_maximum(table : NumericTable, index : Int) -> Double {
  max_value(table_column_values(table, index), 0.0)
}

///|
pub fn table_summary(table : NumericTable) -> String {
  let output = StringBuilder()
  for i in 0.. NumericTable {
  let columns = table.columns.copy()
  columns.push(name)
  let output = NumericTable::new(columns)
  for i in 0.. NumericTable {
  let output = NumericTable::new(table.columns.copy())
  for row in table.rows {
    if column >= 0 &&
      column < row.length() &&
      row[column] >= low &&
      row[column] <= high {
      output.add_row(row.copy())
    }
  }
  output
}

///|
pub fn table_row_sums(table : NumericTable) -> Array[Double] {
  table.rows.map(fn(row) { sum_values(row) })
}

///|
pub fn table_column_sums(table : NumericTable) -> Array[Double] {
  Array::makei(table.columns.length(), fn(index) {
    sum_values(table_column_values(table, index))
  })
}