use rand::{distributions::WeightedIndex, Rng};

/// Represents the colors of the game
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
pub enum Color {
    /// Gold (D)
    Divine,
    /// Blue (R)
    Revelation,
    /// White (A)
    Grace,
    /// Green (G)
    Growth,
    /// Red (U)
    Crusade,
    /// Black (S)
    Suffering,
    /// Gray/Colorless (M)
    Mundane,
}

impl Color {
    /// Array of all colors in color wheel order
    pub const fn all() -> [Color; 7] {
        use Color::*;
        [
            Mundane, Divine, Revelation, Grace, Growth, Crusade, Suffering,
        ]
    }

    /// Array of all factions in color wheel order
    pub const fn all_factions() -> [Color; 6] {
        use Color::*;
        [Divine, Revelation, Grace, Growth, Crusade, Suffering]
    }

    /// Get the color that opposes this one.
    pub fn opposes(self) -> Self {
        match self {
            Self::Mundane => Self::Mundane,
            faction => {
                pub(crate) const ALL_FACTIONS: &[Color] = &Color::all_factions();
                let faction_pos = ALL_FACTIONS.iter().position(|f| *f == faction).unwrap();
                let opposing_index = (faction_pos + 3) % ALL_FACTIONS.len();
                ALL_FACTIONS[opposing_index]
            }
        }
    }

    /// Get the colors adjacent to this one.
    pub fn adjacent_to(self) -> [Self; 2] {
        match self {
            Self::Mundane => [Self::Mundane, Self::Mundane],
            faction => {
                pub(crate) const ALL_FACTIONS: &[Color] = &Color::all_factions();
                let faction_pos = ALL_FACTIONS.iter().position(|f| *f == faction).unwrap();
                let left_adjacent = (faction_pos + ALL_FACTIONS.len() - 1) % ALL_FACTIONS.len();
                let right_adjacent = (faction_pos + 1) % ALL_FACTIONS.len();
                [ALL_FACTIONS[left_adjacent], ALL_FACTIONS[right_adjacent]]
            }
        }
    }

    pub fn color(self) -> &'static str {
        match self {
            Color::Divine => "gold",
            Color::Revelation => "blue",
            Color::Grace => "white",
            Color::Growth => "green",
            Color::Crusade => "red",
            Color::Suffering => "black",
            Color::Mundane => "grey",
        }
    }
}

#[derive(Debug, Clone, Copy, Default)]
pub struct ColorBalance {
    pub mundane: usize,
    pub divine: usize,
    pub revelation: usize,
    pub grace: usize,
    pub growth: usize,
    pub crusade: usize,
    pub suffering: usize,
}

impl ColorBalance {
    pub fn get(&self, color: Color) -> usize {
        match color {
            Color::Divine => self.divine,
            Color::Revelation => self.revelation,
            Color::Grace => self.grace,
            Color::Growth => self.growth,
            Color::Crusade => self.crusade,
            Color::Suffering => self.suffering,
            Color::Mundane => self.mundane,
        }
    }

    pub fn gen_mana<'a, R: Rng + ?Sized>(
        &self,
        rng: &'a mut R,
    ) -> impl Iterator<Item = Color> + 'a {
        let colors = Color::all();
        let weights = [
            self.mundane.saturating_add(1),
            self.divine.saturating_add(1),
            self.revelation.saturating_add(1),
            self.grace.saturating_add(1),
            self.growth.saturating_add(1),
            self.crusade.saturating_add(1),
            self.suffering.saturating_add(1),
        ];
        let dist = WeightedIndex::new(weights).unwrap();
        rng.sample_iter(dist).map(move |idx| colors[idx])
    }
}

#[cfg(test)]
mod tests {
    use super::Color;

    #[test]
    fn test_color_oppose_and_adjacent() {
        let color = Color::Divine;
        assert_eq!(Color::Growth, color.opposes());
        assert_eq!([Color::Suffering, Color::Revelation], color.adjacent_to());
    }
}