use bevy::{
    prelude::*,
    utils::{HashMap, RandomState},
};

/// Deck and Cards
pub struct DeckPlugin;

impl Plugin for DeckPlugin {
    fn build(&self, _app: &mut App) {
        // Nothing yet!
    }
}

/// We expect 81 cards total
#[derive(Resource)]
pub(crate) struct Deck {
    pub cards: HashMap<Card, Sprite>,
}

impl Deck {
    pub(crate) fn cards() -> Vec<Card> {
        let mut v = Vec::new();

        [ItemColor::Red, ItemColor::Green, ItemColor::Purple]
            .iter()
            .for_each(|&color| {
                [ItemShape::Oval, ItemShape::Diamond, ItemShape::Squiggle]
                    .iter()
                    .for_each(|&shape| {
                        [ItemPattern::Open, ItemPattern::Solid, ItemPattern::Striped]
                            .iter()
                            .for_each(|&pattern| {
                                [ItemNumber::One, ItemNumber::Two, ItemNumber::Three]
                                    .iter()
                                    .for_each(|&number| {
                                        v.push(Card {
                                            color,
                                            shape,
                                            pattern,
                                            number,
                                        })
                                    });
                            });
                    });
            });
        v
    }

    pub(crate) fn iter_cards() -> impl Iterator<Item = Card> {
        Self::cards().into_iter()
    }

    pub(crate) fn shuffled() -> Vec<Card> {
        let mut base = Self::cards();

        {
            use rand::seq::SliceRandom;
            use rand::thread_rng;

            let mut rng = thread_rng();

            base.as_mut_slice().shuffle(&mut rng);
        }
        base
    }

    pub(crate) fn iter_shuffled() -> impl Iterator<Item = Card> {
        Deck::shuffled().into_iter()
    }
}

/// A card describes abstractly the contents of a card
/// including color of items, number of items, pattern, and shape
#[derive(PartialEq, Eq, Hash, Clone, Copy, Component, Debug)]
pub(crate) struct Card {
    pub color: ItemColor,
    pub number: ItemNumber,
    pub pattern: ItemPattern,
    pub shape: ItemShape,
}

impl Card {
    pub(crate) fn sprite(&self) -> (String, TextureAtlasLayout, u8) {
        let fname = {
            let col = match self.color {
                ItemColor::Red => "red",
                ItemColor::Green => "green",
                ItemColor::Purple => "purple",
            };
            let pat = match self.pattern {
                ItemPattern::Solid => "filled",
                ItemPattern::Striped => "shaded",
                ItemPattern::Open => "open",
            };
            let shape = match self.shape {
                ItemShape::Oval => "circle",
                ItemShape::Diamond => "diamond",
                ItemShape::Squiggle => "squiggle",
            };
            format!("{}_{}_{}.png", pat, shape, col)
        };
        let num = match self.number {
            ItemNumber::One => 0,
            ItemNumber::Two => 1,
            ItemNumber::Three => 2,
        };
        let size = UVec2 { x: 20, y: 32 };
        let layout = TextureAtlasLayout::from_grid(size, 3, 1, None, None);

        (fname, layout, num)
    }
}

impl std::fmt::Display for Card {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "{} {} {} {}",
            self.number, self.pattern, self.color, self.shape
        )
    }
}

/// Item colors for the card may be red, green or purple
/// All items must be the same color
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub(crate) enum ItemColor {
    Red,
    Green,
    Purple,
}

impl std::fmt::Display for ItemColor {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let color = match self {
            ItemColor::Red => "red",
            ItemColor::Green => "green",
            ItemColor::Purple => "purple",
        };
        write!(f, "{}", color)
    }
}

/// A card may have 1 to 3 items
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub(crate) enum ItemNumber {
    One,
    Two,
    Three,
}

impl std::fmt::Display for ItemNumber {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let num = match self {
            ItemNumber::One => 1,
            ItemNumber::Two => 2,
            ItemNumber::Three => 3,
        };
        write!(f, "{}", num)
    }
}

/// Each item can be solid, striped, or open
/// a card has all of it's item as one pattern
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub(crate) enum ItemPattern {
    Solid,
    Striped,
    Open,
}

impl std::fmt::Display for ItemPattern {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let fill = match self {
            ItemPattern::Open => "empty",
            ItemPattern::Striped => "shaded",
            ItemPattern::Solid => "solid",
        };
        write!(f, "{}", fill)
    }
}

/// Each item can be an oval, diamond, or squiggle
/// All items on a card are one shape
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub(crate) enum ItemShape {
    Oval,
    Diamond,
    Squiggle,
}

impl std::fmt::Display for ItemShape {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let shape = match self {
            ItemShape::Oval => "oval",
            ItemShape::Diamond => "diamond",
            ItemShape::Squiggle => "squiggle",
        };
        write!(f, "{}", shape)
    }
}
