use super::{ControlState, Controls};
use crate::{
    GameState,
    control::{CharacterInputEnabled, ControllerSet},
};
use bevy::{
    input::{
        ButtonState,
        gamepad::{GamepadConnection, GamepadEvent},
        mouse::{MouseButtonInput, MouseMotion},
    },
    prelude::*,
};
#[cfg(feature = "client")]
use lightyear::prelude::input::native::{ActionState, InputMarker};
use serde::{Deserialize, Serialize};

pub fn plugin(app: &mut App) {
    app.init_resource::<Controls>();

    app.register_type::<ControllerSettings>();

    app.add_systems(
        FixedUpdate,
        (
            gamepad_controls,
            keyboard_controls,
            mouse_rotate,
            mouse_click,
            gamepad_connections.run_if(on_event::<GamepadEvent>),
            combine_controls,
        )
            .chain()
            .in_set(ControllerSet::CollectInputs)
            .run_if(
                in_state(GameState::Playing)
                    .and(resource_exists_and_equals(CharacterInputEnabled::On)),
            ),
    );

    #[cfg(feature = "client")]
    {
        use lightyear::prelude::client::input::InputSet;
        app.add_systems(
            FixedPreUpdate,
            buffer_inputs.in_set(InputSet::WriteClientInputs),
        );
    }

    app.add_systems(
        Update,
        reset_control_state_on_disable.run_if(in_state(GameState::Playing)),
    );
}

#[derive(Component, Clone, PartialEq, Reflect, Serialize, Deserialize)]
#[reflect(Component)]
pub struct ControllerSettings {
    pub deceleration_factor: f32,
    pub jump_force: f32,
}

#[cfg(feature = "client")]
/// Write inputs from combined keyboard/gamepad state into the networked input buffer
/// for the local player.
fn buffer_inputs(
    mut player: Single<&mut ActionState<ControlState>, With<InputMarker<ControlState>>>,
    controls: Res<ControlState>,
) {
    player.0 = *controls;
}

/// Reset character inputs to default when character input is disabled.
fn reset_control_state_on_disable(
    state: Res<CharacterInputEnabled>,
    mut controls: ResMut<Controls>,
    mut control_state: ResMut<ControlState>,
) {
    if state.is_changed() && matches!(*state, CharacterInputEnabled::Off) {
        *controls = Controls::default();
        *control_state = ControlState::default();
    }
}

/// Take keyboard and gamepad state and combine them into unified input state
fn combine_controls(controls: Res<Controls>, mut combined_controls: ResMut<ControlState>) {
    let keyboard = controls.keyboard_state;
    let gamepad = controls.gamepad_state.unwrap_or_default();

    combined_controls.look_dir = gamepad.look_dir + keyboard.look_dir;
    combined_controls.move_dir = gamepad.move_dir + keyboard.move_dir;
    combined_controls.jump = gamepad.jump | keyboard.jump;
    combined_controls.view_mode = gamepad.view_mode | keyboard.view_mode;
    combined_controls.trigger = keyboard.trigger | gamepad.trigger;
    combined_controls.just_triggered = keyboard.just_triggered | gamepad.just_triggered;
    combined_controls.select_left = gamepad.select_left | keyboard.select_left;
    combined_controls.select_right = gamepad.select_right | keyboard.select_right;
    combined_controls.backpack_toggle = gamepad.backpack_toggle | keyboard.backpack_toggle;
    combined_controls.backpack_swap = gamepad.backpack_swap | keyboard.backpack_swap;
    combined_controls.backpack_left = gamepad.backpack_left | keyboard.backpack_left;
    combined_controls.backpack_right = gamepad.backpack_right | keyboard.backpack_right;
    combined_controls.cash_heal = gamepad.cash_heal | keyboard.cash_heal;
}

/// Applies a square deadzone to a Vec2
fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
    Vec2::new(
        if v.x.abs() < min { 0. } else { v.x },
        if v.y.abs() < min { 0. } else { v.y },
    )
}

/// Collect gamepad inputs
fn gamepad_controls(gamepads: Query<(Entity, &Gamepad)>, mut controls: ResMut<Controls>) {
    let Some((_e, gamepad)) = gamepads.iter().next() else {
        if controls.gamepad_state.is_some() {
            controls.gamepad_state = None;
        }
        return;
    };

    let deadzone_left_stick = 0.15;
    let deadzone_right_stick = 0.15;

    let rotate = gamepad
        .get(GamepadButton::RightTrigger2)
        .unwrap_or_default();

    // 8BitDo Ultimate wireless Controller for PC
    let look_dir = if gamepad.vendor_id() == Some(11720) && gamepad.product_id() == Some(12306) {
        const EPSILON: f32 = 0.015;
        Vec2::new(
            if rotate < 0.5 - EPSILON {
                40. * (rotate - 0.5)
            } else if rotate > 0.5 + EPSILON {
                -40. * (rotate - 0.5)
            } else {
                0.
            },
            0.,
        )
    } else {
        deadzone_square(gamepad.right_stick(), deadzone_right_stick) * 40.
    };

    let state = ControlState {
        move_dir: deadzone_square(gamepad.left_stick(), deadzone_left_stick),
        look_dir,
        jump: gamepad.pressed(GamepadButton::South),
        view_mode: gamepad.pressed(GamepadButton::LeftTrigger2),
        trigger: gamepad.pressed(GamepadButton::RightTrigger2),
        just_triggered: gamepad.just_pressed(GamepadButton::RightTrigger2),
        select_left: gamepad.just_pressed(GamepadButton::LeftTrigger),
        select_right: gamepad.just_pressed(GamepadButton::RightTrigger),
        backpack_left: gamepad.just_pressed(GamepadButton::DPadLeft),
        backpack_right: gamepad.just_pressed(GamepadButton::DPadRight),
        backpack_swap: gamepad.just_pressed(GamepadButton::DPadDown),
        backpack_toggle: gamepad.just_pressed(GamepadButton::DPadUp),
        cash_heal: gamepad.just_pressed(GamepadButton::East),
    };

    if controls
        .gamepad_state
        .as_ref()
        .map(|last_state| *last_state != state)
        .unwrap_or(true)
    {
        controls.gamepad_state = Some(state);
    }
}

/// Collect mouse movement input
fn mouse_rotate(mut mouse: EventReader<MouseMotion>, mut controls: ResMut<Controls>) {
    controls.keyboard_state.look_dir = Vec2::ZERO;

    for ev in mouse.read() {
        controls.keyboard_state.look_dir += ev.delta;
    }
}

/// Collect keyboard input
fn keyboard_controls(keyboard: Res<ButtonInput<KeyCode>>, mut controls: ResMut<Controls>) {
    let up_binds = [KeyCode::KeyW, KeyCode::ArrowUp];
    let down_binds = [KeyCode::KeyS, KeyCode::ArrowDown];
    let left_binds = [KeyCode::KeyA, KeyCode::ArrowLeft];
    let right_binds = [KeyCode::KeyD, KeyCode::ArrowRight];

    let up = keyboard.any_pressed(up_binds);
    let down = keyboard.any_pressed(down_binds);
    let left = keyboard.any_pressed(left_binds);
    let right = keyboard.any_pressed(right_binds);

    let horizontal = right as i8 - left as i8;
    let vertical = up as i8 - down as i8;
    let direction = Vec2::new(horizontal as f32, vertical as f32).clamp_length_max(1.0);

    controls.keyboard_state.move_dir = direction;
    controls.keyboard_state.jump = keyboard.pressed(KeyCode::Space);
    controls.keyboard_state.view_mode = keyboard.pressed(KeyCode::Tab);
    controls.keyboard_state.backpack_toggle = keyboard.just_pressed(KeyCode::KeyB);
    controls.keyboard_state.backpack_swap = keyboard.just_pressed(KeyCode::Enter);
    controls.keyboard_state.backpack_left = keyboard.just_pressed(KeyCode::Comma);
    controls.keyboard_state.backpack_right = keyboard.just_pressed(KeyCode::Period);
    controls.keyboard_state.select_left = keyboard.just_pressed(KeyCode::KeyQ);
    controls.keyboard_state.select_right = keyboard.just_pressed(KeyCode::KeyE);
    controls.keyboard_state.cash_heal = keyboard.just_pressed(KeyCode::Enter);
}

/// Collect mouse button input when pressed
fn mouse_click(mut events: EventReader<MouseButtonInput>, mut controls: ResMut<Controls>) {
    controls.keyboard_state.just_triggered = false;

    for ev in events.read() {
        match ev {
            MouseButtonInput {
                button: MouseButton::Left,
                state: ButtonState::Pressed,
                ..
            } => {
                controls.keyboard_state.trigger = true;
                controls.keyboard_state.just_triggered = true;
            }
            MouseButtonInput {
                button: MouseButton::Left,
                state: ButtonState::Released,
                ..
            } => {
                controls.keyboard_state.trigger = false;
                controls.keyboard_state.just_triggered = false;
            }
            _ => {}
        }
    }
}

/// Receive gamepad connections and disconnections
fn gamepad_connections(mut evr_gamepad: EventReader<GamepadEvent>) {
    for ev in evr_gamepad.read() {
        if let GamepadEvent::Connection(connection) = ev {
            match &connection.connection {
                GamepadConnection::Connected {
                    name,
                    vendor_id,
                    product_id,
                } => {
                    info!(
                        "New gamepad connected: {:?}, name: {name}, vendor: {vendor_id:?}, product: {product_id:?}",
                        connection.gamepad,
                    );
                }
                GamepadConnection::Disconnected => {
                    info!("Lost connection with gamepad: {:?}", connection.gamepad);
                }
            }
        }
    }
}
