///|
/// Return a solution as a copied integer array.
pub fn solution_values(solution : Solution) -> Array[Int] {
  solution.values.copy()
}

///|
/// Compare two solutions by all variable values.
pub fn solutions_equal(left : Solution, right : Solution) -> Bool {
  if left.values.length() != right.values.length() {
    return false
  }
  for index in 0.. Int {
  let limit = if left.values.length() < right.values.length() {
    left.values.length()
  } else {
    right.values.length()
  }
  let mut distance = if left.values.length() == right.values.length() {
    0
  } else {
    1
  }
  for index in 0.. Array[SolutionDelta] {
  let result : Array[SolutionDelta] = []
  let limit = if left.values.length() < right.values.length() {
    left.values.length()
  } else {
    right.values.length()
  }
  for index in 0.. String {
  "v\{self.variable}: \{self.before} -> \{self.after}"
}

///|
/// Read a delta variable id.
pub fn SolutionDelta::variable(self : SolutionDelta) -> Int {
  self.variable
}

///|
/// Read a delta's old value.
pub fn SolutionDelta::before(self : SolutionDelta) -> Int {
  self.before
}

///|
/// Read a delta's new value.
pub fn SolutionDelta::after(self : SolutionDelta) -> Int {
  self.after
}

///|
/// Render a solution as comma-separated values.
pub fn Solution::csv(self : Solution) -> String {
  let builder = StringBuilder()
  for index, value in self.values {
    if index > 0 {
      builder.write_char(',')
    }
    builder.write_string("\{value}")
  }
  builder.to_string()
}

///|
/// Render a set of solutions as newline-separated CSV.
pub fn solutions_csv(solutions : Array[Solution]) -> String {
  let builder = StringBuilder()
  for index, solution in solutions {
    if index > 0 {
      builder.write_char('\n')
    }
    builder.write_string(solution.csv())
  }
  builder.to_string()
}

///|
/// Render all variable names as a CSV header.
pub fn Solver::csv_header(self : Solver) -> String {
  let builder = StringBuilder()
  for index, variable in self.variables {
    if index > 0 {
      builder.write_char(',')
    }
    builder.write_string(variable.name)
  }
  builder.to_string()
}

///|
/// Render a named solution as a two-line CSV document.
pub fn Solver::solution_csv(self : Solver, solution : Solution) -> String {
  self.csv_header() + "\n" + solution.csv()
}

///|
/// Parse a comma-separated integer list.
pub fn parse_integer_list(input : String) -> Array[Int]? {
  let result : Array[Int] = []
  let mut current = ""
  let flush = () => {
    if current != "" {
      match parse_small_int(current) {
        Some(value) => result.push(value)
        None => return false
      }
      current = ""
    }
    true
  }
  for character in input {
    if character == ',' || character.is_whitespace() {
      if !flush() {
        return None
      }
    } else {
      current += character.to_string()
    }
  }
  if !flush() {
    return None
  }
  Some(result)
}

///|
/// Parse a small signed decimal integer without a dependency on IO packages.
fn parse_small_int(input : String) -> Int? {
  if input == "" {
    return None
  }
  let mut sign = 1
  let mut start = 0
  if input.get_char(0) is Some('-') {
    sign = -1
    start = 1
  }
  let mut result = 0
  let mut seen = false
  for index in start.. {
        if character < '0' || character > '9' {
          return None
        }
        result = result * 10 + character.to_int() - '0'.to_int()
        seen = true
      }
      None => return None
    }
  }
  if seen {
    Some(result * sign)
  } else {
    None
  }
}

///|
/// Parse a finite domain from a comma-separated integer list.
pub fn parse_domain(input : String) -> Domain? {
  match parse_integer_list(input) {
    Some(values) =>
      if values.length() == 0 {
        None
      } else {
        Some(domain_from_values(values))
      }
    None => None
  }
}

///|
/// Render domain intervals as a machine-readable string.
pub fn domain_csv(domain : Domain) -> String {
  let builder = StringBuilder()
  for index, interval in domain.intervals() {
    if index > 0 {
      builder.write_char(',')
    }
    builder.write_string(interval.describe())
  }
  builder.to_string()
}

///|
/// Render a solver's structural report as CSV-like rows.
pub fn Solver::constraint_csv(self : Solver) -> String {
  let builder = StringBuilder()
  builder.write_string("index,label,arity,variables\n")
  for index, info in self.constraint_report() {
    builder.write_string(
      "\{index},\{info.label()},\{info.arity()},\{Repr(info.variables())}\n",
    )
  }
  builder.to_string()
}

///|
/// Return a copy of all current variable domains.
pub fn Solver::domains(self : Solver) -> Array[Domain] {
  self.variables.map(variable => variable.domain.clone())
}

///|
/// Return a compact domain report.
pub fn Solver::domain_csv(self : Solver) -> String {
  let builder = StringBuilder()
  for index, variable in self.variables {
    if index > 0 {
      builder.write_char('\n')
    }
    builder.write_string("\{variable.name},\{domain_csv(variable.domain)}")
  }
  builder.to_string()
}

///|
/// Return a solution delta report.
pub fn solution_delta_report(left : Solution, right : Solution) -> String {
  let builder = StringBuilder()
  for index, delta in solution_delta(left, right) {
    if index > 0 {
      builder.write_string(", ")
    }
    builder.write_string(delta.describe())
  }
  builder.to_string()
}