use crate::generators::{
    GenerationStep, GenerationSteps, GenerationVisitor, MazeGenerator2D, VecGenerationVisitor,
};
use crate::grid_coord_2d::{GetCoordinateBounds2D, GridCoord2D};
use crate::visit_map_2d::VisitMap2D;
use crate::wall4_grid::Wall4Grid;
use rand::prelude::SliceRandom;
use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};

pub struct HuntAndKill4 {
    rng_seed: u64,
}

impl Default for HuntAndKill4 {
    fn default() -> Self {
        Self::new_random()
    }
}

impl HuntAndKill4 {
    fn visited_neighbors(visited: &VisitMap2D, cell: GridCoord2D) -> Vec<GridCoord2D> {
        let mut all = Vec::with_capacity(4);
        for c in [cell.up(), cell.right(), cell.down(), cell.left()]
            .into_iter()
            .flatten()
        {
            if c.x < visited.width() && c.y < visited.height() && visited[c] {
                all.push(c);
            }
        }
        all
    }

    fn generate_with_steps(&self, width: usize, height: usize) -> (Wall4Grid, Vec<GenerationStep>) {
        let mut grid = Wall4Grid::new(width, height);
        let mut visited = VisitMap2D::new_like(&grid);
        let mut visitor = VecGenerationVisitor::default();
        if width == 0 || height == 0 {
            visitor.on_step(&GenerationStep::Complete);
            return (grid, visitor.into_steps());
        }

        let mut rng = StdRng::seed_from_u64(self.rng_seed);
        let mut current = Some(GridCoord2D::new(
            rng.random_range(0..width),
            rng.random_range(0..height),
        ));

        while let Some(cell) = current {
            visited[cell] = true;
            visitor.on_step(&GenerationStep::Visit { cell });

            let mut unvisited = visited.unvisited_neighbors(cell);
            if !unvisited.is_empty() {
                unvisited.shuffle(&mut rng);
                let next = unvisited[0];
                grid.remove_wall_between(cell, next);
                visitor.on_step(&GenerationStep::Carve {
                    from: cell,
                    to: next,
                });
                current = Some(next);
                continue;
            }

            current = None;
            'hunt: for y in 0..height {
                for x in 0..width {
                    let hunt = GridCoord2D::new(x, y);
                    if visited[hunt] {
                        continue;
                    }
                    let mut neighbors = Self::visited_neighbors(&visited, hunt);
                    if neighbors.is_empty() {
                        continue;
                    }
                    neighbors.shuffle(&mut rng);
                    let next = neighbors[0];
                    grid.remove_wall_between(hunt, next);
                    visitor.on_step(&GenerationStep::Carve {
                        from: hunt,
                        to: next,
                    });
                    current = Some(hunt);
                    visitor.on_step(&GenerationStep::AddToFrontier { cell: hunt });
                    break 'hunt;
                }
            }
        }

        visitor.on_step(&GenerationStep::Complete);
        (grid, visitor.into_steps())
    }
}

impl MazeGenerator2D for HuntAndKill4 {
    fn new_random() -> Self {
        Self {
            rng_seed: rand::random(),
        }
    }

    fn new_from_seed(rng_seed: u64) -> Self {
        if rng_seed == 0 {
            Self::new_random()
        } else {
            Self { rng_seed }
        }
    }

    fn generate(&self, width: usize, height: usize) -> Wall4Grid {
        self.generate_with_steps(width, height).0
    }

    fn generate_steps(&self, width: usize, height: usize) -> GenerationSteps {
        GenerationSteps::new(self.generate_with_steps(width, height).1)
    }

    fn name(&self) -> &'static str {
        "hunt-and-kill"
    }

    fn description(&self) -> &'static str {
        "Walk and hunt generator with no explicit recursion stack"
    }
}
