use az::SaturatingAs;
use rand::{Rng, seq::SliceRandom};

use crate::types::{Coord, simulation::Board, wire::Settings};

use super::{Map, MapId};

#[derive(Debug, Clone, Copy)]
pub struct Standard {
    min_food: u16,
    food_spawn_chance: u8,
}

impl Map for Standard {
    fn pre_update(&self, _board: &mut Board, _rng: &mut impl Rng) {}

    fn post_update(&self, board: &mut Board, rng: &mut impl Rng) {
        let food_needed = self.check_food_needing_placement(board, rng);
        if food_needed > 0 {
            Self::place_food_randomly(board, rng, food_needed);
        }
    }
}

impl MapId for Standard {
    const ID: &str = "standard";
}

impl From<&Settings> for Standard {
    fn from(value: &Settings) -> Self {
        let min_food = value.minimum_food;
        let food_spawn_chance = value.food_spawn_chance;
        Self {
            min_food,
            food_spawn_chance,
        }
    }
}

impl Standard {
    fn check_food_needing_placement(self, board: &Board, rng: &mut impl rand::Rng) -> u16 {
        let num_current_food: u16 = board.food.count_ones().saturating_as();

        if num_current_food < self.min_food {
            return self.min_food - num_current_food;
        }
        if self.food_spawn_chance > 0 && (100 - rng.random_range(0..100)) < self.food_spawn_chance {
            return 1;
        }
        0
    }

    fn place_food_randomly(board: &mut Board, rng: &mut impl rand::Rng, food_needed: u16) {
        let mut unoccupied_points: Vec<_> = board.get_unoccupied_points(false, false).collect();
        Self::place_food_ramdomly_at_positions(
            board,
            rng,
            food_needed,
            unoccupied_points.as_mut_slice(),
        );
    }

    fn place_food_ramdomly_at_positions(
        board: &mut Board,
        rng: &mut impl rand::Rng,
        food_needed: u16,
        positions: &mut [Coord],
    ) {
        let food_needed = usize::from(food_needed).min(positions.len());

        positions.shuffle(rng);

        for tile in &positions[..food_needed] {
            let i = board.coord_to_linear(*tile);
            board.food.set(i, true);
        }
    }
}
