use sdl2;
use sdl2::controller::Button;
use sdl2::event::{Event as SdlEvent, EventType as SdlEventType, WindowEvent};
use sdl2::keyboard::Keycode;
use sdl2::mouse::{MouseButton as SdlMouseButton, MouseUtil, MouseWheelDirection};
use sdl2::EventPump;
use sdl2::EventSubsystem;
use sdl2::GameControllerSubsystem;
use sdl2::Sdl;

use ui::prelude::*;

use std::ops::Deref;
use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard};

use crate::Result;

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

fn convert_button(button: Button) -> ControllerButton {
    match button {
        Button::A => ControllerButton::A,
        Button::B => ControllerButton::B,
        Button::Y => ControllerButton::Y,
        Button::X => 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::Paddle1 => ControllerButton::Paddle1,
        Button::Paddle2 => ControllerButton::Paddle2,
        Button::Paddle3 => ControllerButton::Paddle3,
        Button::Paddle4 => ControllerButton::Paddle4,

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

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

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

    // controller events
    ControllerAxis(Arc<RwLock<Controller>>),
    ControllerButtonDown(ControllerButton),
    ControllerButtonUp(ControllerButton),
    ControllerAdded(Arc<RwLock<Controller>>),
    ControllerRemoved(Arc<RwLock<Controller>>),

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

pub struct EventSystem {
    event_pump: RwLock<EventPump>,
    mouse: Mutex<MouseUtil>,
    controller_subsystem: Mutex<GameControllerSubsystem>,
    event_subsystem: Mutex<EventSubsystem>,

    controller_deadzones: RwLock<ControllerDeadzones>,

    selected_controller: RwLock<Option<Arc<RwLock<Controller>>>>,
    connected_controllers: RwLock<Vec<Arc<RwLock<Controller>>>>,
}

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

            controller_deadzones: RwLock::new(ControllerDeadzones::default()),

            selected_controller: RwLock::new(None),
            connected_controllers: RwLock::new(Vec::new()),
        };

        event_system.disable_mouse();
        event_system.disable_keyboard();
        event_system.disable_controller();

        Ok(event_system)
    }

    pub fn enable_mouse(&self) {
        let mut event_pump = self.event_pump.write().unwrap();

        event_pump.enable_event(SdlEventType::MouseMotion);
        event_pump.enable_event(SdlEventType::MouseButtonDown);
        event_pump.enable_event(SdlEventType::MouseButtonUp);

        self.mouse.lock().unwrap().show_cursor(true);
    }

    pub fn disable_mouse(&self) {
        let mut event_pump = self.event_pump.write().unwrap();

        event_pump.disable_event(SdlEventType::MouseMotion);
        event_pump.disable_event(SdlEventType::MouseButtonDown);
        event_pump.disable_event(SdlEventType::MouseButtonUp);

        self.mouse.lock().unwrap().show_cursor(false);
    }

    pub fn enable_keyboard(&self) {
        let mut event_pump = self.event_pump.write().unwrap();

        event_pump.enable_event(SdlEventType::KeyUp);
        event_pump.enable_event(SdlEventType::KeyDown);
    }

    pub fn disable_keyboard(&self) {
        let mut event_pump = self.event_pump.write().unwrap();

        event_pump.disable_event(SdlEventType::KeyUp);
        event_pump.disable_event(SdlEventType::KeyDown);
    }

    pub fn enable_controller(&self) {
        let mut event_pump = self.event_pump.write().unwrap();

        event_pump.enable_event(SdlEventType::ControllerAxisMotion);
        event_pump.enable_event(SdlEventType::ControllerButtonDown);
        event_pump.enable_event(SdlEventType::ControllerButtonUp);
    }

    pub fn disable_controller(&self) {
        let mut event_pump = self.event_pump.write().unwrap();

        event_pump.disable_event(SdlEventType::ControllerAxisMotion);
        event_pump.disable_event(SdlEventType::ControllerButtonDown);
        event_pump.disable_event(SdlEventType::ControllerButtonUp);
    }

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

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

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

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

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

    pub fn poll_events<F, R>(&self, event_callback: F, mut resize: R) -> Result<bool>
    where
        F: Fn(Event) -> Result<()>,
        R: FnMut(u32, u32) -> Result<()>,
    {
        let mut controller_axis_changed = false;
        let mut event_pump = self.event_pump.write().unwrap();

        for event in event_pump.poll_iter() {
            match event {
                SdlEvent::Window { win_event, .. } => match win_event {
                    WindowEvent::Resized(w, h) | WindowEvent::SizeChanged(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, y, 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.lock().unwrap().deref(),
                        which as u32,
                        self.controller_deadzones.read().unwrap().clone(),
                    ) {
                        let controller = {
                            if cfg!(debug_assertions) {
                                println!(
                                    "Controller added: {}({})",
                                    controller.name(),
                                    controller.id()
                                );
                            }

                            let mut connected_controllers =
                                self.connected_controllers.write().unwrap();

                            let mut selected_controller = self.selected_controller.write().unwrap();

                            let arc_controller = Arc::new(RwLock::new(controller));

                            connected_controllers.push(arc_controller.clone());

                            if selected_controller.is_none() {
                                if cfg!(debug_assertions) {
                                    let contr = arc_controller.read().unwrap();

                                    println!(
                                        "New active controller: {}({})",
                                        contr.name(),
                                        contr.id()
                                    );
                                }

                                *selected_controller = Some(arc_controller.clone());
                            }

                            arc_controller
                        };

                        event_callback(Event::ControllerAdded(controller))?;
                    }
                }
                SdlEvent::ControllerDeviceRemoved { which, .. } => {
                    let removed_controller = {
                        let mut selected_controller = self.selected_controller.write().unwrap();

                        if selected_controller.is_some() {
                            if cfg!(debug_assertions) {
                                let contr = selected_controller.as_ref().unwrap().read().unwrap();

                                println!(
                                    "Remove active controller: {}({})",
                                    contr.name(),
                                    contr.id()
                                );
                            }

                            // unwrap is save since we just tested for `is_some()`
                            if selected_controller.as_ref().unwrap().read().unwrap().id() == which {
                                *selected_controller = None;
                            }
                        }

                        let mut connected_controllers = self.connected_controllers.write().unwrap();

                        connected_controllers
                            .iter()
                            .position(|controller_cell| {
                                let controller = controller_cell.read().unwrap();
                                controller.id() == which
                            })
                            .map(|remove_index| {
                                let removed_controller =
                                    connected_controllers.swap_remove(remove_index);

                                if cfg!(debug_assertions) {
                                    let contr = removed_controller.read().unwrap();
                                    println!(
                                        "Controller removed: {}({})",
                                        contr.name(),
                                        contr.id()
                                    );
                                }

                                // if we removed the selected controller, take the controller at the first position if possible
                                if selected_controller.is_none()
                                    && !connected_controllers.is_empty()
                                {
                                    if cfg!(debug_assertions) {
                                        println!(
                                            "Set active controller: {}",
                                            connected_controllers[0].read().unwrap().name()
                                        );
                                    }

                                    *selected_controller = Some(connected_controllers[0].clone());
                                }

                                removed_controller
                            })
                    };

                    if let Some(removed_controller) = removed_controller {
                        event_callback(Event::ControllerRemoved(removed_controller))?;
                    }
                }
                // maybe make use of `which`, for support of multiple controllers
                SdlEvent::ControllerButtonDown {
                    button,
                    // which,
                    ..
                } => {
                    // // only call back if the selected controller pressed a button
                    // match self.selected_controller.read().unwrap().as_ref() {
                    //     Some(selected_controller) => {
                    //         if selected_controller.read().unwrap().id() != which {
                    //             continue;
                    //         }
                    //     }
                    //     None => continue,
                    // }

                    event_callback(Event::ControllerButtonDown(convert_button(button)))?;
                }
                // maybe make use of `which`, for support of multiple controllers
                SdlEvent::ControllerButtonUp {
                    button,
                    // which,
                    ..
                } => {
                    // // only call back if the selected controller released a button
                    // match self.selected_controller.read().unwrap().as_ref() {
                    //     Some(selected_controller) => {
                    //         if selected_controller.read().unwrap().id() != which {
                    //             continue;
                    //         }
                    //     }
                    //     None => continue,
                    // }

                    event_callback(Event::ControllerButtonUp(convert_button(button)))?;
                }
                SdlEvent::ControllerAxisMotion {
                    axis,
                    value,
                    // which,
                    ..
                } => {
                    let mut selected_controller = self.selected_controller.write().unwrap();

                    if let Some(controller) = selected_controller.as_mut() {
                        let mut controller = controller.write().unwrap();

                        // // only update axis, when selected controller made the change
                        // if controller.id() != which {
                        //     continue;
                        // }

                        // 1 / 32768 = 0,000030518
                        let normalized = value as f32 * 0.000_030_518;

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

                        controller_axis_changed = true;
                    }
                }
                SdlEvent::DropFile { filename, .. } => {
                    event_callback(Event::FileDrop(filename))?;
                }
                _ => (),
            }
        }

        if controller_axis_changed {
            if let Some(controller) = self.selected_controller.read().unwrap().as_ref() {
                let (left_trigger, right_trigger) = {
                    let mut controller_lock = controller.write().unwrap();

                    (
                        controller_lock.left_trigger(),
                        controller_lock.right_trigger(),
                    )
                };

                if let Some(right_trigger) = right_trigger {
                    if right_trigger {
                        event_callback(Event::ControllerButtonDown(
                            ControllerButton::RightTrigger,
                        ))?;
                    } else {
                        event_callback(Event::ControllerButtonUp(ControllerButton::RightTrigger))?;
                    }
                }

                if let Some(left_trigger) = left_trigger {
                    if left_trigger {
                        event_callback(Event::ControllerButtonDown(ControllerButton::LeftTrigger))?;
                    } else {
                        event_callback(Event::ControllerButtonUp(ControllerButton::LeftTrigger))?;
                    }
                }

                event_callback(Event::ControllerAxis(controller.clone()))?;
            }
        }

        Ok(true)
    }

    pub fn controllers(&self) -> RwLockReadGuard<'_, Vec<Arc<RwLock<Controller>>>> {
        self.connected_controllers.read().unwrap()
    }

    pub fn active_controller(&self) -> Result<Option<Arc<RwLock<Controller>>>> {
        Ok(self.selected_controller.read().unwrap().clone())
    }

    pub fn set_active_controller(&self, controller: &Arc<RwLock<Controller>>) {
        if let Some(res) = self
            .connected_controllers
            .read()
            .unwrap()
            .iter()
            .find(|c| Arc::ptr_eq(c, controller))
        {
            *self.selected_controller.write().unwrap() = Some(res.clone());
        }
    }
}

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