///|
/// A non-attacking N-Queens model.
pub struct NQueens {
  solver : Solver
  queens : Array[Int]
  size : Int
}

///|
/// Build an N-Queens model using all-different columns and diagonal distance
/// constraints. The model is useful as a deterministic search benchmark.
pub fn n_queens(size : Int) -> NQueens? {
  if size < 1 {
    return None
  }
  let solver = new_solver()
  let queens : Array[Int] = []
  for row in 0.. Solution? {
  self.solver.solve()
}

///|
/// Enumerate up to `limit` placements.
pub fn NQueens::solve_all(self : NQueens, limit : Int) -> Array[Solution] {
  self.solver.limit(limit)
  self.solver.solve_all()
}

///|
/// Return search counters.
pub fn NQueens::stats(self : NQueens) -> SearchStats {
  self.solver.stats()
}

///|
/// Return queen variables in row order.
pub fn NQueens::queen_ids(self : NQueens) -> Array[Int] {
  self.queens.copy()
}

///|
/// Draw a placement with `Q` and `.` characters.
pub fn NQueens::render(self : NQueens, solution : Solution) -> String {
  let builder = StringBuilder()
  for row in 0.. 0 {
      builder.write_char('\n')
    }
    let column = solution.get(self.queens[row])
    for position in 0.. 0 {
        builder.write_char(' ')
      }
      builder.write_char(if position == column { 'Q' } else { '.' })
    }
  }
  builder.to_string()
}

///|
/// Validate a complete N-Queens placement.
pub fn NQueens::is_valid(self : NQueens, solution : Solution) -> Bool {
  if !self.solver.is_valid_solution(solution) {
    return false
  }
  for left in 0.. Int? {
  if lower > upper {
    return None
  }
  for size in lower..<=upper {
    match n_queens(size) {
      Some(problem) => if problem.solve() is Some(_) { return Some(size) }
      None => ()
    }
  }
  None
}

///|
/// Return a canonical placement for the eight-queens example.
pub fn eight_queens_solution() -> String? {
  match n_queens(8) {
    Some(problem) =>
      match problem.solve() {
        Some(solution) => Some(problem.render(solution))
        None => None
      }
    None => None
  }
}

///|
/// Build a fixed-size N-Queens model with a preferred first row.
pub fn n_queens_with_first_column(size : Int, first_column : Int) -> NQueens? {
  match n_queens(size) {
    Some(problem) =>
      if first_column < 0 || first_column >= size {
        None
      } else if problem.solver.assign(problem.queens[0], first_column) {
        Some(problem)
      } else {
        None
      }
    None => None
  }
}

///|
/// Return a compact row-to-column representation.
pub fn NQueens::compact(self : NQueens, solution : Solution) -> String {
  let builder = StringBuilder()
  for row in 0.. 0 {
      builder.write_char(',')
    }
    builder.write_string("\{solution.get(self.queens[row])}")
  }
  builder.to_string()
}