use std::collections::HashSet;

use glutin::{
    event::{
        ElementState, Event, KeyboardInput, MouseScrollDelta, WindowEvent as GlutinWindowEvent,
    },
    event_loop::{ControlFlow, EventLoop},
    platform::desktop::EventLoopExtDesktop,
};
use shrev::{EventChannel, ReaderId};
use space_crush_common::misc::{LogResult, WorldHelper};
use specs::World;

use crate::{misc::Window, Error};

pub use glutin::event::{MouseButton, VirtualKeyCode};

pub struct Events {
    keys: HashSet<VirtualKeyCode>,
    event_loop: EventLoop<()>,
    mouse_pos: Option<(f64, f64)>,
    mouse_lock: Option<(f64, f64)>,
    control_event_id: ReaderId<ControlEvent>,
}

impl Events {
    pub fn new(world: &mut World) -> Result<Self, Error> {
        world.insert(EventChannel::<ControlEvent>::default());
        world.insert(EventChannel::<MouseEvent>::default());
        world.insert(EventChannel::<WindowEvent>::default());
        world.insert(EventChannel::<KeyboardEvent>::default());

        let control_event_id = world.register_event_reader::<ControlEvent>()?;

        Ok(Self {
            keys: HashSet::new(),
            event_loop: EventLoop::new(),
            mouse_pos: None,
            mouse_lock: None,
            control_event_id,
        })
    }
}

impl Events {
    pub fn handle(&self) -> &EventLoop<()> {
        &self.event_loop
    }

    pub fn process(&mut self, world: &World, window: &Window) {
        let control_events = world.fetch::<EventChannel<ControlEvent>>();
        let mut mouse_events = world.fetch_mut::<EventChannel<MouseEvent>>();
        let mut window_events = world.fetch_mut::<EventChannel<WindowEvent>>();
        let mut keyboard_events = world.fetch_mut::<EventChannel<KeyboardEvent>>();

        let keys = &mut self.keys;
        let mouse_pos = &mut self.mouse_pos;
        let event_loop = &mut self.event_loop;
        let mouse_lock = &mut self.mouse_lock;

        let events = control_events.read(&mut self.control_event_id);
        for event in events {
            match event {
                ControlEvent::LockMouse => {
                    *mouse_lock = *mouse_pos;
                    window
                        .set_mouse_locked(true)
                        .warn("Unable to lock mouse to window");
                }
                ControlEvent::UnlockMouse => {
                    *mouse_lock = None;
                    window
                        .set_mouse_locked(false)
                        .warn("Unable to lock mouse to window");
                }
            }
        }

        let mut has_mouse_moved = false;
        event_loop.run_return(|event, _target, flow_control| {
            *flow_control = ControlFlow::Poll;

            match event {
                Event::MainEventsCleared | Event::RedrawRequested(_) => {
                    *flow_control = ControlFlow::Exit;
                }
                Event::WindowEvent { event, .. } => match event {
                    /* Mouse Events */
                    GlutinWindowEvent::CursorMoved { position, .. } => {
                        if mouse_lock.is_none() {
                            mouse_events
                                .single_write(MouseEvent::Move(position.x as _, position.y as _));
                        }

                        if let Some((x, y)) = mouse_pos {
                            let dx = position.x - *x;
                            let dy = position.y - *y;

                            mouse_events.single_write(MouseEvent::Delta(dx as _, dy as _));
                        }

                        has_mouse_moved = true;
                        *mouse_pos = Some((position.x as _, position.y as _));
                    }
                    GlutinWindowEvent::MouseInput {
                        state: ElementState::Pressed,
                        button,
                        ..
                    } => {
                        mouse_events.single_write(MouseEvent::ButtonDown(button));
                    }
                    GlutinWindowEvent::MouseInput {
                        state: ElementState::Released,
                        button,
                        ..
                    } => {
                        mouse_events.single_write(MouseEvent::ButtonUp(button));
                    }
                    GlutinWindowEvent::MouseWheel {
                        delta: MouseScrollDelta::LineDelta(x, y),
                        ..
                    } => {
                        if x != 0.0 {
                            mouse_events.single_write(MouseEvent::ScrollX(x));
                        }

                        if y != 0.0 {
                            mouse_events.single_write(MouseEvent::ScrollY(y));
                        }
                    }
                    GlutinWindowEvent::CursorLeft { .. } => {
                        *mouse_pos = None;
                    }

                    /* Key Event */
                    GlutinWindowEvent::KeyboardInput {
                        input:
                            KeyboardInput {
                                state: ElementState::Pressed,
                                virtual_keycode: Some(key),
                                ..
                            },
                        ..
                    } => {
                        if keys.insert(key) {
                            keyboard_events.single_write(KeyboardEvent::KeyDown(key));
                        }

                        keyboard_events.single_write(KeyboardEvent::KeyPress(key));
                    }
                    GlutinWindowEvent::KeyboardInput {
                        input:
                            KeyboardInput {
                                state: ElementState::Released,
                                virtual_keycode: Some(key),
                                ..
                            },
                        ..
                    } => {
                        if keys.remove(&key) {
                            keyboard_events.single_write(KeyboardEvent::KeyUp(key));
                        }
                    }

                    /* Window Event */
                    GlutinWindowEvent::Resized(pos) => {
                        window_events.single_write(WindowEvent::Resize(pos.width, pos.height));
                    }
                    GlutinWindowEvent::CloseRequested => {
                        window_events.single_write(WindowEvent::Close);
                    }

                    /* Ignore */
                    _ => (),
                },
                _ => (),
            }
        });

        if let Some((x, y)) = self.mouse_lock {
            if has_mouse_moved {
                window.set_mouse_pos(x, y).warn("Unable to set mouse pos");
                self.mouse_pos = Some((x, y));
            }
        }
    }
}

pub enum ControlEvent {
    LockMouse,
    UnlockMouse,
}

pub enum WindowEvent {
    Resize(u32, u32),
    Close,
}

pub enum MouseEvent {
    Move(f32, f32),
    Delta(f32, f32),
    ButtonDown(MouseButton),
    ButtonUp(MouseButton),
    ScrollX(f32),
    ScrollY(f32),
}

pub enum KeyboardEvent {
    KeyUp(VirtualKeyCode),
    KeyDown(VirtualKeyCode),
    KeyPress(VirtualKeyCode),
}
