use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::gamepad::{self, Axis, AxisDirection, Button, PlayerControllerInputType};
use crate::framework::keyboard::ScanCode;
use crate::game::settings::{
    p1_default_keymap, p2_default_keymap, player_default_controller_button_map, ControllerType,
    PlayerControllerButtonMap, PlayerKeyMap,
};
use crate::game::shared_game_state::SharedGameState;
use crate::input::combined_menu_controller::CombinedMenuController;

use super::{ControlMenuData, Menu, MenuEntry, MenuSelectionResult};

const FORBIDDEN_SCANCODES: [ScanCode; 12] = [
    ScanCode::F1,
    ScanCode::F2,
    ScanCode::F3,
    ScanCode::F4,
    ScanCode::F5,
    ScanCode::F6,
    ScanCode::F7,
    ScanCode::F8,
    ScanCode::F9,
    ScanCode::F10,
    ScanCode::F11,
    ScanCode::F12,
];

#[derive(PartialEq, Eq, Clone, Debug)]
#[repr(u8)]
enum CurrentMenu {
    MainMenu,
    SelectControllerMenu,
    RebindMenu,
    ConfirmRebindMenu,
    ConfirmResetMenu,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum MainMenuEntry {
    SelectedPlayer,
    Controller,
    Rebind,
    Rumble,
    DisplayTouchControls,
    Back,
}

impl Default for MainMenuEntry {
    fn default() -> Self {
        #[cfg(target_os = "android")]
        return MainMenuEntry::DisplayTouchControls;

        #[cfg(not(target_os = "android"))]
        return MainMenuEntry::SelectedPlayer;
    }
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum SelectControllerMenuEntry {
    Keyboard,
    Gamepad(usize),
    Back,
}

impl Default for SelectControllerMenuEntry {
    fn default() -> Self {
        SelectControllerMenuEntry::Keyboard
    }
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum RebindMenuEntry {
    Control(ControlEntry),
    Reset,
    Back,
}

impl Default for RebindMenuEntry {
    fn default() -> Self {
        RebindMenuEntry::Control(ControlEntry::MenuOk)
    }
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum ConfirmResetMenuEntry {
    Title,
    Yes,
    No,
}

impl Default for ConfirmResetMenuEntry {
    fn default() -> Self {
        ConfirmResetMenuEntry::No
    }
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum Player {
    Player1,
    Player2,
}

impl Player {
    fn controller_type(self, state: &SharedGameState) -> ControllerType {
        match self {
            Player::Player1 => state.settings.player1_controller_type,
            Player::Player2 => state.settings.player2_controller_type,
        }
    }
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum ControlEntry {
    Left,
    Up,
    Right,
    Down,
    PrevWeapon,
    NextWeapon,
    Jump,
    Shoot,
    Skip,
    Inventory,
    Map,
    Strafe,
    MenuOk,
    MenuBack,
}

impl ControlEntry {
    fn to_string(&self, state: &SharedGameState) -> String {
        match self {
            ControlEntry::Left => state.loc.t("menus.controls_menu.rebind_menu.left"),
            ControlEntry::Up => state.loc.t("menus.controls_menu.rebind_menu.up"),
            ControlEntry::Right => state.loc.t("menus.controls_menu.rebind_menu.right"),
            ControlEntry::Down => state.loc.t("menus.controls_menu.rebind_menu.down"),
            ControlEntry::PrevWeapon => state.loc.t("menus.controls_menu.rebind_menu.prev_weapon"),
            ControlEntry::NextWeapon => state.loc.t("menus.controls_menu.rebind_menu.next_weapon"),
            ControlEntry::Jump => state.loc.t("menus.controls_menu.rebind_menu.jump"),
            ControlEntry::Shoot => state.loc.t("menus.controls_menu.rebind_menu.shoot"),
            ControlEntry::Skip => state.loc.t("menus.controls_menu.rebind_menu.skip"),
            ControlEntry::Inventory => state.loc.t("menus.controls_menu.rebind_menu.inventory"),
            ControlEntry::Map => state.loc.t("menus.controls_menu.rebind_menu.map"),
            ControlEntry::Strafe => state.loc.t("menus.controls_menu.rebind_menu.strafe"),
            ControlEntry::MenuOk => state.loc.t("menus.controls_menu.rebind_menu.menu_ok"),
            ControlEntry::MenuBack => state.loc.t("menus.controls_menu.rebind_menu.menu_back"),
        }
        .to_owned()
    }
}

pub struct ControlsMenu {
    current: CurrentMenu,
    main: Menu<MainMenuEntry>,
    select_controller: Menu<SelectControllerMenuEntry>,
    rebind: Menu<RebindMenuEntry>,
    confirm_rebind: Menu<usize>,
    confirm_reset: Menu<ConfirmResetMenuEntry>,

    selected_player: Player,
    selected_controller: ControllerType,
    selected_control: Option<ControlEntry>,

    player1_key_map: Vec<(ControlEntry, ScanCode)>,
    player2_key_map: Vec<(ControlEntry, ScanCode)>,
    player1_controller_button_map: Vec<(ControlEntry, PlayerControllerInputType)>,
    player2_controller_button_map: Vec<(ControlEntry, PlayerControllerInputType)>,

    input_busy: bool,
}

impl ControlsMenu {
    pub fn new() -> ControlsMenu {
        let main = Menu::new(0, 0, 220, 0);
        let select_controller = Menu::new(0, 0, 220, 0);
        let rebind = Menu::new(0, 0, 220, 0);
        let confirm_rebind = Menu::new(0, 0, 220, 0);
        let confirm_reset = Menu::new(0, 0, 160, 0);

        ControlsMenu {
            current: CurrentMenu::MainMenu,
            main,
            select_controller,
            rebind,
            confirm_rebind,
            confirm_reset,

            selected_player: Player::Player1,
            selected_controller: ControllerType::Keyboard,
            selected_control: None,

            player1_key_map: Vec::new(),
            player2_key_map: Vec::new(),
            player1_controller_button_map: Vec::new(),
            player2_controller_button_map: Vec::new(),

            input_busy: false,
        }
    }

    pub fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
        #[cfg(not(target_os = "android"))]
        {
            self.main.push_entry(
                MainMenuEntry::SelectedPlayer,
                MenuEntry::Options(
                    state.loc.t("menus.controls_menu.select_player.entry").to_owned(),
                    self.selected_player as usize,
                    vec![
                        state.loc.t("menus.controls_menu.select_player.player_1").to_owned(),
                        state.loc.t("menus.controls_menu.select_player.player_2").to_owned(),
                    ],
                ),
            );

            self.main.push_entry(
                MainMenuEntry::Controller,
                MenuEntry::Active(state.loc.t("menus.controls_menu.controller.entry").to_owned()),
            );
            self.main
                .push_entry(MainMenuEntry::Rebind, MenuEntry::Active(state.loc.t("menus.controls_menu.rebind").to_owned()));
            self.main.push_entry(MainMenuEntry::Rumble, MenuEntry::Hidden);
        }

        if state.settings.touch_controls {
            self.main.push_entry(
                MainMenuEntry::DisplayTouchControls,
                MenuEntry::Toggle(
                    state.loc.t("menus.options_menu.controls_menu.display_touch_controls").to_owned(),
                    state.settings.display_touch_controls,
                ),
            );
        }
        self.main.push_entry(MainMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));

        self.confirm_reset.push_entry(
            ConfirmResetMenuEntry::Title,
            MenuEntry::Disabled(state.loc.t("menus.controls_menu.reset_confirm_menu_title").to_owned()),
        );
        self.confirm_reset
            .push_entry(ConfirmResetMenuEntry::Yes, MenuEntry::Active(state.loc.t("common.yes").to_owned()));
        self.confirm_reset
            .push_entry(ConfirmResetMenuEntry::No, MenuEntry::Active(state.loc.t("common.no").to_owned()));

        self.player1_key_map = self.init_key_map(&state.settings.player1_key_map);
        self.player2_key_map = self.init_key_map(&state.settings.player2_key_map);
        self.player1_controller_button_map =
            self.init_controller_button_map(&state.settings.player1_controller_button_map);
        self.player2_controller_button_map =
            self.init_controller_button_map(&state.settings.player2_controller_button_map);

        self.confirm_rebind.draw_cursor = false;
        self.confirm_rebind.non_interactive = true;

        self.update_controller_options(state, ctx);
        self.update_rebind_menu(state, ctx);
        self.update_sizes(state);

        Ok(())
    }

    fn update_sizes(&mut self, state: &SharedGameState) {
        self.main.update_width(state);
        self.main.update_height(state);
        self.main.x = ((state.canvas_size.0 - self.main.width as f32) / 2.0).floor() as isize;
        self.main.y = ((state.canvas_size.1 - self.main.height as f32) / 2.0).floor() as isize;

        self.select_controller.update_width(state);
        self.select_controller.update_height(state);
        self.select_controller.x = ((state.canvas_size.0 - self.select_controller.width as f32) / 2.0).floor() as isize;
        self.select_controller.y =
            ((state.canvas_size.1 - self.select_controller.height as f32) / 2.0).floor() as isize;

        self.rebind.update_width(state);
        self.rebind.update_height(state);
        self.rebind.x = ((state.canvas_size.0 - self.rebind.width as f32) / 2.0).floor() as isize;
        self.rebind.y = ((state.canvas_size.1 - self.rebind.height as f32) / 2.0).floor() as isize;

        self.confirm_rebind.update_width(state);
        self.confirm_rebind.update_height(state);
        self.confirm_rebind.x = ((state.canvas_size.0 - self.confirm_rebind.width as f32) / 2.0).floor() as isize;
        self.confirm_rebind.y = ((state.canvas_size.1 - self.confirm_rebind.height as f32) / 2.0).floor() as isize;

        self.confirm_reset.update_width(state);
        self.confirm_reset.update_height(state);
        self.confirm_reset.x = ((state.canvas_size.0 - self.confirm_reset.width as f32) / 2.0).floor() as isize;
        self.confirm_reset.y = ((state.canvas_size.1 - self.confirm_reset.height as f32) / 2.0).floor() as isize;
    }

    fn init_key_map(&self, settings_key_map: &PlayerKeyMap) -> Vec<(ControlEntry, ScanCode)> {
        let mut map = Vec::new();

        map.push((ControlEntry::MenuOk, settings_key_map.menu_ok));
        map.push((ControlEntry::MenuBack, settings_key_map.menu_back));
        map.push((ControlEntry::Up, settings_key_map.up));
        map.push((ControlEntry::Down, settings_key_map.down));
        map.push((ControlEntry::Left, settings_key_map.left));
        map.push((ControlEntry::Right, settings_key_map.right));
        map.push((ControlEntry::Jump, settings_key_map.jump));
        map.push((ControlEntry::Shoot, settings_key_map.shoot));
        map.push((ControlEntry::PrevWeapon, settings_key_map.prev_weapon));
        map.push((ControlEntry::NextWeapon, settings_key_map.next_weapon));
        map.push((ControlEntry::Inventory, settings_key_map.inventory));
        map.push((ControlEntry::Map, settings_key_map.map));
        map.push((ControlEntry::Skip, settings_key_map.skip));
        map.push((ControlEntry::Strafe, settings_key_map.strafe));

        map
    }

    fn init_controller_button_map(
        &self,
        settings_controller_button_map: &PlayerControllerButtonMap,
    ) -> Vec<(ControlEntry, PlayerControllerInputType)> {
        let mut map = Vec::new();

        map.push((ControlEntry::MenuOk, settings_controller_button_map.menu_ok));
        map.push((ControlEntry::MenuBack, settings_controller_button_map.menu_back));
        map.push((ControlEntry::Up, settings_controller_button_map.up));
        map.push((ControlEntry::Down, settings_controller_button_map.down));
        map.push((ControlEntry::Left, settings_controller_button_map.left));
        map.push((ControlEntry::Right, settings_controller_button_map.right));
        map.push((ControlEntry::Jump, settings_controller_button_map.jump));
        map.push((ControlEntry::Shoot, settings_controller_button_map.shoot));
        map.push((ControlEntry::PrevWeapon, settings_controller_button_map.prev_weapon));
        map.push((ControlEntry::NextWeapon, settings_controller_button_map.next_weapon));
        map.push((ControlEntry::Inventory, settings_controller_button_map.inventory));
        map.push((ControlEntry::Map, settings_controller_button_map.map));
        map.push((ControlEntry::Skip, settings_controller_button_map.skip));
        map.push((ControlEntry::Strafe, settings_controller_button_map.strafe));

        map
    }

    fn update_rebind_menu(&mut self, state: &SharedGameState, ctx: &Context) {
        self.rebind.entries.clear();

        match self.selected_player {
            Player::Player1 => {
                if self.selected_controller == ControllerType::Keyboard {
                    for (k, v) in self.player1_key_map.iter() {
                        self.rebind.push_entry(
                            RebindMenuEntry::Control(*k),
                            MenuEntry::Control(
                                k.to_string(state).to_owned(),
                                ControlMenuData::String(format!("{:?}", v)),
                            ),
                        );
                    }
                } else {
                    for (k, v) in self.player1_controller_button_map.iter() {
                        let gamepad_sprite_offset = match state.settings.player1_controller_type {
                            ControllerType::Keyboard => 1,
                            ControllerType::Gamepad(index) => {
                                ctx.gamepad_context.get_gamepad_sprite_offset(index as usize)
                            }
                        };

                        self.rebind.push_entry(
                            RebindMenuEntry::Control(*k),
                            MenuEntry::Control(
                                k.to_string(state).to_owned(),
                                ControlMenuData::Rect(v.get_rect(gamepad_sprite_offset, &state.constants)),
                            ),
                        );
                    }
                }
            }
            Player::Player2 => {
                if self.selected_controller == ControllerType::Keyboard {
                    for (k, v) in self.player2_key_map.iter() {
                        self.rebind.push_entry(
                            RebindMenuEntry::Control(*k),
                            MenuEntry::Control(
                                k.to_string(state).to_owned(),
                                ControlMenuData::String(format!("{:?}", v)),
                            ),
                        );
                    }
                } else {
                    for (k, v) in self.player2_controller_button_map.iter() {
                        let gamepad_sprite_offset = match state.settings.player2_controller_type {
                            ControllerType::Keyboard => 1,
                            ControllerType::Gamepad(index) => {
                                ctx.gamepad_context.get_gamepad_sprite_offset(index as usize)
                            }
                        };

                        self.rebind.push_entry(
                            RebindMenuEntry::Control(*k),
                            MenuEntry::Control(
                                k.to_string(state).to_owned(),
                                ControlMenuData::Rect(v.get_rect(gamepad_sprite_offset, &state.constants)),
                            ),
                        );
                    }
                }
            }
        }

        self.rebind.push_entry(
            RebindMenuEntry::Reset,
            MenuEntry::Active(state.loc.t("menus.controls_menu.reset_confirm").to_owned()),
        );
        self.rebind.push_entry(RebindMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
    }

    fn update_controller_options(&mut self, state: &SharedGameState, ctx: &Context) {
        self.select_controller.entries.clear();

        self.select_controller.push_entry(
            SelectControllerMenuEntry::Keyboard,
            MenuEntry::Active(state.loc.t("menus.controls_menu.controller.keyboard").to_owned()),
        );

        let gamepads = gamepad::get_gamepads(ctx);

        let other_player_controller_type = match self.selected_player {
            Player::Player1 => state.settings.player2_controller_type,
            Player::Player2 => state.settings.player1_controller_type,
        };

        let mut available_gamepads = gamepads.len();

        for i in 0..gamepads.len() {
            if let ControllerType::Gamepad(index) = other_player_controller_type {
                if index as usize == i {
                    available_gamepads -= 1;
                    continue;
                }
            }

            self.select_controller.push_entry(
                SelectControllerMenuEntry::Gamepad(i),
                MenuEntry::Active(format!("{} {}", gamepads[i].get_gamepad_name(), i + 1)),
            );
        }

        self.select_controller
            .push_entry(SelectControllerMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));

        let controller_type = match self.selected_player {
            Player::Player1 => state.settings.player1_controller_type,
            Player::Player2 => state.settings.player2_controller_type,
        };

        let rumble = match self.selected_player {
            Player::Player1 => state.settings.player1_rumble,
            Player::Player2 => state.settings.player2_rumble,
        };

        if let ControllerType::Gamepad(index) = controller_type {
            if index as usize >= available_gamepads {
                self.selected_controller = ControllerType::Keyboard;
                self.main.set_entry(MainMenuEntry::Rumble, MenuEntry::Hidden);
            } else {
                self.selected_controller = controller_type;
                self.main.set_entry(
                    MainMenuEntry::Rumble,
                    MenuEntry::Toggle(state.loc.t("menus.controls_menu.rumble").to_owned(), rumble),
                );
            }
        } else {
            self.selected_controller = controller_type;
            self.main.set_entry(MainMenuEntry::Rumble, MenuEntry::Hidden);
        }

        match self.selected_controller {
            ControllerType::Keyboard => self.select_controller.selected = SelectControllerMenuEntry::Keyboard,
            ControllerType::Gamepad(index) => {
                self.select_controller.selected = SelectControllerMenuEntry::Gamepad(index as usize)
            }
        }
    }

    fn update_confirm_controls_menu(&mut self, state: &SharedGameState) {
        match self.selected_control {
            Some(control) => {
                self.confirm_rebind.entries.clear();

                self.confirm_rebind.push_entry(
                    0,
                    MenuEntry::DisabledWhite(state.tt(
                        "menus.controls_menu.rebind_confirm_menu.title",
                        &[("control", control.to_string(state).as_str())],
                    )),
                );
                self.confirm_rebind.push_entry(
                    1,
                    MenuEntry::Disabled(state.loc.t("menus.controls_menu.rebind_confirm_menu.cancel").to_owned()),
                );
            }
            None => {}
        }
    }

    fn reset_controls(&mut self, state: &mut SharedGameState, ctx: &Context) -> GameResult {
        match self.selected_player {
            Player::Player1 => {
                if self.selected_controller == ControllerType::Keyboard {
                    state.settings.player1_key_map = p1_default_keymap();
                    self.player1_key_map = self.init_key_map(&state.settings.player1_key_map);
                } else {
                    state.settings.player1_controller_button_map = player_default_controller_button_map();
                    self.player1_controller_button_map =
                        self.init_controller_button_map(&state.settings.player1_controller_button_map);
                }
            }
            Player::Player2 => {
                if self.selected_controller == ControllerType::Keyboard {
                    state.settings.player2_key_map = p2_default_keymap();
                    self.player2_key_map = self.init_key_map(&state.settings.player2_key_map);
                } else {
                    state.settings.player2_controller_button_map = player_default_controller_button_map();
                    self.player2_controller_button_map =
                        self.init_controller_button_map(&state.settings.player2_controller_button_map);
                }
            }
        }

        state.settings.save(ctx)
    }

    fn is_key_occupied(&self, scan_code: ScanCode) -> bool {
        let other_player_keymap = match self.selected_player {
            Player::Player1 => &self.player2_key_map,
            Player::Player2 => &self.player1_key_map,
        };

        for (_, v) in other_player_keymap.iter() {
            if *v == scan_code {
                return true;
            }
        }

        false
    }

    fn set_key(&mut self, state: &mut SharedGameState, scan_code: ScanCode, ctx: &Context) -> GameResult {
        if self.selected_control.is_none() {
            return Ok(());
        }

        let mut did_swap_controls = false;

        match self.selected_control.unwrap() {
            ControlEntry::Left => match self.selected_player {
                Player::Player1 => state.settings.player1_key_map.left = scan_code,
                Player::Player2 => state.settings.player2_key_map.left = scan_code,
            },
            ControlEntry::Up => match self.selected_player {
                Player::Player1 => state.settings.player1_key_map.up = scan_code,
                Player::Player2 => state.settings.player2_key_map.up = scan_code,
            },
            ControlEntry::Right => match self.selected_player {
                Player::Player1 => state.settings.player1_key_map.right = scan_code,
                Player::Player2 => state.settings.player2_key_map.right = scan_code,
            },
            ControlEntry::Down => match self.selected_player {
                Player::Player1 => state.settings.player1_key_map.down = scan_code,
                Player::Player2 => state.settings.player2_key_map.down = scan_code,
            },
            ControlEntry::PrevWeapon => match self.selected_player {
                Player::Player1 => state.settings.player1_key_map.prev_weapon = scan_code,
                Player::Player2 => state.settings.player2_key_map.prev_weapon = scan_code,
            },
            ControlEntry::NextWeapon => match self.selected_player {
                Player::Player1 => state.settings.player1_key_map.next_weapon = scan_code,
                Player::Player2 => state.settings.player2_key_map.next_weapon = scan_code,
            },
            ControlEntry::Jump => match self.selected_player {
                Player::Player1 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player1_key_map.jump,
                        &mut state.settings.player1_key_map.shoot,
                        scan_code,
                    );
                }
                Player::Player2 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player2_key_map.jump,
                        &mut state.settings.player2_key_map.shoot,
                        scan_code,
                    );
                }
            },
            ControlEntry::Shoot => match self.selected_player {
                Player::Player1 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player1_key_map.shoot,
                        &mut state.settings.player1_key_map.jump,
                        scan_code,
                    );
                }
                Player::Player2 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player2_key_map.shoot,
                        &mut state.settings.player2_key_map.jump,
                        scan_code,
                    );
                }
            },
            ControlEntry::Skip => match self.selected_player {
                Player::Player1 => state.settings.player1_key_map.skip = scan_code,
                Player::Player2 => state.settings.player2_key_map.skip = scan_code,
            },
            ControlEntry::Inventory => match self.selected_player {
                Player::Player1 => state.settings.player1_key_map.inventory = scan_code,
                Player::Player2 => state.settings.player2_key_map.inventory = scan_code,
            },
            ControlEntry::Map => match self.selected_player {
                Player::Player1 => state.settings.player1_key_map.map = scan_code,
                Player::Player2 => state.settings.player2_key_map.map = scan_code,
            },
            ControlEntry::Strafe => match self.selected_player {
                Player::Player1 => state.settings.player1_key_map.strafe = scan_code,
                Player::Player2 => state.settings.player2_key_map.strafe = scan_code,
            },
            ControlEntry::MenuOk => match self.selected_player {
                Player::Player1 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player1_key_map.menu_ok,
                        &mut state.settings.player1_key_map.menu_back,
                        scan_code,
                    );
                }
                Player::Player2 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player2_key_map.menu_ok,
                        &mut state.settings.player2_key_map.menu_back,
                        scan_code,
                    );
                }
            },
            ControlEntry::MenuBack => match self.selected_player {
                Player::Player1 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player1_key_map.menu_back,
                        &mut state.settings.player1_key_map.menu_ok,
                        scan_code,
                    );
                }
                Player::Player2 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player2_key_map.menu_back,
                        &mut state.settings.player2_key_map.menu_ok,
                        scan_code,
                    );
                }
            },
        }

        state.settings.save(ctx)?;

        let keymap = match self.selected_player {
            Player::Player1 => &mut self.player1_key_map,
            Player::Player2 => &mut self.player2_key_map,
        };

        for (entry, value) in keymap.iter_mut() {
            if *entry == self.selected_control.unwrap() {
                *value = scan_code;
            }

            if did_swap_controls {
                let map = match self.selected_player {
                    Player::Player1 => &state.settings.player1_key_map,
                    Player::Player2 => &state.settings.player2_key_map,
                };

                match *entry {
                    ControlEntry::Jump => *value = map.jump,
                    ControlEntry::Shoot => *value = map.shoot,
                    ControlEntry::MenuOk => *value = map.menu_ok,
                    ControlEntry::MenuBack => *value = map.menu_back,
                    _ => {}
                }
            }
        }

        Ok(())
    }

    fn set_controller_input(
        &mut self,
        state: &mut SharedGameState,
        input_type: PlayerControllerInputType,
        ctx: &Context,
    ) -> GameResult {
        if self.selected_control.is_none() {
            return Ok(());
        }

        let mut did_swap_controls = false;

        match self.selected_control.unwrap() {
            ControlEntry::Left => match self.selected_player {
                Player::Player1 => state.settings.player1_controller_button_map.left = input_type,
                Player::Player2 => state.settings.player2_controller_button_map.left = input_type,
            },
            ControlEntry::Up => match self.selected_player {
                Player::Player1 => state.settings.player1_controller_button_map.up = input_type,
                Player::Player2 => state.settings.player2_controller_button_map.up = input_type,
            },
            ControlEntry::Right => match self.selected_player {
                Player::Player1 => state.settings.player1_controller_button_map.right = input_type,
                Player::Player2 => state.settings.player2_controller_button_map.right = input_type,
            },
            ControlEntry::Down => match self.selected_player {
                Player::Player1 => state.settings.player1_controller_button_map.down = input_type,
                Player::Player2 => state.settings.player2_controller_button_map.down = input_type,
            },
            ControlEntry::PrevWeapon => match self.selected_player {
                Player::Player1 => state.settings.player1_controller_button_map.prev_weapon = input_type,
                Player::Player2 => state.settings.player2_controller_button_map.prev_weapon = input_type,
            },
            ControlEntry::NextWeapon => match self.selected_player {
                Player::Player1 => state.settings.player1_controller_button_map.next_weapon = input_type,
                Player::Player2 => state.settings.player2_controller_button_map.next_weapon = input_type,
            },
            ControlEntry::Jump => match self.selected_player {
                Player::Player1 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player1_controller_button_map.jump,
                        &mut state.settings.player1_controller_button_map.shoot,
                        input_type,
                    );
                }
                Player::Player2 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player2_controller_button_map.jump,
                        &mut state.settings.player2_controller_button_map.shoot,
                        input_type,
                    );
                }
            },
            ControlEntry::Shoot => match self.selected_player {
                Player::Player1 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player1_controller_button_map.shoot,
                        &mut state.settings.player1_controller_button_map.jump,
                        input_type,
                    );
                }
                Player::Player2 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player2_controller_button_map.shoot,
                        &mut state.settings.player2_controller_button_map.jump,
                        input_type,
                    );
                }
            },
            ControlEntry::Skip => match self.selected_player {
                Player::Player1 => state.settings.player1_controller_button_map.skip = input_type,
                Player::Player2 => state.settings.player2_controller_button_map.skip = input_type,
            },
            ControlEntry::Inventory => match self.selected_player {
                Player::Player1 => state.settings.player1_controller_button_map.inventory = input_type,
                Player::Player2 => state.settings.player2_controller_button_map.inventory = input_type,
            },
            ControlEntry::Map => match self.selected_player {
                Player::Player1 => state.settings.player1_controller_button_map.map = input_type,
                Player::Player2 => state.settings.player2_controller_button_map.map = input_type,
            },
            ControlEntry::Strafe => match self.selected_player {
                Player::Player1 => state.settings.player1_controller_button_map.strafe = input_type,
                Player::Player2 => state.settings.player2_controller_button_map.strafe = input_type,
            },
            ControlEntry::MenuOk => match self.selected_player {
                Player::Player1 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player1_controller_button_map.menu_ok,
                        &mut state.settings.player1_controller_button_map.menu_back,
                        input_type,
                    );
                }
                Player::Player2 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player2_controller_button_map.menu_ok,
                        &mut state.settings.player2_controller_button_map.menu_back,
                        input_type,
                    );
                }
            },
            ControlEntry::MenuBack => match self.selected_player {
                Player::Player1 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player1_controller_button_map.menu_back,
                        &mut state.settings.player1_controller_button_map.menu_ok,
                        input_type,
                    );
                }
                Player::Player2 => {
                    did_swap_controls = self.swap_if_same(
                        &mut state.settings.player2_controller_button_map.menu_back,
                        &mut state.settings.player2_controller_button_map.menu_ok,
                        input_type,
                    );
                }
            },
        }

        state.settings.save(ctx)?;

        let button_map = match self.selected_player {
            Player::Player1 => &mut self.player1_controller_button_map,
            Player::Player2 => &mut self.player2_controller_button_map,
        };

        for (entry, value) in button_map.iter_mut() {
            if *entry == self.selected_control.unwrap() {
                *value = input_type;
            }

            if did_swap_controls {
                let map = match self.selected_player {
                    Player::Player1 => &state.settings.player1_controller_button_map,
                    Player::Player2 => &state.settings.player2_controller_button_map,
                };

                match *entry {
                    ControlEntry::Jump => *value = map.jump,
                    ControlEntry::Shoot => *value = map.shoot,
                    ControlEntry::MenuOk => *value = map.menu_ok,
                    ControlEntry::MenuBack => *value = map.menu_back,
                    _ => {}
                }
            }
        }

        Ok(())
    }

    fn swap_if_same<T: Eq + Copy>(&mut self, fst: &mut T, snd: &mut T, value: T) -> bool {
        let mut swapped = false;

        if *snd == value {
            *snd = *fst;
            swapped = true;
        }

        *fst = value;

        swapped
    }

    fn normalize_gamepad_input(&self, input: PlayerControllerInputType) -> PlayerControllerInputType {
        match input {
            PlayerControllerInputType::ButtonInput(Button::DPadUp) => {
                PlayerControllerInputType::Either(Button::DPadUp, Axis::LeftY, AxisDirection::Up)
            }
            PlayerControllerInputType::ButtonInput(Button::DPadDown) => {
                PlayerControllerInputType::Either(Button::DPadDown, Axis::LeftY, AxisDirection::Down)
            }
            PlayerControllerInputType::ButtonInput(Button::DPadLeft) => {
                PlayerControllerInputType::Either(Button::DPadLeft, Axis::LeftX, AxisDirection::Left)
            }
            PlayerControllerInputType::ButtonInput(Button::DPadRight) => {
                PlayerControllerInputType::Either(Button::DPadRight, Axis::LeftX, AxisDirection::Right)
            }
            PlayerControllerInputType::AxisInput(Axis::LeftY, AxisDirection::Up) => {
                PlayerControllerInputType::Either(Button::DPadUp, Axis::LeftY, AxisDirection::Up)
            }
            PlayerControllerInputType::AxisInput(Axis::LeftY, AxisDirection::Down) => {
                PlayerControllerInputType::Either(Button::DPadDown, Axis::LeftY, AxisDirection::Down)
            }
            PlayerControllerInputType::AxisInput(Axis::LeftX, AxisDirection::Left) => {
                PlayerControllerInputType::Either(Button::DPadLeft, Axis::LeftX, AxisDirection::Left)
            }
            PlayerControllerInputType::AxisInput(Axis::LeftX, AxisDirection::Right) => {
                PlayerControllerInputType::Either(Button::DPadRight, Axis::LeftX, AxisDirection::Right)
            }
            PlayerControllerInputType::AxisInput(Axis::RightY, AxisDirection::Up) => {
                PlayerControllerInputType::Either(Button::DPadUp, Axis::RightY, AxisDirection::Up)
            }
            PlayerControllerInputType::AxisInput(Axis::RightY, AxisDirection::Down) => {
                PlayerControllerInputType::Either(Button::DPadDown, Axis::RightY, AxisDirection::Down)
            }
            PlayerControllerInputType::AxisInput(Axis::RightX, AxisDirection::Left) => {
                PlayerControllerInputType::Either(Button::DPadLeft, Axis::RightX, AxisDirection::Left)
            }
            PlayerControllerInputType::AxisInput(Axis::RightX, AxisDirection::Right) => {
                PlayerControllerInputType::Either(Button::DPadRight, Axis::RightX, AxisDirection::Right)
            }
            _ => input,
        }
    }

    pub fn tick(
        &mut self,
        exit_action: &mut dyn FnMut(),
        controller: &mut CombinedMenuController,
        state: &mut SharedGameState,
        ctx: &mut Context,
    ) -> GameResult {
        self.update_sizes(state);

        match self.current {
            CurrentMenu::MainMenu => match self.main.tick(controller, state) {
                MenuSelectionResult::Selected(MainMenuEntry::SelectedPlayer, toggle)
                | MenuSelectionResult::Left(MainMenuEntry::SelectedPlayer, toggle, _)
                | MenuSelectionResult::Right(MainMenuEntry::SelectedPlayer, toggle, _) => {
                    if let MenuEntry::Options(_, value, _) = toggle {
                        let (new_player, new_value) = match *value {
                            0 => (Player::Player2, 1),
                            1 => (Player::Player1, 0),
                            _ => unreachable!(),
                        };

                        *value = new_value;

                        self.selected_player = new_player;
                        self.selected_controller = new_player.controller_type(state);

                        self.update_controller_options(state, ctx);
                        self.update_rebind_menu(state, ctx);
                    }
                }
                MenuSelectionResult::Selected(MainMenuEntry::Controller, _) => {
                    if self.input_busy {
                        return Ok(());
                    }

                    self.update_controller_options(state, ctx);
                    self.current = CurrentMenu::SelectControllerMenu;
                }
                MenuSelectionResult::Selected(MainMenuEntry::Rebind, _) => {
                    self.current = CurrentMenu::RebindMenu;
                }
                MenuSelectionResult::Selected(MainMenuEntry::Rumble, toggle) => {
                    if let MenuEntry::Toggle(_, value) = toggle {
                        match self.selected_player {
                            Player::Player1 => {
                                state.settings.player1_rumble = !state.settings.player1_rumble;

                                if state.settings.player1_rumble {
                                    if let ControllerType::Gamepad(idx) = self.selected_controller {
                                        gamepad::set_rumble(
                                            ctx,
                                            state,
                                            idx,
                                            0,
                                            0x5000,
                                            (state.settings.timing_mode.get_tps() / 2) as u32,
                                        )?;
                                    }
                                }

                                *value = state.settings.player1_rumble;
                            }
                            Player::Player2 => {
                                state.settings.player2_rumble = !state.settings.player2_rumble;

                                if state.settings.player2_rumble {
                                    if let ControllerType::Gamepad(idx) = self.selected_controller {
                                        gamepad::set_rumble(
                                            ctx,
                                            state,
                                            idx,
                                            0,
                                            0x5000,
                                            (state.settings.timing_mode.get_tps() / 2) as u32,
                                        )?;
                                    }
                                }

                                *value = state.settings.player2_rumble;
                            }
                        }

                        state.settings.save(ctx)?;
                    }
                }
                MenuSelectionResult::Selected(MainMenuEntry::DisplayTouchControls, toggle) => {
                    if let MenuEntry::Toggle(_, value) = toggle {
                        state.settings.display_touch_controls = !state.settings.display_touch_controls;
                        let _ = state.settings.save(ctx);

                        *value = state.settings.display_touch_controls;
                    }
                }
                MenuSelectionResult::Selected(MainMenuEntry::Back, _) | MenuSelectionResult::Canceled => exit_action(),
                _ => {}
            },
            CurrentMenu::SelectControllerMenu => match self.select_controller.tick(controller, state) {
                MenuSelectionResult::Selected(SelectControllerMenuEntry::Keyboard, _) => {
                    if self.selected_player == Player::Player1 {
                        state.settings.player1_controller_type = ControllerType::Keyboard;
                    } else {
                        state.settings.player2_controller_type = ControllerType::Keyboard;
                    }

                    let _ = state.settings.save(ctx);

                    let mut new_menu_controller = CombinedMenuController::new();
                    new_menu_controller.add(state.settings.create_player1_controller());
                    new_menu_controller.add(state.settings.create_player2_controller());
                    self.input_busy = true;
                    self.main.non_interactive = true;
                    *controller = new_menu_controller;

                    self.selected_controller = ControllerType::Keyboard;
                    self.update_rebind_menu(state, ctx);

                    self.current = CurrentMenu::MainMenu;
                }
                MenuSelectionResult::Selected(SelectControllerMenuEntry::Gamepad(idx), _) => {
                    if self.selected_player == Player::Player1 {
                        state.settings.player1_controller_type = ControllerType::Gamepad(idx as u32);
                    } else {
                        state.settings.player2_controller_type = ControllerType::Gamepad(idx as u32);
                    }

                    let _ = state.settings.save(ctx);

                    let mut new_menu_controller = CombinedMenuController::new();
                    new_menu_controller.add(state.settings.create_player1_controller());
                    new_menu_controller.add(state.settings.create_player2_controller());
                    self.input_busy = true;
                    self.main.non_interactive = true;
                    *controller = new_menu_controller;

                    self.selected_controller = ControllerType::Gamepad(idx as u32);
                    self.update_rebind_menu(state, ctx);

                    self.current = CurrentMenu::MainMenu;
                }
                MenuSelectionResult::Selected(SelectControllerMenuEntry::Back, _) | MenuSelectionResult::Canceled => {
                    self.current = CurrentMenu::MainMenu;
                }
                _ => {}
            },
            CurrentMenu::RebindMenu => match self.rebind.tick(controller, state) {
                MenuSelectionResult::Selected(RebindMenuEntry::Back, _) | MenuSelectionResult::Canceled => {
                    if !self.input_busy {
                        self.current = CurrentMenu::MainMenu;
                    }
                }
                MenuSelectionResult::Selected(RebindMenuEntry::Control(control), _) => {
                    if !self.input_busy {
                        self.selected_control = Some(control);
                        self.update_confirm_controls_menu(state);
                        self.input_busy = true;
                        self.current = CurrentMenu::ConfirmRebindMenu;
                    }
                }
                MenuSelectionResult::Selected(RebindMenuEntry::Reset, _) => {
                    self.confirm_reset.selected = ConfirmResetMenuEntry::default();
                    self.current = CurrentMenu::ConfirmResetMenu;
                }
                _ => {}
            },
            CurrentMenu::ConfirmRebindMenu => match self.confirm_rebind.tick(controller, state) {
                _ => {
                    let pressed_keys: Vec<_> = ctx.keyboard_context.pressed_keys().into_iter().collect();

                    for key in pressed_keys.clone() {
                        if *key == ScanCode::Escape {
                            state.sound_manager.play_sfx(5);
                            self.current = CurrentMenu::RebindMenu;
                            return Ok(());
                        }
                    }

                    match self.selected_controller {
                        ControllerType::Keyboard => {
                            if pressed_keys.len() == 1 {
                                if !self.input_busy {
                                    self.input_busy = true;
                                    self.rebind.non_interactive = true;

                                    let key = **pressed_keys.first().unwrap();

                                    if self.is_key_occupied(key)
                                        || FORBIDDEN_SCANCODES.contains(&key)
                                        || self.selected_controller != ControllerType::Keyboard
                                    {
                                        state.sound_manager.play_sfx(12);
                                    } else {
                                        self.set_key(state, key, ctx)?;
                                        self.update_rebind_menu(state, ctx);
                                        self.selected_control = None;
                                        state.sound_manager.play_sfx(18);
                                        self.current = CurrentMenu::RebindMenu;
                                    }
                                }
                            }
                        }
                        ControllerType::Gamepad(idx) => {
                            let pressed_gamepad_buttons: Vec<_> =
                                ctx.gamepad_context.pressed_buttons(idx).into_iter().collect();

                            for button in pressed_gamepad_buttons.clone() {
                                if button == Button::Start {
                                    state.sound_manager.play_sfx(5);
                                    self.current = CurrentMenu::RebindMenu;
                                    return Ok(());
                                }
                            }

                            if pressed_gamepad_buttons.len() == 1 {
                                if !self.input_busy {
                                    self.input_busy = true;
                                    self.rebind.non_interactive = true;

                                    let button = *pressed_gamepad_buttons.first().unwrap();

                                    if self.selected_player.controller_type(state) != self.selected_controller {
                                        state.sound_manager.play_sfx(12);
                                    } else {
                                        let normalized_input = self
                                            .normalize_gamepad_input(PlayerControllerInputType::ButtonInput(button));

                                        self.set_controller_input(state, normalized_input, ctx)?;
                                        self.update_rebind_menu(state, ctx);
                                        self.selected_control = None;
                                        state.sound_manager.play_sfx(18);
                                        self.current = CurrentMenu::RebindMenu;
                                    }
                                }
                            }

                            let active_axes: Vec<_> = ctx.gamepad_context.active_axes(idx).into_iter().collect();

                            if active_axes.len() == 1 {
                                if !self.input_busy {
                                    self.input_busy = true;
                                    self.rebind.non_interactive = true;

                                    if self.selected_player.controller_type(state) != self.selected_controller {
                                        state.sound_manager.play_sfx(12);
                                    } else {
                                        let (axis, value) = *active_axes.first().unwrap();
                                        let direction = AxisDirection::from_axis_data(axis, value);
                                        let normalized_input = self.normalize_gamepad_input(
                                            PlayerControllerInputType::AxisInput(axis, direction),
                                        );

                                        self.set_controller_input(state, normalized_input, ctx)?;
                                        self.update_rebind_menu(state, ctx);
                                        self.selected_control = None;
                                        state.sound_manager.play_sfx(18);
                                        self.current = CurrentMenu::RebindMenu;
                                    }
                                }
                            }

                            if pressed_keys.is_empty() && pressed_gamepad_buttons.is_empty() && active_axes.is_empty() {
                                self.input_busy = false;
                            }
                        }
                    }
                }
            },
            CurrentMenu::ConfirmResetMenu => match self.confirm_reset.tick(controller, state) {
                MenuSelectionResult::Selected(ConfirmResetMenuEntry::Yes, _) => {
                    self.reset_controls(state, ctx)?;
                    self.update_rebind_menu(state, ctx);
                    self.input_busy = true;
                    self.rebind.non_interactive = true;
                    self.current = CurrentMenu::RebindMenu;
                }
                MenuSelectionResult::Selected(ConfirmResetMenuEntry::No, _) | MenuSelectionResult::Canceled => {
                    self.current = CurrentMenu::RebindMenu;
                }
                _ => {}
            },
        }

        if self.input_busy {
            let pressed_keys = ctx.keyboard_context.pressed_keys();
            let mut input_busy = pressed_keys.len() > 0;

            let gamepads = ctx.gamepad_context.get_gamepads();
            for idx in 0..gamepads.len() {
                let pressed_gamepad_buttons = ctx.gamepad_context.pressed_buttons(idx as u32);
                let active_axes = ctx.gamepad_context.active_axes(idx as u32);

                input_busy = input_busy || !pressed_gamepad_buttons.is_empty() || !active_axes.is_empty();
            }

            self.input_busy = input_busy;

            if !self.input_busy {
                self.main.non_interactive = false;
                self.rebind.non_interactive = false;
            }
        }

        Ok(())
    }

    pub fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
        match self.current {
            CurrentMenu::MainMenu => self.main.draw(state, ctx)?,
            CurrentMenu::SelectControllerMenu => self.select_controller.draw(state, ctx)?,
            CurrentMenu::RebindMenu => self.rebind.draw(state, ctx)?,
            CurrentMenu::ConfirmRebindMenu => self.confirm_rebind.draw(state, ctx)?,
            CurrentMenu::ConfirmResetMenu => self.confirm_reset.draw(state, ctx)?,
        }

        Ok(())
    }
}
