use bevy::prelude::*;

use crate::{deck::Card, menu::UiMessage, setup::CardAnimations, GameState};

pub struct PlayPlugin;

impl Plugin for PlayPlugin {
    fn build(&self, app: &mut App) {
        app.add_event::<ServeCards>()
            .add_observer(serve_cards)
            .add_systems(OnEnter(GameState::Main), deal_cards)
            .add_systems(Update, deal_cards.run_if(set_added));
    }
}

/// Marker for cards which the player has selected
#[derive(Component)]
pub(crate) struct Selected;

/// Where in the deck a card is
#[derive(Component)]
pub(crate) struct DeckOrder(pub u8);

/// Where on the board/table/play-space a card is
#[derive(Component)]
pub(crate) struct PlayLocation {
    pub x: u8,
    pub y: u8,
}

/// Which captured set a card is associated with
#[derive(Component)]
pub(crate) struct SetNumber(pub u8);

/// Observer system for adding/removing selected components when a card is clicked
pub(crate) fn toggle_selected(
    trigger: Trigger<Pointer<Click>>,
    mut commands: Commands,
    query: Query<&Selected>,
    mut players: Query<(&mut AnimationPlayer, &CardAnimations)>,
) {
    commands.trigger(UiMessage("".into()));
    let e = trigger.entity();
    if query.contains(e) {
        commands.entity(e).remove::<Selected>();
        info!("stopping animation");
        let (mut player, anims) = players.get_mut(e).unwrap();
        player.stop(anims.rotate);
    } else {
        commands.entity(e).insert(Selected);
        info!("starting animation");
        let (mut player, anims) = players.get_mut(e).unwrap();
        player.play(anims.rotate);
    }
}

/// Observer system for resetting rotation when a card is de-selected
pub(crate) fn reset_rotation(
    trigger: Trigger<OnRemove, Selected>,
    mut transforms: Query<&mut Transform>,
) {
    let mut t = transforms.get_mut(trigger.entity()).unwrap();
    t.rotation = Quat::default();
}

/// Check a set when the "Set" button is clicked
pub(crate) fn check_set(
    _trigger: Trigger<Pointer<Click>>,
    mut query: Query<(Entity, &Card, &mut Visibility, &mut Transform), With<Selected>>,
    sets: Query<&SetNumber>,
    mut commands: Commands,
) {
    let mut cards = query.iter();
    if cards.len() == 3 {
        let ((_, a, _, _), (_, b, _, _), (_, c, _, _)) = (
            cards.next().unwrap(),
            cards.next().unwrap(),
            cards.next().unwrap(),
        );
        match is_set((a, b, c)) {
            Ok(()) => {
                query
                    .iter_mut()
                    .for_each(|(entity, _, mut visibility, mut transform)| {
                        *visibility = Visibility::Hidden;
                        *transform = Transform::default();
                        let set_number = sets.iter().len() as u8 / 3 + 1;
                        commands
                            .entity(entity)
                            .remove::<PlayLocation>()
                            .remove::<Selected>()
                            .insert(SetNumber(set_number));
                    });
                commands.trigger(UiMessage("Yipee!".into()));
            }
            Err(invalid_set_err) => {
                commands.trigger(UiMessage(format!("{}", invalid_set_err)));
            }
        }
    } else if cards.len() > 3 {
        commands.trigger(UiMessage("Too many cards!".into()));
    } else if cards.len() < 3 {
        commands.trigger(UiMessage("Not enough cards!".into()));
    }
}

struct InvalidSetErr {
    color: bool,
    number: bool,
    pattern: bool,
    shape: bool,
}

impl std::fmt::Display for InvalidSetErr {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let mut issues = vec![];
        if !self.color {
            issues.push("Color is an issue...");
        }
        if !self.number {
            issues.push("Number is an issue...");
        }
        if !self.pattern {
            issues.push("Pattern is an issue...");
        }
        if !self.shape {
            issues.push("Shapes is an issue...");
        }
        write!(f, "{}", issues.join("\n"))
    }
}

/// Helper function to determine if three cards constitute a set
fn is_set((a, b, c): (&Card, &Card, &Card)) -> Result<(), InvalidSetErr> {
    let color = {
        ((a.color == b.color) && (b.color == c.color) && (c.color == a.color))
            || ((a.color != b.color) && (b.color != c.color) && (c.color != a.color))
    };
    let number = {
        ((a.number == b.number) && (b.number == c.number) && (c.number == a.number))
            || ((a.number != b.number) && (b.number != c.number) && (c.number != a.number))
    };
    let pattern = {
        ((a.pattern == b.pattern) && (b.pattern == c.pattern) && (c.pattern == a.pattern))
            || ((a.pattern != b.pattern) && (b.pattern != c.pattern) && (c.pattern != a.pattern))
    };
    let shape = {
        ((a.shape == b.shape) && (b.shape == c.shape) && (c.shape == a.shape))
            || ((a.shape != b.shape) && (b.shape != c.shape) && (c.shape != a.shape))
    };
    if color && number && pattern && shape {
        Ok(())
    } else {
        Err(InvalidSetErr {
            color,
            number,
            shape,
            pattern,
        })
    }
}

#[derive(Event, Clone)]
struct ServeCards;

/// When requested, fill in empty spots on the board with cards
fn serve_cards(
    _trigger: Trigger<ServeCards>,
    in_deck: Query<(Entity, &Card, &DeckOrder)>,
    spots: Query<&PlayLocation>,
    mut commands: Commands,
) {
    info!(
        "Serving cards from deck ({} cards left)",
        in_deck.iter().len()
    );
    let mut n = in_deck.iter().len().saturating_sub(1);

    // Iterate over every x, y play location
    for this_x in 0..=3 {
        for this_y in 0..=3 {
            // If this spot does not have a card
            if spots
                .iter()
                .find(|PlayLocation { x, y }| *x == (this_x as u8) && *y == (this_y as u8))
                .is_none()
            {
                // If we have more than 9 cards on the board
                let candidate = in_deck
                    .iter()
                    .find(|(_entity, _deck_card, order)| order.0 == n as u8);
                n = n.saturating_sub(1);
                // If that search was fruitful, pull the card from the deck and play it
                if let Some((e, _, _)) = candidate {
                    commands
                        .entity(e)
                        .remove::<DeckOrder>()
                        .insert(PlayLocation {
                            x: this_x,
                            y: this_y,
                        });
                }
            }
        }
    }
}

/// Trigger dealing cards when the game starts
fn deal_cards(mut commands: Commands) {
    commands.trigger(ServeCards);
}

fn set_added(query: Query<Entity, Added<SetNumber>>) -> bool {
    !query.is_empty()
}

/// When a card is added to the board, place it on the screen
pub(crate) fn place_card(
    trigger: Trigger<OnAdd, PlayLocation>,
    mut query: Query<(&PlayLocation, &mut Visibility, &mut Transform)>,
) {
    let (PlayLocation { x, y }, mut visibility, mut transform) =
        query.get_mut(trigger.entity()).unwrap();

    // Set it's transform based on it's placement
    let card_size = [100.0, 160.0];
    let offset = Vec2::new(card_size[0] * 4.0, card_size[1] * 4.0) / 2.5;
    transform.translation = Vec3::new(
        ((*x as f32) * card_size[0]) - offset.x,
        ((*y as f32) * card_size[1]) - offset.y,
        0.0,
    );

    // Set it to visible
    *visibility = Visibility::Inherited;
}

pub(crate) fn check_for_sets(
    _trigger: Trigger<Pointer<Click>>,
    cards: Query<(Entity, &Card), With<PlayLocation>>,
    selected: Query<Entity, With<Selected>>,
    mut commands: Commands,
) {
    let candidate = cards
        // Iterate over all combinations of cards on the board
        .iter_combinations()
        // Only find combinations including currently selected cards
        .filter(|[(ea, _ca), (eb, _cb), (ec, _cc)]| {
            // If selected is empty, proceed
            match selected.iter().len() {
                0 => true,
                1..=3 => {
                    let a = selected.contains(*ea);
                    let b = selected.contains(*eb);
                    let c = selected.contains(*ec);
                    match selected.iter().len() {
                        1 => a || b || c,
                        2 => a && b || b && c || a && c,
                        3 => a && b && c,
                        _ => panic!("WTF?"),
                    }
                }
                _ => false,
            }
        })
        // return the first valid set
        .find_map(|[(ea, ca), (eb, cb), (ec, cc)]| {
            if is_set((ca, cb, cc)).is_ok() {
                info!("\n\t{}\n\t{}\n\t{}", ca, cb, cc);
                Some((ea, eb, ec))
            } else {
                None
            }
        });

    if let Some((a, b, c)) = candidate {
        // Message for adding
        match selected.iter().len() {
            0 => commands.trigger(UiMessage("First one's free!".into())),
            1 => commands.trigger(UiMessage("This one is tricky!".into())),
            2 => commands.trigger(UiMessage("Threes company!".into())),
            _ => commands.trigger(UiMessage("I don't feel so good...".into())),
        };
        // Add the selected component to the next entity
        if !selected.contains(a) {
            commands.entity(a).insert(Selected);
        } else if !selected.contains(b) {
            commands.entity(b).insert(Selected);
        } else if !selected.contains(c) {
            commands.entity(c).insert(Selected);
        } else {
            commands.trigger(UiMessage("Lock it in!".into()));
        }
    } else {
        commands.trigger(UiMessage("I'm stumped!".into()));
    }
}
