use std::time::{Duration, Instant};
use crate::{classify_puzzle, utils::find_eulerian_cycle, validate_puzzle, ComplexityClass, Graph, Puzzle, Solution, Tile};
use rand::Rng;
pub fn generate_puzzle(n: usize, c: usize) -> Puzzle {
let graph = Graph::regular(n);
let eulerian_cycle = find_eulerian_cycle(&graph,true);
let mut solution: Solution = create_solution_from_cycle(&eulerian_cycle);
let mut puzzle= solution.clone().into_iter().map(Some).collect::<Vec<Option<Tile>>>();
let mut rng = rand::thread_rng();
let mut expected_complexity = ComplexityClass::new(c).ok();
let mut actual_complexity: Option<ComplexityClass> = None;
let mut is_not_complex_enough = actual_complexity != expected_complexity;
let mut is_too_complex = false;
let mut now = Instant::now();
let timeout = Duration::from_millis(2000);
let is_not_valid = validate_puzzle(&puzzle.clone().into(), &solution).is_err();
let mut index = rng.gen_range(0..puzzle.len());
let mut removal_history: Vec<(Option<Tile>, usize)> = vec![(puzzle[index].clone(), index)];
puzzle[index] = None;
while is_not_valid || is_not_complex_enough {
while is_too_complex {
reinsert_tile(&mut puzzle, &mut removal_history);
update_complexity(&mut actual_complexity, &mut expected_complexity, &puzzle, &mut is_not_complex_enough, &mut is_too_complex);
}
let removed_tile: Option<Tile>;
let removed_position: Option<usize>;
(puzzle, removed_tile, removed_position) = remove_non_empty_tile(puzzle);
removal_history.push((removed_tile, removed_position.unwrap()));
update_complexity(&mut actual_complexity, &mut expected_complexity, &puzzle, &mut is_not_complex_enough, &mut is_too_complex);
let is_not_valid = validate_puzzle(&puzzle.clone().into(), &solution).is_err();
if is_not_valid {
reinsert_tile(&mut puzzle, &mut removal_history);
update_complexity(&mut actual_complexity, &mut expected_complexity, &puzzle, &mut is_not_complex_enough, &mut is_too_complex);
}
if now.elapsed().as_millis() > timeout.as_millis() || actual_complexity.is_none(){
solution = create_solution_from_cycle(&eulerian_cycle);
puzzle = solution.clone().into_iter().map(Some).collect::<Vec<Option<Tile>>>().into();
index = rng.gen_range(0..puzzle.len());
removal_history = vec![(puzzle[index].clone(), index)];
puzzle[index] = None;
update_complexity(&mut actual_complexity, &mut expected_complexity, &puzzle, &mut is_not_complex_enough, &mut is_too_complex);
now = Instant::now();
}
}
puzzle.into()
}
fn update_complexity(actual_complexity: &mut Option<ComplexityClass>, expected_complexity: &mut Option<ComplexityClass>, puzzle: &Vec<Option<Tile>>, is_not_complex_enough: &mut bool, is_too_complex: &mut bool) {
let result = classify_puzzle(&puzzle.clone().into());
*actual_complexity = result.ok();
*is_not_complex_enough = actual_complexity != expected_complexity;
*is_too_complex = actual_complexity.is_some() && actual_complexity.unwrap() > expected_complexity.unwrap();
}
fn reinsert_tile(puzzle: &mut Vec<Option<Tile>>, history: &mut Vec<(Option<Tile>, usize)>) {
let (removed_tile, removed_position) = history.pop().unwrap();
puzzle[removed_position] = removed_tile;
}
fn remove_non_empty_tile(mut puzzle: Vec<Option<Tile>>) -> (Vec<Option<Tile>>, Option<Tile>, Option<usize>) {
let mut rng = rand::thread_rng();
let mut index = rng.gen_range(0..puzzle.len());
for _ in 0..10 {
if puzzle[index].is_some() {
index = rng.gen_range(0..puzzle.len());
}
}
while puzzle[index].is_none() {
index = (index + 1) % puzzle.len();
}
let removed_tile = puzzle[index].clone();
puzzle[index] = None;
(puzzle.clone(), removed_tile, Some(index))
}
fn create_solution_from_cycle(eulerian_cycle: &Vec<crate::Node>) -> Solution {
eulerian_cycle
.windows(2)
.map(|arc| {
Tile(
arc[0].clone().try_into().unwrap(),
arc[1].clone().try_into().unwrap(),
)
})
.collect()
}