use common::FastSet;
use geom::{vec2, Vec2};
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use winit::event::{ElementState, KeyEvent, MouseScrollDelta, TouchPhase, WindowEvent};
use winit::keyboard::{NamedKey, PhysicalKey, SmolStr};
use winit::window::CursorIcon;

#[cfg(not(target_os = "android"))]
fn physical_scancode(key: &PhysicalKey) -> Option<u32> {
    use winit::platform::scancode::PhysicalKeyExtScancode;
    match key {
        PhysicalKey::Code(code) => code.to_scancode(),
        PhysicalKey::Unidentified(_) => None,
    }
}

// Winit's scancode compatibility API exists only for desktop platforms.
// Android input still uses logical keys; legacy user bindings by raw desktop
// scancode are intentionally unavailable there.
#[cfg(target_os = "android")]
fn physical_scancode(_key: &PhysicalKey) -> Option<u32> {
    None
}

lazy_static! {
    static ref CURSOR_ICON: Arc<Mutex<(CursorIcon, bool)>> =
        Arc::new(Mutex::new((CursorIcon::Default, false)));
}

pub fn set_cursor_icon(icon: CursorIcon) {
    let old = &mut *CURSOR_ICON.lock().unwrap();
    *old = (icon, old.1 || (old.0 != icon));
}

pub fn get_cursor_icon() -> (CursorIcon, bool) {
    let v = &mut *CURSOR_ICON.lock().unwrap();
    let to_ret = *v;
    v.1 = false;
    to_ret
}

#[derive(Default)]
pub struct InputContext {
    pub mouse: MouseInfo,
    pub keyboard: KeyboardInfo,
    pub cursor_left: bool,
    touches: HashMap<u64, Vec2>,
    primary_touch: Option<u64>,
    touch_start: Vec2,
    last_multitouch_centroid: Option<Vec2>,
    last_pinch_distance: Option<f32>,
    pub touch_pan_delta: Vec2,
    pub touch_rotate_delta: Vec2,
    pub touch_zoom_delta: f32,
}

impl InputContext {
    pub fn end_frame(&mut self) {
        self.keyboard.last_characters.clear();
        self.mouse.wheel_delta = 0.0;
        self.mouse.screen_delta = Vec2::ZERO;
        self.touch_pan_delta = Vec2::ZERO;
        self.touch_rotate_delta = Vec2::ZERO;
        self.touch_zoom_delta = 0.0;
        self.cursor_left = false;
    }

    pub fn handle_device(&mut self, event: &winit::event::DeviceEvent) {
        #[allow(clippy::single_match)]
        match event {
            winit::event::DeviceEvent::MouseMotion { delta } => {
                self.mouse.screen_delta += vec2(delta.0 as f32, delta.1 as f32);
            }
            _ => {}
        }
    }

    pub fn handle(&mut self, event: &WindowEvent) -> bool {
        match event {
            WindowEvent::CursorLeft { .. } => {
                self.cursor_left = true;
                true
            }
            WindowEvent::KeyboardInput {
                event:
                    KeyEvent {
                        text,
                        logical_key,
                        physical_key,
                        state,
                        ..
                    },
                ..
            } => {
                if let Some(k) = text {
                    self.keyboard.last_characters.extend(k.chars());
                }

                let code = Key::from(logical_key.clone());
                match state {
                    ElementState::Pressed => {
                        self.keyboard.pressed.insert(code);

                        if let Some(scancode) = physical_scancode(physical_key) {
                            self.keyboard.pressed_scancode.insert(scancode);
                        }
                    }
                    ElementState::Released => {
                        self.keyboard.pressed.remove(&code);
                        if let Some(scancode) = physical_scancode(physical_key) {
                            self.keyboard.pressed_scancode.remove(&scancode);
                        }
                    }
                };
                true
            }
            WindowEvent::CursorMoved { position, .. } => {
                self.mouse.screen = vec2(position.x as f32, position.y as f32);
                true
            }
            WindowEvent::MouseInput { button, state, .. } => {
                let b = MouseButton::from(*button);
                match state {
                    ElementState::Pressed => {
                        self.mouse.pressed.insert(b);
                    }
                    ElementState::Released => {
                        self.mouse.pressed.remove(&b);
                    }
                };
                true
            }
            WindowEvent::MouseWheel { delta, .. } => {
                match delta {
                    // Provided mainly by the scroll wheel of computer mouse devices
                    MouseScrollDelta::LineDelta(_, y) => {
                        self.mouse.wheel_delta = *y * 10.0;
                    }
                    // Provided by touchpads and drawing tablets
                    MouseScrollDelta::PixelDelta(pos) => {
                        self.mouse.wheel_delta = pos.y as f32;
                    }
                }

                true
            }
            WindowEvent::Touch(touch) => {
                let pos = vec2(touch.location.x as f32, touch.location.y as f32);
                match touch.phase {
                    TouchPhase::Started => {
                        self.touches.insert(touch.id, pos);
                        if self.touches.len() == 1 {
                            self.primary_touch = Some(touch.id);
                            self.touch_start = pos;
                            self.mouse.screen = pos;
                            self.mouse.pressed.insert(MouseButton::Left);
                        } else {
                            self.mouse.pressed.remove(&MouseButton::Left);
                            self.mouse.pressed.remove(&MouseButton::Middle);
                            self.mouse.pressed.insert(MouseButton::Right);
                            self.update_multitouch(false);
                        }
                    }
                    TouchPhase::Moved => {
                        let old = self.touches.insert(touch.id, pos).unwrap_or(pos);
                        if self.touches.len() == 1 && self.primary_touch == Some(touch.id) {
                            let delta = pos - old;
                            self.mouse.screen_delta += delta;
                            self.mouse.screen = pos;
                            if (pos - self.touch_start).mag2() > 12.0 * 12.0 {
                                self.mouse.pressed.remove(&MouseButton::Left);
                                self.mouse.pressed.insert(MouseButton::Middle);
                                self.touch_pan_delta += delta;
                            }
                        } else if self.touches.len() >= 2 {
                            self.update_multitouch(true);
                        }
                    }
                    TouchPhase::Ended | TouchPhase::Cancelled => {
                        self.touches.remove(&touch.id);
                        self.mouse.pressed.remove(&MouseButton::Left);
                        self.mouse.pressed.remove(&MouseButton::Middle);
                        self.mouse.pressed.remove(&MouseButton::Right);
                        self.last_multitouch_centroid = None;
                        self.last_pinch_distance = None;
                        if self.primary_touch == Some(touch.id) {
                            self.primary_touch = self.touches.keys().next().copied();
                        }
                    }
                }
                true
            }
            _ => false,
        }
    }

    fn update_multitouch(&mut self, apply_delta: bool) {
        let mut touches = self.touches.values();
        let Some(&a) = touches.next() else { return };
        let Some(&b) = touches.next() else { return };
        let centroid = (a + b) * 0.5;
        let distance = (a - b).mag2().sqrt();

        if apply_delta {
            if let Some(last) = self.last_multitouch_centroid {
                let delta = centroid - last;
                self.mouse.screen_delta += delta;
                self.touch_rotate_delta += delta;
            }
            if let Some(last) = self.last_pinch_distance {
                let delta = distance - last;
                self.mouse.wheel_delta += delta * 0.05;
                self.touch_zoom_delta += delta;
            }
        }

        self.mouse.screen = centroid;
        self.last_multitouch_centroid = Some(centroid);
        self.last_pinch_distance = Some(distance);
    }
}

#[derive(Clone, Default)]
pub struct MouseInfo {
    pub wheel_delta: f32,
    pub screen: Vec2,
    pub screen_delta: Vec2,
    pub pressed: FastSet<MouseButton>,
}

#[derive(Clone, Default)]
pub struct KeyboardInfo {
    pub pressed: FastSet<Key>,
    pub pressed_scancode: FastSet<u32>,
    pub last_characters: Vec<char>,
}

impl From<winit::event::MouseButton> for MouseButton {
    fn from(x: winit::event::MouseButton) -> MouseButton {
        match x {
            winit::event::MouseButton::Left => MouseButton::Left,
            winit::event::MouseButton::Right => MouseButton::Right,
            winit::event::MouseButton::Middle => MouseButton::Middle,
            winit::event::MouseButton::Other(v) => MouseButton::Other(v),
            winit::event::MouseButton::Back => MouseButton::Other(4),
            winit::event::MouseButton::Forward => MouseButton::Other(5),
        }
    }
}

impl From<winit::keyboard::Key> for Key {
    fn from(x: winit::keyboard::Key) -> Key {
        match x {
            winit::keyboard::Key::Named(named) => match named {
                NamedKey::Escape | NamedKey::BrowserBack => Key::Escape,
                NamedKey::F1 => Key::F1,
                NamedKey::F2 => Key::F2,
                NamedKey::F3 => Key::F3,
                NamedKey::F4 => Key::F4,
                NamedKey::F5 => Key::F5,
                NamedKey::F6 => Key::F6,
                NamedKey::F7 => Key::F7,
                NamedKey::F8 => Key::F8,
                NamedKey::F9 => Key::F9,
                NamedKey::F10 => Key::F10,
                NamedKey::F11 => Key::F11,
                NamedKey::F12 => Key::F12,
                NamedKey::F13 => Key::F13,
                NamedKey::F14 => Key::F14,
                NamedKey::F15 => Key::F15,
                NamedKey::F16 => Key::F16,
                NamedKey::F17 => Key::F17,
                NamedKey::F18 => Key::F18,
                NamedKey::F19 => Key::F19,
                NamedKey::F20 => Key::F20,
                NamedKey::F21 => Key::F21,
                NamedKey::F22 => Key::F22,
                NamedKey::F23 => Key::F23,
                NamedKey::F24 => Key::F24,
                NamedKey::Pause => Key::Pause,
                NamedKey::Insert => Key::Insert,
                NamedKey::Home => Key::Home,
                NamedKey::Delete => Key::Delete,
                NamedKey::End => Key::End,
                NamedKey::PageDown => Key::PageDown,
                NamedKey::PageUp => Key::PageUp,
                NamedKey::ArrowLeft => Key::ArrowLeft,
                NamedKey::ArrowUp => Key::ArrowUp,
                NamedKey::ArrowRight => Key::ArrowRight,
                NamedKey::ArrowDown => Key::ArrowDown,
                NamedKey::Backspace => Key::Backspace,
                NamedKey::Enter => Key::Return,
                NamedKey::Space => Key::Space,
                NamedKey::Compose => Key::Compose,
                NamedKey::NumLock => Key::Numlock,
                NamedKey::Convert => Key::Convert,
                NamedKey::MediaPlayPause => Key::MediaSelect,
                NamedKey::MediaStop => Key::MediaStop,
                NamedKey::Power => Key::Power,
                NamedKey::Tab => Key::Tab,
                NamedKey::Copy => Key::Copy,
                NamedKey::Paste => Key::Paste,
                NamedKey::Cut => Key::Cut,
                NamedKey::Control => Key::Control,
                NamedKey::Shift => Key::Shift,
                NamedKey::Alt => Key::Alt,
                NamedKey::PrintScreen => Key::PrintScreen,
                NamedKey::ScrollLock => Key::ScrollLock,
                _ => Key::Unlabeled,
            },
            winit::keyboard::Key::Character(c) => Key::Char(SmolStr::new(c.to_uppercase())),
            winit::keyboard::Key::Unidentified(_) => Key::Unlabeled,
            winit::keyboard::Key::Dead(_) => Key::Unlabeled,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
pub enum MouseButton {
    Left,
    Right,
    Middle,
    Other(u16),
}

impl Key {
    pub fn is_modifier(&self) -> bool {
        use Key::*;
        matches!(self, Control | Shift | Alt)
    }

    pub const fn c(s: &str) -> Key {
        Key::Char(SmolStr::new_inline(s))
    }
}

/// Symbolic name for a keyboard key.
/// We copy this from winit because we want to serialize it without implementing serialize for ALL
/// of winit's structs which would eat up compile time.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Key {
    // Make sure modifiers are put at the beginning when sorting
    Alt,
    Control,
    Shift,

    Char(SmolStr),

    Escape,

    F1,
    F2,
    F3,
    F4,
    F5,
    F6,
    F7,
    F8,
    F9,
    F10,
    F11,
    F12,
    F13,
    F14,
    F15,
    F16,
    F17,
    F18,
    F19,
    F20,
    F21,
    F22,
    F23,
    F24,

    PrintScreen,
    ScrollLock,
    Pause,

    Insert,
    Home,
    Delete,
    End,
    PageDown,
    PageUp,

    ArrowLeft,
    ArrowUp,
    ArrowRight,
    ArrowDown,

    Backspace,
    Return,
    Space,

    Compose,

    Numlock,

    Convert,
    MediaSelect,
    MediaStop,
    Mute,
    Power,
    Tab,
    Unlabeled,
    Copy,
    Paste,
    Cut,
}
