use sdl3::{
    self, EventPump, EventSubsystem, GamepadSubsystem, JoystickSubsystem, Sdl,
    event::{Event as SdlEvent, WindowEvent},
    gamepad::{Axis, Button},
    keyboard::Keycode,
    mouse::{MouseButton as SdlMouseButton, MouseUtil, MouseWheelDirection},
};

use ui::prelude::*;

use std::collections::HashMap;

use crate::Result;
use anyhow::anyhow;

use super::controller::{Controller, ControllerDeadzones};
use super::joystick::Joystick;

fn convert_button(button: Button) -> ControllerButton {
    match button {
        Button::South => ControllerButton::A,
        Button::East => ControllerButton::B,
        Button::North => ControllerButton::Y,
        Button::West => ControllerButton::X,

        Button::Start => ControllerButton::Start,
        Button::Back => ControllerButton::Select,

        Button::RightShoulder => ControllerButton::RightButton,
        Button::LeftShoulder => ControllerButton::LeftButton,

        Button::DPadUp => ControllerButton::DPadUp,
        Button::DPadDown => ControllerButton::DPadDown,
        Button::DPadRight => ControllerButton::DPadRight,
        Button::DPadLeft => ControllerButton::DPadLeft,

        Button::Guide => ControllerButton::Guide,

        Button::LeftStick => ControllerButton::LeftStick,
        Button::RightStick => ControllerButton::RightStick,

        Button::Misc1 => ControllerButton::Misc,

        Button::RightPaddle1 => ControllerButton::Paddle1,
        Button::RightPaddle2 => ControllerButton::Paddle2,
        Button::LeftPaddle1 => ControllerButton::Paddle3,
        Button::LeftPaddle2 => ControllerButton::Paddle4,

        Button::Touchpad => ControllerButton::Touchpad,
    }
}

#[derive(Debug)]
pub enum Event<'a> {
    // mouse events
    MouseMotion(u32, u32),
    MouseButtonDown(MouseButton),
    MouseButtonUp(MouseButton),
    MouseWheel(i32, i32, MouseWheelDirection),

    // keyboard events
    KeyDown(Keycode),
    KeyUp(Keycode),
    TextInput(String),

    // controller events
    ControllerAxis(&'a Controller),
    ControllerButtonDown(&'a Controller, ControllerButton),
    ControllerButtonUp(&'a Controller, ControllerButton),
    ControllerAdded(&'a Controller),
    ControllerRemoved(&'a Controller),

    // joystick events
    JoystickAxis(&'a Joystick, u8, i16),
    JoystickButtonDown(&'a Joystick),
    JoystickButtonUp(&'a Joystick),
    JoystickAdded(&'a Joystick),
    JoystickRemoved(&'a Joystick),

    // drag'n'drop
    FileDrop(String),
}

pub struct EventSystem {
    event_pump: EventPump,
    mouse: MouseUtil,
    controller_subsystem: GamepadSubsystem,
    joystick_subsystem: JoystickSubsystem,
    event_subsystem: EventSubsystem,

    controller_deadzones: ControllerDeadzones,

    connected_controllers: HashMap<u32, Controller>,
    connected_joysticks: HashMap<u32, Joystick>,
}

impl EventSystem {
    pub fn new(sdl2_context: &Sdl) -> Result<EventSystem> {
        let mut event_system = EventSystem {
            event_pump: sdl2_context
                .event_pump()
                .map_err(|s| anyhow::Error::msg(s))?,
            mouse: sdl2_context.mouse(),
            controller_subsystem: sdl2_context.gamepad().map_err(|s| anyhow::Error::msg(s))?,
            joystick_subsystem: sdl2_context.joystick().map_err(|s| anyhow::Error::msg(s))?,
            event_subsystem: sdl2_context.event().map_err(|s| anyhow::Error::msg(s))?,

            controller_deadzones: ControllerDeadzones::default(),

            connected_controllers: HashMap::new(),
            connected_joysticks: HashMap::new(),
        };

        event_system.connected_joysticks = event_system
            .joystick_subsystem
            .joysticks()
            .map_err(|s| anyhow::Error::msg(s))?
            .into_iter()
            .map(|instance| {
                Ok((
                    instance.id,
                    Joystick::new(&event_system.joystick_subsystem, instance)?,
                ))
            })
            .collect::<Result<HashMap<_, _>>>()?;

        event_system.connected_controllers = (0..event_system
            .controller_subsystem
            .num_gamepads()
            .map_err(|s| anyhow::Error::msg(s))?)
            .into_iter()
            .filter(|i| event_system.controller_subsystem.is_game_controller(*i))
            .map(|i| {
                Ok((
                    i,
                    Controller::new(
                        &event_system.controller_subsystem,
                        i,
                        event_system.controller_deadzones.clone(),
                    )?,
                ))
            })
            .collect::<Result<HashMap<_, _>>>()?;

        event_system.disable_mouse();

        Ok(event_system)
    }

    pub fn enable_mouse(&mut self) {
        self.mouse.show_cursor(true);
    }

    pub fn disable_mouse(&mut self) {
        self.mouse.show_cursor(false);
    }

    pub fn set_controller_axis_enable_deadzone(&mut self, deadzone: f32) {
        self.controller_deadzones.axis_enable_deadzone = deadzone;
    }

    pub fn set_controller_axis_disable_deadzone(&mut self, deadzone: f32) {
        self.controller_deadzones.axis_disable_deadzone = deadzone;
    }

    pub fn set_controller_trigger_enable_deadzone(&mut self, deadzone: f32) {
        self.controller_deadzones.trigger_enable_deadzone = deadzone;
    }

    pub fn set_controller_trigger_disable_deadzone(&mut self, deadzone: f32) {
        self.controller_deadzones.trigger_disable_deadzone = deadzone;
    }

    pub fn quit(&self) -> Result<()> {
        Ok(self
            .event_subsystem
            .push_event(SdlEvent::Quit { timestamp: 0 })
            .map_err(|s| anyhow::Error::msg(s))?)
    }

    pub fn poll_events<F, R>(&mut self, mut event_callback: F, mut resize: R) -> Result<bool>
    where
        F: FnMut(Event<'_>) -> Result<()>,
        R: FnMut(u32, u32) -> Result<()>,
    {
        for event in self.event_pump.poll_iter() {
            match event {
                SdlEvent::Window { win_event, .. } => match win_event {
                    WindowEvent::Resized(w, h) => {
                        resize(w as u32, h as u32)?;
                    }

                    _ => (),
                },
                SdlEvent::Quit { .. } => return Ok(false),
                // ----------------- Mouse Events ---------------------
                SdlEvent::MouseMotion { x, y, .. } => {
                    event_callback(Event::MouseMotion(x as u32, y as u32))?;
                }
                SdlEvent::MouseButtonDown { mouse_btn, .. } => {
                    let mouse_button = match mouse_btn {
                        SdlMouseButton::Left => MouseButton::Left,
                        SdlMouseButton::Right => MouseButton::Right,
                        SdlMouseButton::Middle => MouseButton::Middle,
                        SdlMouseButton::X1 => MouseButton::Forward,
                        SdlMouseButton::X2 => MouseButton::Backward,
                        SdlMouseButton::Unknown => continue,
                    };

                    event_callback(Event::MouseButtonDown(mouse_button))?;
                }
                SdlEvent::MouseButtonUp { mouse_btn, .. } => {
                    let mouse_button = match mouse_btn {
                        SdlMouseButton::Left => MouseButton::Left,
                        SdlMouseButton::Right => MouseButton::Right,
                        SdlMouseButton::Middle => MouseButton::Middle,
                        SdlMouseButton::X1 => MouseButton::Forward,
                        SdlMouseButton::X2 => MouseButton::Backward,
                        SdlMouseButton::Unknown => continue,
                    };

                    event_callback(Event::MouseButtonUp(mouse_button))?;
                }
                SdlEvent::MouseWheel {
                    x, y, direction, ..
                } => {
                    event_callback(Event::MouseWheel(x as i32, y as i32, direction))?;
                }
                // ------------------- Key Events ---------------------
                SdlEvent::KeyDown {
                    keycode, repeat, ..
                } => {
                    if repeat {
                        continue;
                    }

                    if let Some(keycode) = keycode {
                        event_callback(Event::KeyDown(keycode))?;
                    }
                }
                SdlEvent::KeyUp {
                    keycode, repeat, ..
                } => {
                    if repeat {
                        continue;
                    }

                    if let Some(keycode) = keycode {
                        event_callback(Event::KeyUp(keycode))?;
                    }
                }
                SdlEvent::TextInput { text, .. } => {
                    event_callback(Event::TextInput(text))?;
                }

                // --------------- Controller Events -------------------
                SdlEvent::ControllerDeviceAdded { which, .. } => {
                    if cfg!(debug_assertions) {
                        println!("New Device: {}", which);
                    }

                    if let Ok(controller) = Controller::new(
                        &self.controller_subsystem,
                        which as u32,
                        self.controller_deadzones.clone(),
                    ) {
                        if cfg!(debug_assertions) {
                            println!(
                                "Controller added: {}({})",
                                controller.name(),
                                controller.id()
                            );
                        }

                        event_callback(Event::ControllerAdded(&controller))?;
                        self.connected_controllers
                            .insert(controller.id(), controller);
                    }
                }
                SdlEvent::ControllerDeviceRemoved { which, .. } => {
                    if let Some(controller) = self.connected_controllers.remove(&which) {
                        if cfg!(debug_assertions) {
                            println!(
                                "Controller removed: {}({})",
                                controller.name(),
                                controller.id()
                            );
                        }

                        event_callback(Event::ControllerRemoved(&controller))?;
                    }
                }
                SdlEvent::ControllerButtonDown { button, which, .. } => {
                    event_callback(Event::ControllerButtonDown(
                        self.connected_controllers.get(&which).unwrap(),
                        convert_button(button),
                    ))?;
                }
                SdlEvent::ControllerButtonUp { button, which, .. } => {
                    event_callback(Event::ControllerButtonUp(
                        self.connected_controllers.get(&which).unwrap(),
                        convert_button(button),
                    ))?;
                }
                SdlEvent::ControllerAxisMotion {
                    axis, value, which, ..
                } => {
                    let controller = self.connected_controllers.get_mut(&which).unwrap();
                    let normalized = value as f32 * 0.000_030_518;

                    match axis {
                        Axis::LeftX => {
                            controller.set_left_x(normalized);
                        }
                        Axis::RightX => {
                            controller.set_right_x(normalized);
                        }
                        Axis::LeftY => {
                            controller.set_left_y(-normalized);
                        }
                        Axis::RightY => {
                            controller.set_right_y(normalized);
                        }
                        Axis::TriggerLeft => {
                            controller.set_left_trigger(normalized);
                        }
                        Axis::TriggerRight => {
                            controller.set_right_trigger(normalized);
                        }
                    }

                    event_callback(Event::ControllerAxis(&*controller))?;
                }
                SdlEvent::JoyDeviceAdded { which, .. } => {
                    if let Some(joystick_instance) = self
                        .joystick_subsystem
                        .joysticks()
                        .map_err(|_| anyhow!("failed querying joysticks"))?
                        .into_iter()
                        .find(|instance| instance.id == which)
                    {
                        let joystick = Joystick::new(&self.joystick_subsystem, joystick_instance)?;

                        event_callback(Event::JoystickAdded(&joystick))?;
                        self.connected_joysticks.insert(joystick.id(), joystick);
                    }
                }
                SdlEvent::JoyDeviceRemoved { which, .. } => {
                    if let Some(joysticks) = self.connected_joysticks.remove(&which) {
                        event_callback(Event::JoystickRemoved(&joysticks))?;
                    }
                }
                SdlEvent::JoyAxisMotion {
                    which,
                    axis_idx,
                    value,
                    ..
                } => {
                    if let Some(joysticks) = self.connected_joysticks.get(&which) {
                        event_callback(Event::JoystickAxis(&joysticks, axis_idx, value))?;
                    }
                }
                SdlEvent::DropFile { filename, .. } => {
                    event_callback(Event::FileDrop(filename))?;
                }
                _ => (),
            }
        }

        Ok(true)
    }

    pub fn controllers(&self) -> impl Iterator<Item = &Controller> {
        self.connected_controllers.values()
    }

    pub fn joysticks(&self) -> impl Iterator<Item = &Joystick> {
        self.connected_joysticks.values()
    }
}

unsafe impl Send for EventSystem {}
unsafe impl Sync for EventSystem {}