//! Handle player input and translate it into movement through a character
//! controller. A character controller is the collection of systems that govern
//! the movement of characters.
//!
//! In our case, the character controller has the following logic:
//! - Set [`MovementController`] intent based on directional keyboard input.
//!   This is done in the `player` module, as it is specific to the player
//!   character.
//! - Apply movement based on [`MovementController`] intent and maximum speed.
//! - Wrap the character within the window.
//!
//! Note that the implementation used here is limited for demonstration
//! purposes. If you want to move the player in a smoother way,
//! consider using a [fixed timestep](https://github.com/bevyengine/bevy/blob/main/examples/movement/physics_in_fixed_timestep.rs).

use bevy::{prelude::*, window::PrimaryWindow};
use bevy_ecs_ldtk::{GridCoords, LevelSelection};

use crate::{AppSystems, PausableSystems};

use super::{
    level::{LevelWalls, MultiLevelWalls},
    player::Player,
};

pub(super) fn plugin(app: &mut App) {
    app.register_type::<MovementController>();
    app.register_type::<ScreenWrap>();

    app.add_systems(
        Update,
        (apply_movement, apply_screen_wrap)
            .chain()
            .in_set(AppSystems::Update)
            .in_set(PausableSystems),
    );
}

/// These are the movement parameters for our character controller.
/// For now, this is only used for a single player, but it could power NPCs or
/// other players as well.
#[derive(Component, Debug, Reflect)]
#[reflect(Component)]
pub struct MovementController {
    /// The direction the character wants to move in.
    pub intent: Vec2,

    /// Maximum speed in world units per second.
    /// 1 world unit = 1 pixel when using the default 2D camera and no physics engine.
    pub max_speed: f32,
}

impl Default for MovementController {
    fn default() -> Self {
        Self {
            intent: Vec2::ZERO,
            // 400 pixels per second is a nice default, but we can still vary this per character.
            max_speed: 40.0,
        }
    }
}

fn apply_movement(
    _time: Res<Time>,
    movement_query: Query<&MovementController, With<Player>>,
    player_transform_query: Query<&Transform, With<Player>>,
    mut players: Query<&mut GridCoords, With<Player>>,
    level_selection: Res<LevelSelection>,
    // level_walls: Res<LevelWalls>,
    level_walls: Res<MultiLevelWalls>,
) {
    let level_selection_iid = match level_selection.as_ref() {
        LevelSelection::Iid(iid) => iid,
        _ => panic!("level should be selected by iid"),
    };

    for controller in &movement_query {
        // no velocity... for now? It'd be nice to have different speed depending on the tile type
        // let velocity = controller.max_speed * controller.intent;

        for mut player_grid_coords in players.iter_mut() {
            let movement_direction = if controller.intent.x == 1.0 {
                GridCoords::new(1, 0)
            } else if controller.intent.x == -1.0 {
                GridCoords::new(-1, 0)
            } else if controller.intent.y == 1.0 {
                GridCoords::new(0, 1)
            } else if controller.intent.y == -1.0 {
                GridCoords::new(0, -1)
            } else if controller.intent == Vec2::ZERO {
                // no movement
                continue;
            } else {
                // unrecognized intent, log a warning
                warn!("Unrecognized intent: {:?}", controller.intent);
                return;
            };
            info!("player old coords: {:?}", player_grid_coords);
            let destination = *player_grid_coords + movement_direction;
            info!("commanded movement player coords: {:?}", destination);
            info!("Level selection: {:?}", level_selection_iid);
            if !level_walls.in_wall(level_selection_iid, &destination) {
                *player_grid_coords = destination;
                info!("new player grid_coords: {:?}", player_grid_coords);
                info!(
                    "new player screen_coords: {:?}",
                    player_transform_query.single()
                );
                // transform.translation += velocity.extend(0.0) * time.delta_secs();
            } else {
                info!("SDENG!");
            }
            level_walls.debug_collisions(level_selection_iid, &player_grid_coords);
        }
    }
}

#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct ScreenWrap;

fn apply_screen_wrap(
    window: Single<&Window, With<PrimaryWindow>>,
    mut wrap_query: Query<&mut Transform, With<ScreenWrap>>,
) {
    let size = window.size() + 256.0;
    let half_size = size / 2.0;
    for mut transform in &mut wrap_query {
        let position = transform.translation.xy();
        let wrapped = (position + half_size).rem_euclid(size) - half_size;
        transform.translation = wrapped.extend(transform.translation.z);
    }
}
