use crate::grid_coord_2d::{GetCoordinateBounds2D, GridCoord2D, LinearizeCoords2D};
use std::ops::{Index, IndexMut};

#[derive(Debug, Default)]
pub struct VisitMap2D {
    width: usize,
    height: usize,
    cells: Vec<bool>,
}

impl VisitMap2D {
    pub fn new(width: usize, height: usize) -> Self {
        Self {
            width,
            height,
            cells: vec![false; width * height],
        }
    }

    pub fn new_like<T>(other: &T) -> Self
    where
        T: GetCoordinateBounds2D,
    {
        Self::new(other.width(), other.height())
    }

    pub fn get(&self, coords: GridCoord2D) -> Option<&bool> {
        if coords.x >= self.width || coords.y >= self.height {
            return None;
        }
        Some(&self[coords])
    }

    pub fn unvisited_neighbors(&self, coords: GridCoord2D) -> Vec<GridCoord2D> {
        let mut vec = Vec::with_capacity(4);
        if coords.x >= self.width || coords.y >= self.height {
            return vec;
        }

        self.push_if_unvisited(&mut vec, coords.up());
        self.push_if_unvisited(&mut vec, coords.right());
        self.push_if_unvisited(&mut vec, coords.down());
        self.push_if_unvisited(&mut vec, coords.left());
        vec
    }

    fn push_if_unvisited(&self, vec: &mut Vec<GridCoord2D>, coord: Option<GridCoord2D>) {
        if let Some(coord) = self.is_unvisited(coord) {
            vec.push(coord);
        }
    }

    fn is_unvisited(&self, coord: Option<GridCoord2D>) -> Option<GridCoord2D> {
        match coord {
            None => None,
            Some(value) => match self.get(value) {
                None => None,
                Some(true) => None,
                Some(false) => Some(value),
            },
        }
    }
}

impl GetCoordinateBounds2D for VisitMap2D {
    #[inline]
    fn width(&self) -> usize {
        self.width
    }

    #[inline]
    fn height(&self) -> usize {
        self.height
    }
}

impl Index<GridCoord2D> for VisitMap2D {
    type Output = bool;

    fn index(&self, index: GridCoord2D) -> &Self::Output {
        let index = self.linearize_coords(index);
        &self.cells[index]
    }
}

impl IndexMut<GridCoord2D> for VisitMap2D {
    fn index_mut(&mut self, index: GridCoord2D) -> &mut Self::Output {
        let index = self.linearize_coords(index);
        &mut self.cells[index]
    }
}
