use std::ops::Add;

use bevy::ecs::error::Result;
use bevy::input::keyboard::{Key, KeyboardInput};
use bevy::prelude::*;

use crate::consts::{
    EVEN_COLOR,
    GAME_AREA,
    HOVER_COLOR,
    ODD_COLOR,
    RESOLUTION,
    TITLE_COLOR,
    Y_OFFSET,
};
use crate::resources::{AppState, Chronometer, Easy, Modifiers, MonospaceFont, Mute, NumbersFont, SFX};

use super::board::Board;
use super::cell_size::CellSize;
use super::current_cell::CurrentCell;
use super::marks::{Column, Row};
use super::{Cell, LevelSize};

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Component)]
pub struct Gameplay;

impl Gameplay {

    pub fn load(
        mut commands: Commands,
        paused_query: Query<Entity, With<PausedFlag>>,
        level_size: Res<LevelSize>,
        easy: Res<Easy>,
        numbers_font: Res<NumbersFont>,
        monospace_font: Res<MonospaceFont>,
        mute: Res<Mute>,
    ) {
        if let Ok(paused) = paused_query.single() {
            commands.entity(paused).despawn();
        } else {
            let board = level_size.board(easy.0);
            let (width, height) = board.size.clone();
            let cell_width = GAME_AREA.x / width as f32;
            let cell_height = GAME_AREA.y / height as f32;
            let cell_size = Vec2::new(cell_width, cell_height);
            commands.insert_resource(CellSize::new(cell_size));
            commands.insert_resource(CurrentCell::default());
            commands.insert_resource(Chronometer::default());
            Self::render_board_background(
                commands.reborrow(),
                &board,
                &numbers_font,
                &monospace_font,
                cell_size,
                mute.0,
            );
            Self::render_board(commands.reborrow(), &board, cell_size);
            commands.spawn((Self, OriginalBoard(board.clone())));
            commands.spawn((Self, board));
        }
    }

    pub fn unload(
        mut commands: Commands,
        paused_query: Query<Entity, With<PausedFlag>>,
        entities_query: Query<Entity, With<Self>>,
    ) {
        if paused_query.is_empty() {
            for entity in &entities_query {
                commands.entity(entity).despawn();
            }
            commands.remove_resource::<CellSize>();
            commands.remove_resource::<CurrentCell>();
        }
    }

    pub fn key_events(
        mut commands: Commands,
        mut board_query: Query<&mut Board>,
        original_board_query: Query<&OriginalBoard>,
        board_render_query: Query<Entity, With<BoardRender>>,
        mut mute_display_query: Query<&mut Visibility, With<MuteFlag>>,
        mut keyboard: EventReader<KeyboardInput>,
        mut next_state: ResMut<NextState<AppState>>,
        cell_size: Res<CellSize>,
        mut mute: ResMut<Mute>,
    ) -> Result<()> {
        for (input, _) in keyboard.par_read() {
            if input.state.is_pressed() && !input.repeat {
                match &input.logical_key {
                    Key::Escape => {
                        next_state.set(AppState::Title);
                        return Ok(());
                    }

                    Key::Pause => {
                        commands.spawn(PausedFlag);
                        next_state.set(AppState::Paused);
                        return Ok(());
                    }

                    Key::F2 => {
                        let mut mute_display = mute_display_query.single_mut()?;
                        *mute = mute.not();
                        *mute_display = if mute.0 {
                            Visibility::Visible
                        } else {
                            Visibility::Hidden
                        };
                    }

                    Key::Character(ch) if ch == "r" || ch == "R" => {
                        let mut board = board_query.single_mut()?;
                        let original = original_board_query.single()?;
                        *board = (*original).clone();
                        for entity in &board_render_query {
                            commands.entity(entity).despawn();
                        }
                        Self::render_board(commands.reborrow(), &board, **cell_size);
                    }

                    _ => (),
                }
            }
        }
        Ok(())
    }

    fn render_board(
        mut commands: Commands,
        board: &Board,
        cell_size: Vec2,
    ) {
        let (width, height) = board.size.clone();
        for iy in 0..height {
            for ix in 0..width {
                let cell = board.get(ix, iy);
                let mut ecomm = commands
                    .spawn((
                        BoardRender,
                        Column(ix),
                        Row(iy),
                        BackgroundColor(cell.into()),
                        Node {
                            position_type: PositionType::Absolute,
                            top: Val::Px(Y_OFFSET + cell_size.y * iy as f32 + 1.0),
                            left: Val::Px(cell_size.x * ix as f32 + 1.0),
                            width: Val::Px(cell_size.x - 2.0),
                            height: Val::Px(cell_size.y - 2.0),
                            ..default()
                        }
                    ))
                ;
                if !cell.locked() {
                    ecomm
                        .observe(Self::click_observer)
                        .observe(Self::finish_clicking_observer)
                        .observe(Self::hover_observer)
                        .observe(Self::drag_observer)
                    ;
                }
            }
        }
    }

    pub fn update_time(
        mut query: Query<&mut Text, With<ChronometerFlag>>,
        mut chronometer: ResMut<Chronometer>,
        time: Res<Time>,
    ) {
        **chronometer = chronometer.add(time.delta());
        if let Ok(mut display) = query.single_mut() {
            let seconds = chronometer.as_secs();
            let minutes = seconds / 60;
            let seconds = seconds % 60;
            *display = format!("{:02}:{:02}", minutes, seconds).into();
        }
    }

    fn render_board_background(
        mut commands: Commands,
        board: &Board,
        numbers_font: &Res<NumbersFont>,
        monospace_font: &Res<MonospaceFont>,
        cell_size: Vec2,
        mute: bool,
    ) {
        commands.spawn((
            ChronometerFlag,
            Text::new("00:00"),
            TextColor(TITLE_COLOR),
            TextFont {
                font: monospace_font.clone_weak(),
                font_size: 32.0,
                ..default()
            },
            Node {
                position_type: PositionType::Absolute,
                top: Val::Px(4.0),
                right: Val:: Px(4.0),
                ..default()
            },
        ));
        commands.spawn((
            MuteFlag,
            Text::new("Mute"),
            TextColor(TITLE_COLOR),
            TextFont {
                font: numbers_font.clone_weak(),
                font_size: 32.0,
                ..default()
            },
            if mute { Visibility::Visible } else { Visibility::Hidden },
            Node {
                position_type: PositionType::Absolute,
                top: Val::Px(38.0),
                right: Val:: Px(4.0),
                ..default()
            },
        ));

        for x in 0..board.size.0 {
            let color = if x % 2 == 0 {
                EVEN_COLOR
            } else {
                ODD_COLOR
            };
            let mut current = commands.spawn((
                Self,
                Column(x),
                BackgroundColor(color),
                Node {
                    position_type: PositionType::Absolute,
                    width: Val::Px(cell_size.x),
                    height: Val::Px(Y_OFFSET),
                    left: Val::Px(x as f32 * cell_size.x),
                    ..default()
                },
            ));
            let count = board.col_hints.iter()
                .map(|v| v.len())
                .reduce(|a, b| a.max(b))
                .unwrap_or(1)
                .max(1);
            let maximum = *board.col_hints.iter()
                .map(|v| v.iter().reduce(|a, b| a.max(b)).unwrap_or(&0))
                .max()
                .unwrap_or(&0);
            let font_size = if maximum < 10 {
                cell_size
            } else {
                cell_size / 1.5
            };
            let font_size = font_size.x.min(Y_OFFSET / count as f32);
            current
                .with_children(|commands|
                    for (i, hint) in board.col_hints[x].iter().enumerate() {
                        commands.spawn((
                            Self,
                            Text(format!("{}", hint)),
                            TextColor(Color::BLACK),
                            TextFont {
                                font: numbers_font.clone_weak(),
                                font_size,
                                ..default()
                            },
                            Node {
                                position_type: PositionType::Absolute,
                                left: Val::Px(font_size / 2.0),
                                top: Val::Px(i as f32 * font_size),
                                ..default()
                            },
                        ));
                    }
                )
            ;
            if x % 5 == 0 {
                commands.spawn((
                    Self,
                    BackgroundColor(Color::BLACK),
                    Node {
                        position_type: PositionType::Absolute,
                        width: Val::Px(2.0),
                        height: Val::Px(GAME_AREA.y),
                        top: Val::Px(Y_OFFSET),
                        left: Val::Px(x as f32 * cell_size.x - 1.0),
                        ..default()
                    },
                ));
            }
        }

        let larger = match board.size.0 {
            15 => 14,
            10 => 8,
            _ => 7,
        };
        for y in 0..board.size.1 {
            let color = if y % 2 == 0 {
                EVEN_COLOR
            } else {
                ODD_COLOR
            };
            let mut current = commands.spawn((
                Self,
                Row(y),
                BackgroundColor(color),
                Node {
                    position_type: PositionType::Absolute,
                    width: Val::Px(GAME_AREA.x / 2.0),
                    height: Val::Px(cell_size.y),
                    top: Val::Px(Y_OFFSET + y as f32 * cell_size.y),
                    left: Val::Px(GAME_AREA.x),
                    ..default()
                },
            ));
            let count = board.row_hints.iter()
                .map(|v| v.len())
                .reduce(|a, b| a.max(b))
                .unwrap_or(1)
                .max(1);
            let count = count + count / 2;
            let font_size = cell_size.y.min(GAME_AREA.x / 2.0 / count as f32);
            current.with_children(|commands| {
                let text = board.row_hints[y].iter()
                    .map(|v| v.to_string())
                    .reduce(|a, b| format!("{}, {}", a, b))
                    .unwrap_or_else(String::new);
                let font_size = if text.len() > larger {
                    font_size / 1.5
                } else {
                    font_size
                };
                commands.spawn((
                    Self,
                    Text(text),
                    TextColor(Color::BLACK),
                    TextFont {
                        font: numbers_font.clone_weak(),
                        font_size,
                        ..default()
                    },
                    Node {
                        position_type: PositionType::Absolute,
                        left: Val::Px(4.0),
                        ..default()
                    },
                ));
            });
            if y % 5 == 4 {
                commands.spawn((
                    Self,
                    BackgroundColor(Color::BLACK),
                    Node {
                        position_type: PositionType::Absolute,
                        width: Val::Px(GAME_AREA.x),
                        height: Val::Px(2.0),
                        top: Val::Px(Y_OFFSET + (y + 1) as f32 * cell_size.y - 1.0),
                        left: Val::ZERO,
                        ..default()
                    },
                ));
            }
        }
    }

    pub fn update_background(
        window_query: Query<&Window>,
        mut col_query: Query<(&Column, &mut BackgroundColor), Without<Row>>,
        mut row_query: Query<(&Row, &mut BackgroundColor), Without<Column>>,
        cell_size: Res<CellSize>,
    ) -> Result<()> {
        let window = window_query.single()?;
        if let Some(cursor) = window.cursor_position() {
            let ix = (cursor.x / cell_size.x) as usize;
            let iy = if cursor.y < Y_OFFSET {
                RESOLUTION.y as usize
            } else {
                ((cursor.y - Y_OFFSET) / cell_size.y) as usize
            };
            for (column, mut color) in col_query.iter_mut() {
                if column.0 == ix {
                    color.0 = HOVER_COLOR;
                } else if column.0 % 2 == 0 {
                    color.0 = EVEN_COLOR;
                } else {
                    color.0 = ODD_COLOR;
                }
            }
            for (row, mut color) in row_query.iter_mut() {
                if row.0 == iy {
                    color.0 = HOVER_COLOR;
                } else if row.0 % 2 == 0 {
                    color.0 = EVEN_COLOR;
                } else {
                    color.0 = ODD_COLOR;
                }
            }
        }
        Ok(())
    }

    pub fn watch_focus(
        mut commands: Commands,
        window_query: Query<&Window>,
        mut next_state: ResMut<NextState<AppState>>,
    ) -> Result<()> {
        let window = window_query.single()?;
        if !window.focused {
            commands.spawn(PausedFlag);
            next_state.set(AppState::Paused);
        }
        Ok(())
    }

    pub fn watch_click(
        input: Res<ButtonInput<MouseButton>>,
        mut cell: ResMut<CurrentCell>,
        modifiers: Res<Modifiers>,
    ) {
        if input.just_released(MouseButton::Left)
        || input.just_released(MouseButton::Middle)
        || input.just_released(MouseButton::Right) {
            **cell = None;
        }
        else if cell.is_none() {
            let lmb = input.just_pressed(MouseButton::Left);
            let rmb = input.just_pressed(MouseButton::Right);
            let mmb = input.just_pressed(MouseButton::Middle);
            if lmb && modifiers.is_clean() {
                **cell = Some(Cell::Yes);
            }
            else if rmb || (lmb && modifiers.ctrl) {
                **cell = Some(Cell::No);
            }
            else if mmb || (lmb && modifiers.shift) {
                **cell = Some(Cell::MarkedClosed);
            }
        }
    }

    pub fn check_gameover(
        mut commands: Commands,
        query: Query<&Board>,
        mut next_state: ResMut<NextState<AppState>>,
    ) -> Result<()> {
        let board = query.single()?;
        if board.is_done() {
            commands.spawn(PausedFlag);
            next_state.set(AppState::GameOver);
        }
        Ok(())
    }

    fn play(
        mut commands: Commands,
        query: &Query<Entity, With<AudioPlayer>>,
        sfx: &Res<SFX>,
        mute: bool,
        cell: Cell,
    ) {
        if mute {
            return;
        }

        if query.is_empty() {
            let source = match cell {
                Cell::Closed => sfx.unset.clone_weak(),
                Cell::MarkedClosed => return,
                Cell::Yes => sfx.set.clone_weak(),
                Cell::No => sfx.lock.clone_weak(),
                Cell::LockedYes |
                Cell::LockedNo => sfx.error.clone_weak(),
            };
            commands.spawn((
                Self,
                AudioPlayer::new(source),
                PlaybackSettings::DESPAWN,
            ));
        }
    }

    fn play_error(
        mut commands: Commands,
        sfx: &Res<SFX>,
        mute: bool,
    ) {
        if !mute {
            commands.spawn((
                Self,
                AudioPlayer::new(sfx.error.clone_weak()),
                PlaybackSettings::DESPAWN,
            ));
        }
    }

    fn finish_clicking_observer(
        mut trigger: Trigger<Pointer<Released>>,
        mut commands: Commands,
        mut board_query: Query<&mut Board>,
        mut cells_query: Query<(Entity, &Column, &Row, &mut BackgroundColor)>,
        audio_query: Query<Entity, With<AudioPlayer>>,
        sfx: Res<SFX>,
        mute: Res<Mute>,
        mut current_cell: ResMut<CurrentCell>,
    ) -> Result<()> {
        let mut board = board_query.single_mut()?;
        for (id, column, row, mut color) in &mut cells_query {
            if id == trigger.target() {
                let desired = current_cell.unwrap_or(Cell::MarkedClosed);
                let mute = **mute || desired == Cell::MarkedClosed;
                if board.set(column.0, row.0, desired) {
                    color.0 = desired.into();
                    Self::play(
                        commands.reborrow(),
                        &audio_query,
                        &sfx,
                        mute,
                        desired,
                    );
                } else {
                    Self::play_error(commands.reborrow(), &sfx, mute);
                }
                break
            }
        }
        **current_cell = None;
        trigger.propagate(false);
        Ok(())
    }

    fn click_observer(
        mut trigger: Trigger<Pointer<Click>>,
        board_query: Query<&Board>,
        cells_query: Query<(Entity, &Column, &Row)>,
        mut current_cell: ResMut<CurrentCell>,
    ) -> Result<()> {
        let board = board_query.single()?;
        for (id, column, row) in &cells_query {
            if id == trigger.target() {
                let desired = current_cell.unwrap_or(Cell::MarkedClosed);
                if board.get(column.0, row.0) == desired {
                    **current_cell = Some(Cell::Closed);
                }
                else if desired == Cell::Yes || desired == Cell::No {
                    **current_cell = Some(desired);
                }
                break
            }
        }
        trigger.propagate(false);
        Ok(())
    }

    fn drag_observer(
        mut trigger: Trigger<Pointer<DragStart>>,
        mut commands: Commands,
        mut board_query: Query<&mut Board>,
        mut cells_query: Query<(Entity, &Column, &Row, &mut BackgroundColor)>,
        audio_query: Query<Entity, With<AudioPlayer>>,
        input: Res<ButtonInput<MouseButton>>,
        current_cell: Res<CurrentCell>,
        sfx: Res<SFX>,
        mute: Res<Mute>,
    ) -> Result<()> {
        Self::drag_hover_observers(
            trigger.target(),
            commands.reborrow(),
            &mut board_query,
            &mut cells_query,
            &audio_query,
            &current_cell,
            &input,
            &sfx,
            **mute,
        )?;
        trigger.propagate(false);
        Ok(())
    }

    fn hover_observer(
        mut trigger: Trigger<Pointer<Over>>,
        mut commands: Commands,
        mut board_query: Query<&mut Board>,
        mut cells_query: Query<(Entity, &Column, &Row, &mut BackgroundColor)>,
        current_cell: Res<CurrentCell>,
        audio_query: Query<Entity, With<AudioPlayer>>,
        sfx: Res<SFX>,
        mute: Res<Mute>,
        input: Res<ButtonInput<MouseButton>>,
    ) -> Result<()> {
        Self::drag_hover_observers(
            trigger.target(),
            commands.reborrow(),
            &mut board_query,
            &mut cells_query,
            &audio_query,
            &current_cell,
            &input,
            &sfx,
            **mute,
        )?;
        trigger.propagate(false);
        Ok(())
    }

    fn drag_hover_observers(
        target: Entity,
        mut commands: Commands,
        board_query: &mut Query<&mut Board>,
        cells_query: &mut Query<(Entity, &Column, &Row, &mut BackgroundColor)>,
        audio_query: &Query<Entity, With<AudioPlayer>>,
        current_cell: &Res<CurrentCell>,
        input: &Res<ButtonInput<MouseButton>>,
        sfx: &Res<SFX>,
        mute: bool,
    ) -> Result<()> {
        // Workaround unmark/remark issue
        if input.just_pressed(MouseButton::Left)
        || input.just_pressed(MouseButton::Middle)
        || input.just_pressed(MouseButton::Right) {
            return Ok(());
        }
        if let Some(cell) = ***current_cell {
            let mut board = board_query.single_mut()?;
            for (id, column, row, mut color) in cells_query {
                if id == target {
                    if board.set(column.0, row.0, cell) {
                        color.0 = cell.into();
                        Self::play(commands.reborrow(), &audio_query, sfx, mute, cell);
                    } else {
                        Self::play_error(commands.reborrow(), sfx, mute);
                    }
                    break
                }
            }
        }
        Ok(())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Component)]
#[require(Gameplay)]
pub struct BoardRender;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Component)]
#[require(Gameplay)]
pub struct BoardElement;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Component)]
#[require(Gameplay)]
pub struct PausedFlag;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Component)]
#[require(Gameplay)]
pub struct ChronometerFlag;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Component)]
#[require(Gameplay)]
pub struct MuteFlag;

#[derive(Debug, Deref, Component)]
#[require(Gameplay)]
pub struct OriginalBoard(Board);
