use rand::prelude::*;
use super::{MapGenerator, MapModifier};
use super::map::{Map, TileType};


pub struct CellularAutomataGen {
    width: usize,
    height: usize
}

impl MapGenerator for CellularAutomataGen {
    fn generate_map(&self, rng : &mut StdRng) -> Map {
        self.build(rng)
    }
}

impl CellularAutomataGen {
    pub fn new(width: usize, height: usize) -> CellularAutomataGen {
        CellularAutomataGen {width, height}
    }

    /// Generate map
    fn build(&self, rng : &mut StdRng) -> Map {
        let mut map = Map::new(self.width, self.height);
        // First we completely randomize the map, setting 55% of it to be floor.
        for y in 1..self.height-1 {
            for x in 1..self.width-1 {
                let roll = rng.next_u32() % 100;
                if roll > 55 { map.set_tile(x, y, TileType::Floor) } 
                else { map.set_tile(x, y, TileType::Wall) }
            }
        }

        // Now we iteratively apply cellular automata rules
        for _ in 0..15 {
            map = apply_iteration(&map);
        }

        map
    }

}

impl MapModifier for CellularAutomataGen {
    fn modify_map(&self, _rng: &mut StdRng, map : &Map) -> Map {
        apply_iteration(map)
    }
}


fn apply_iteration(map: &Map) -> Map {
    let mut new_map = map.clone();

    for y in 1..map.height-1 {
        for x in 1..map.width-1 {
            let idxs = [
                (x-1, y-1), (x, y-1), (x+1, y-1), 
                (x-1, y), (x+1, y), 
                (x-1, y+1), (x, y+1), (x+1, y+1)];
            let neighbors = idxs.iter()
                .filter(|(x, y)| map.at(*x, *y) == TileType::Wall)
                .count();
            
            if neighbors > 4 || neighbors == 0 {
                new_map.set_tile(x, y, TileType::Wall)
            }
            else {
                new_map.set_tile(x, y, TileType::Floor);
            }
        }
    }

    new_map
}

/// ------------------------------------------------------------------------------------------------
/// Module unit tests
/// ------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_iteration_wal() {
        let map = Map::new(3, 3);
        let new_map = apply_iteration(&map);
        assert_eq!(new_map.at(1, 1), TileType::Wall);
    }


    #[test]
    fn test_iteration_floor() {
        let mut map = Map::new(3, 3);
        for i in 0..3 {
            for j in 0..2 {
                map.set_tile(i, j, TileType::Floor);
            }
        }
        let new_map = apply_iteration(&map);
        assert_eq!(new_map.at(1, 1), TileType::Floor);
    }

}