use crate::{
    GameState,
    head::ActiveHead,
    heads_database::{HeadControls, HeadsDatabase},
    player::Player,
};
use bevy::{ecs::entity::MapEntities, prelude::*};
use serde::{Deserialize, Serialize};

pub mod controller_common;
pub mod controller_flying;
pub mod controller_running;
pub mod controls;

#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Default)]
enum ControllerSet {
    CollectInputs,
    ApplyControlsFly,
    #[default]
    ApplyControlsRun,
}

#[derive(Resource, Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, Reflect)]
pub struct ControlState {
    /// Movement direction with a maximum length of 1.0
    pub move_dir: Vec2,
    pub look_dir: Vec2,
    pub jump: bool,
    /// Determines if the camera can rotate freely around the player
    pub view_mode: bool,
    pub trigger: bool,
    pub just_triggered: bool,
    pub select_left: bool,
    pub select_right: bool,
    pub backpack_toggle: bool,
    pub backpack_swap: bool,
    pub backpack_left: bool,
    pub backpack_right: bool,
    pub cash_heal: bool,
}

impl MapEntities for ControlState {
    fn map_entities<E: EntityMapper>(&mut self, _entity_mapper: &mut E) {}
}

#[derive(Resource, Debug, Default)]
struct Controls {
    keyboard_state: ControlState,
    gamepad_state: Option<ControlState>,
}

#[derive(Resource, Debug, PartialEq, Eq)]
pub enum CharacterInputEnabled {
    On,
    Off,
}

#[derive(Event)]
pub struct ControllerSwitchEvent;

#[derive(Resource, Debug, Default, PartialEq)]
pub struct SelectedController(ControllerSet);

pub fn plugin(app: &mut App) {
    app.init_resource::<SelectedController>();
    app.init_resource::<ControlState>();
    app.insert_resource(CharacterInputEnabled::On);

    app.add_plugins(controls::plugin);
    app.add_plugins(controller_common::plugin);
    app.add_plugins(controller_flying::CharacterControllerPlugin);
    app.add_plugins(controller_running::CharacterControllerPlugin);

    app.add_event::<ControllerSwitchEvent>();

    app.configure_sets(
        FixedUpdate,
        (
            ControllerSet::CollectInputs,
            ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController(
                ControllerSet::ApplyControlsFly,
            ))),
            ControllerSet::ApplyControlsRun.run_if(resource_equals(SelectedController(
                ControllerSet::ApplyControlsRun,
            ))),
        )
            .chain()
            .run_if(in_state(GameState::Playing)),
    );

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

fn head_change(
    //TODO: needs a 'LocalPlayer' at some point for multiplayer
    query: Query<&ActiveHead, (Changed<ActiveHead>, With<Player>)>,
    heads_db: Res<HeadsDatabase>,
    mut selected_controller: ResMut<SelectedController>,
    mut event_controller_switch: EventWriter<ControllerSwitchEvent>,
) {
    for head in query.iter() {
        let stats = heads_db.head_stats(head.0);
        let controller = match stats.controls {
            HeadControls::Plane => ControllerSet::ApplyControlsFly,
            HeadControls::Walk => ControllerSet::ApplyControlsRun,
        };

        if selected_controller.0 != controller {
            event_controller_switch.write(ControllerSwitchEvent);

            selected_controller.0 = controller;
        }
    }
}
