//! Translation between [`sdl2`] keyboard input and the standardized
//! [`keyboard_types`] representation used by Servo, druid, blitz, and other
//! Rust UI projects.
//!
//! The crate is pure glue — three stateless functions:
//!
//! | Function           | SDL2 input        | `keyboard_types` output      | Meaning                                     |
//! | ------------------ | ----------------- | ---------------------------- | ------------------------------------------- |
//! | [`into_code`]      | [`Scancode`]      | [`keyboard_types::Code`]      | Physical key position (layout-independent)  |
//! | [`into_key`]       | [`Keycode`]       | [`keyboard_types::Key`]       | Logical key value                           |
//! | [`into_modifiers`] | [`Mod`]           | [`keyboard_types::Modifiers`] | Active modifier flags                       |
//!
//! Both [`sdl2`] and [`keyboard_types`] are re-exported from the crate root, so
//! a dependency on this crate alone is enough to name their types. All of
//! `rust-sdl2`'s build features (`bundled`, `static-link`, `image`, `ttf`, …)
//! are forwarded.
//!
//! # Example
//!
//! Assemble a [`keyboard_types::KeyboardEvent`] from an SDL2 event:
//!
//! ```no_run
//! use sdl2_keyboard_types::{into_code, into_key, into_modifiers};
//! use sdl2_keyboard_types::keyboard_types::{KeyState, KeyboardEvent, Location};
//! use sdl2_keyboard_types::sdl2::event::Event;
//!
//! # fn handle(event: Event) {
//! if let Event::KeyDown { keycode, scancode, keymod, repeat, .. } = event {
//!     let event = KeyboardEvent {
//!         state: KeyState::Down,
//!         key: keycode.map(into_key).unwrap_or_default(),
//!         code: scancode.map(into_code).unwrap_or_default(),
//!         location: Location::Standard,
//!         modifiers: into_modifiers(keymod),
//!         repeat,
//!         is_composing: false,
//!     };
//! }
//! # }
//! ```
//!
//! # Text input
//!
//! [`into_key`] returns **bare, lowercase** [`Key::Character`] values and does
//! not apply Shift or any other modifier. For actual typed text (uppercase,
//! symbols, IME/composed input) use SDL2's text-input events rather than
//! deriving characters from this mapping.
//!
//! [`Key::Character`]: keyboard_types::Key::Character

pub use keyboard_types;
pub use sdl2;

use sdl2::keyboard::{Keycode, Mod, Scancode};

/// Translates SDL2 keyboard [`Mod`] flags into [`keyboard_types::Modifiers`].
///
/// Left and right variants of each modifier collapse into a single flag, e.g.
/// both `Mod::LCTRLMOD` and `Mod::RCTRLMOD` map to [`Modifiers::CONTROL`].
/// `Mod::LGUIMOD` / `Mod::RGUIMOD` (the OS / "Super" / Command key) map to
/// [`Modifiers::META`]. Lock states (Caps/Num/Mode) are not represented.
///
/// [`Modifiers::CONTROL`]: keyboard_types::Modifiers::CONTROL
/// [`Modifiers::META`]: keyboard_types::Modifiers::META
pub fn into_modifiers(m: Mod) -> keyboard_types::Modifiers {
    use keyboard_types::Modifiers;
    let mut mods = Modifiers::empty();
    if m.intersects(Mod::LCTRLMOD | Mod::RCTRLMOD) {
        mods |= Modifiers::CONTROL;
    }
    if m.intersects(Mod::LSHIFTMOD | Mod::RSHIFTMOD) {
        mods |= Modifiers::SHIFT;
    }
    if m.intersects(Mod::LALTMOD | Mod::RALTMOD) {
        mods |= Modifiers::ALT;
    }
    if m.intersects(Mod::LGUIMOD | Mod::RGUIMOD) {
        mods |= Modifiers::META;
    }

    mods
}

/// Translates an SDL2 [`Scancode`] into a [`keyboard_types::Code`].
///
/// A scancode identifies the *physical* position of a key, independent of the
/// active keyboard layout — the key in the QWERTY `A` position maps to
/// [`Code::KeyA`] regardless of what character it produces.
///
/// Scancodes without a `keyboard_types` equivalent map to [`Code::Unidentified`]
/// rather than panicking.
///
/// [`Code::KeyA`]: keyboard_types::Code::KeyA
/// [`Code::Unidentified`]: keyboard_types::Code::Unidentified
pub fn into_code(sc: Scancode) -> keyboard_types::Code {
    use keyboard_types::Code;
    match sc {
        Scancode::A => Code::KeyA,
        Scancode::B => Code::KeyB,
        Scancode::C => Code::KeyC,
        Scancode::D => Code::KeyD,
        Scancode::E => Code::KeyE,
        Scancode::F => Code::KeyF,
        Scancode::G => Code::KeyG,
        Scancode::H => Code::KeyH,
        Scancode::I => Code::KeyI,
        Scancode::J => Code::KeyJ,
        Scancode::K => Code::KeyK,
        Scancode::L => Code::KeyL,
        Scancode::M => Code::KeyM,
        Scancode::N => Code::KeyN,
        Scancode::O => Code::KeyO,
        Scancode::P => Code::KeyP,
        Scancode::Q => Code::KeyQ,
        Scancode::R => Code::KeyR,
        Scancode::S => Code::KeyS,
        Scancode::T => Code::KeyT,
        Scancode::U => Code::KeyU,
        Scancode::V => Code::KeyV,
        Scancode::W => Code::KeyW,
        Scancode::X => Code::KeyX,
        Scancode::Y => Code::KeyY,
        Scancode::Z => Code::KeyZ,

        Scancode::Num1 => Code::Digit1,
        Scancode::Num2 => Code::Digit2,
        Scancode::Num3 => Code::Digit3,
        Scancode::Num4 => Code::Digit4,
        Scancode::Num5 => Code::Digit5,
        Scancode::Num6 => Code::Digit6,
        Scancode::Num7 => Code::Digit7,
        Scancode::Num8 => Code::Digit8,
        Scancode::Num9 => Code::Digit9,
        Scancode::Num0 => Code::Digit0,

        Scancode::Return => Code::Enter,
        Scancode::Escape => Code::Escape,
        Scancode::Backspace => Code::Backspace,
        Scancode::Tab => Code::Tab,
        Scancode::Space => Code::Space,

        Scancode::Minus => Code::Minus,
        Scancode::Equals => Code::Equal,
        Scancode::LeftBracket => Code::BracketLeft,
        Scancode::RightBracket => Code::BracketRight,
        Scancode::Backslash => Code::Backslash,
        Scancode::Semicolon => Code::Semicolon,
        Scancode::Apostrophe => Code::Quote,
        Scancode::Grave => Code::Backquote,
        Scancode::Comma => Code::Comma,
        Scancode::Period => Code::Period,
        Scancode::Slash => Code::Slash,

        Scancode::CapsLock => Code::CapsLock,

        Scancode::F1 => Code::F1,
        Scancode::F2 => Code::F2,
        Scancode::F3 => Code::F3,
        Scancode::F4 => Code::F4,
        Scancode::F5 => Code::F5,
        Scancode::F6 => Code::F6,
        Scancode::F7 => Code::F7,
        Scancode::F8 => Code::F8,
        Scancode::F9 => Code::F9,
        Scancode::F10 => Code::F10,
        Scancode::F11 => Code::F11,
        Scancode::F12 => Code::F12,

        Scancode::PrintScreen => Code::PrintScreen,
        Scancode::ScrollLock => Code::ScrollLock,
        Scancode::Pause => Code::Pause,

        Scancode::Insert => Code::Insert,
        Scancode::Home => Code::Home,
        Scancode::PageUp => Code::PageUp,
        Scancode::Delete => Code::Delete,
        Scancode::End => Code::End,
        Scancode::PageDown => Code::PageDown,

        Scancode::Right => Code::ArrowRight,
        Scancode::Left => Code::ArrowLeft,
        Scancode::Down => Code::ArrowDown,
        Scancode::Up => Code::ArrowUp,

        Scancode::NumLockClear => Code::NumLock,
        Scancode::KpDivide => Code::NumpadDivide,
        Scancode::KpMultiply => Code::NumpadMultiply,
        Scancode::KpMinus => Code::NumpadSubtract,
        Scancode::KpPlus => Code::NumpadAdd,
        Scancode::KpEnter => Code::NumpadEnter,
        Scancode::Kp1 => Code::Numpad1,
        Scancode::Kp2 => Code::Numpad2,
        Scancode::Kp3 => Code::Numpad3,
        Scancode::Kp4 => Code::Numpad4,
        Scancode::Kp5 => Code::Numpad5,
        Scancode::Kp6 => Code::Numpad6,
        Scancode::Kp7 => Code::Numpad7,
        Scancode::Kp8 => Code::Numpad8,
        Scancode::Kp9 => Code::Numpad9,
        Scancode::Kp0 => Code::Numpad0,
        Scancode::KpPeriod => Code::NumpadDecimal,

        Scancode::LCtrl => Code::ControlLeft,
        Scancode::LShift => Code::ShiftLeft,
        Scancode::LAlt => Code::AltLeft,
        Scancode::LGui => Code::MetaLeft,
        Scancode::RCtrl => Code::ControlRight,
        Scancode::RShift => Code::ShiftRight,
        Scancode::RAlt => Code::AltRight,
        Scancode::RGui => Code::MetaRight,

        _ => Code::Unidentified,
    }
}

/// Translates an SDL2 [`Keycode`] into a [`keyboard_types::Key`].
///
/// A keycode carries the *logical* meaning of a key after the OS keyboard
/// layout is applied. Printable keys become [`Key::Character`]; named keys such
/// as Enter or the arrows become [`Key::Named`].
///
/// [`Key::Character`] values are emitted **bare and lowercase** (e.g. `a`, `1`,
/// `-`) without applying Shift or any other modifier — `Shift` + `a` still maps
/// to `"a"`. For actual typed text (uppercase, symbols, IME/composed input)
/// rely on SDL2's text-input events rather than this mapping.
///
/// Left/right modifier keys collapse to a single named value (both `LCtrl` and
/// `RCtrl` map to [`NamedKey::Control`]). Keycodes without a `keyboard_types`
/// equivalent map to `Key::Named(`[`NamedKey::Unidentified`]`)` rather than
/// panicking.
///
/// [`Key::Character`]: keyboard_types::Key::Character
/// [`Key::Named`]: keyboard_types::Key::Named
/// [`NamedKey::Control`]: keyboard_types::NamedKey::Control
/// [`NamedKey::Unidentified`]: keyboard_types::NamedKey::Unidentified
pub fn into_key(kc: Keycode) -> keyboard_types::Key {
    use keyboard_types::Key;
    use keyboard_types::NamedKey;
    match kc {
        Keycode::A => Key::Character("a".into()),
        Keycode::B => Key::Character("b".into()),
        Keycode::C => Key::Character("c".into()),
        Keycode::D => Key::Character("d".into()),
        Keycode::E => Key::Character("e".into()),
        Keycode::F => Key::Character("f".into()),
        Keycode::G => Key::Character("g".into()),
        Keycode::H => Key::Character("h".into()),
        Keycode::I => Key::Character("i".into()),
        Keycode::J => Key::Character("j".into()),
        Keycode::K => Key::Character("k".into()),
        Keycode::L => Key::Character("l".into()),
        Keycode::M => Key::Character("m".into()),
        Keycode::N => Key::Character("n".into()),
        Keycode::O => Key::Character("o".into()),
        Keycode::P => Key::Character("p".into()),
        Keycode::Q => Key::Character("q".into()),
        Keycode::R => Key::Character("r".into()),
        Keycode::S => Key::Character("s".into()),
        Keycode::T => Key::Character("t".into()),
        Keycode::U => Key::Character("u".into()),
        Keycode::V => Key::Character("v".into()),
        Keycode::W => Key::Character("w".into()),
        Keycode::X => Key::Character("x".into()),
        Keycode::Y => Key::Character("y".into()),
        Keycode::Z => Key::Character("z".into()),

        Keycode::Num1 => Key::Character("1".into()),
        Keycode::Num2 => Key::Character("2".into()),
        Keycode::Num3 => Key::Character("3".into()),
        Keycode::Num4 => Key::Character("4".into()),
        Keycode::Num5 => Key::Character("5".into()),
        Keycode::Num6 => Key::Character("6".into()),
        Keycode::Num7 => Key::Character("7".into()),
        Keycode::Num8 => Key::Character("8".into()),
        Keycode::Num9 => Key::Character("9".into()),
        Keycode::Num0 => Key::Character("0".into()),

        Keycode::Space => Key::Character(" ".into()),
        Keycode::Return => Key::Named(NamedKey::Enter),
        Keycode::Escape => Key::Named(NamedKey::Escape),
        Keycode::Backspace => Key::Named(NamedKey::Backspace),
        Keycode::Tab => Key::Named(NamedKey::Tab),

        Keycode::Minus => Key::Character("-".into()),
        Keycode::Equals => Key::Character("=".into()),
        Keycode::LeftBracket => Key::Character("[".into()),
        Keycode::RightBracket => Key::Character("]".into()),
        Keycode::Backslash => Key::Character("\\".into()),
        Keycode::Semicolon => Key::Character(";".into()),
        Keycode::Comma => Key::Character(",".into()),
        Keycode::Period => Key::Character(".".into()),
        Keycode::Slash => Key::Character("/".into()),

        Keycode::CapsLock => Key::Named(NamedKey::CapsLock),

        Keycode::F1 => Key::Named(NamedKey::F1),
        Keycode::F2 => Key::Named(NamedKey::F2),
        Keycode::F3 => Key::Named(NamedKey::F3),
        Keycode::F4 => Key::Named(NamedKey::F4),
        Keycode::F5 => Key::Named(NamedKey::F5),
        Keycode::F6 => Key::Named(NamedKey::F6),
        Keycode::F7 => Key::Named(NamedKey::F7),
        Keycode::F8 => Key::Named(NamedKey::F8),
        Keycode::F9 => Key::Named(NamedKey::F9),
        Keycode::F10 => Key::Named(NamedKey::F10),
        Keycode::F11 => Key::Named(NamedKey::F11),
        Keycode::F12 => Key::Named(NamedKey::F12),

        Keycode::PrintScreen => Key::Named(NamedKey::PrintScreen),
        Keycode::ScrollLock => Key::Named(NamedKey::ScrollLock),
        Keycode::Pause => Key::Named(NamedKey::Pause),

        Keycode::Insert => Key::Named(NamedKey::Insert),
        Keycode::Home => Key::Named(NamedKey::Home),
        Keycode::PageUp => Key::Named(NamedKey::PageUp),
        Keycode::Delete => Key::Named(NamedKey::Delete),
        Keycode::End => Key::Named(NamedKey::End),
        Keycode::PageDown => Key::Named(NamedKey::PageDown),

        Keycode::Right => Key::Named(NamedKey::ArrowRight),
        Keycode::Left => Key::Named(NamedKey::ArrowLeft),
        Keycode::Down => Key::Named(NamedKey::ArrowDown),
        Keycode::Up => Key::Named(NamedKey::ArrowUp),

        Keycode::NumLockClear => Key::Named(NamedKey::NumLock),
        Keycode::KpDivide => Key::Character("/".into()),
        Keycode::KpMultiply => Key::Character("*".into()),
        Keycode::KpMinus => Key::Character("-".into()),
        Keycode::KpPlus => Key::Character("+".into()),
        Keycode::KpEnter => Key::Named(NamedKey::Enter),
        Keycode::Kp1 => Key::Character("1".into()),
        Keycode::Kp2 => Key::Character("2".into()),
        Keycode::Kp3 => Key::Character("3".into()),
        Keycode::Kp4 => Key::Character("4".into()),
        Keycode::Kp5 => Key::Character("5".into()),
        Keycode::Kp6 => Key::Character("6".into()),
        Keycode::Kp7 => Key::Character("7".into()),
        Keycode::Kp8 => Key::Character("8".into()),
        Keycode::Kp9 => Key::Character("9".into()),
        Keycode::Kp0 => Key::Character("0".into()),
        Keycode::KpPeriod => Key::Character(".".into()),

        Keycode::LCtrl => Key::Named(NamedKey::Control),
        Keycode::LShift => Key::Named(NamedKey::Shift),
        Keycode::LAlt => Key::Named(NamedKey::Alt),
        Keycode::LGui => Key::Named(NamedKey::Meta),
        Keycode::RCtrl => Key::Named(NamedKey::Control),
        Keycode::RShift => Key::Named(NamedKey::Shift),
        Keycode::RAlt => Key::Named(NamedKey::Alt),
        Keycode::RGui => Key::Named(NamedKey::Meta),

        _ => Key::Named(NamedKey::Unidentified),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use keyboard_types::{Code, Key, Modifiers, NamedKey};

    #[test]
    fn modifiers_empty() {
        assert_eq!(into_modifiers(Mod::NOMOD), Modifiers::empty());
    }

    #[test]
    fn modifiers_single() {
        assert_eq!(into_modifiers(Mod::LCTRLMOD), Modifiers::CONTROL);
        assert_eq!(into_modifiers(Mod::LSHIFTMOD), Modifiers::SHIFT);
        assert_eq!(into_modifiers(Mod::LALTMOD), Modifiers::ALT);
        assert_eq!(into_modifiers(Mod::LGUIMOD), Modifiers::META);
    }

    #[test]
    fn modifiers_left_and_right_are_equivalent() {
        assert_eq!(into_modifiers(Mod::LCTRLMOD), into_modifiers(Mod::RCTRLMOD));
        assert_eq!(
            into_modifiers(Mod::LSHIFTMOD),
            into_modifiers(Mod::RSHIFTMOD)
        );
        assert_eq!(into_modifiers(Mod::LALTMOD), into_modifiers(Mod::RALTMOD));
        assert_eq!(into_modifiers(Mod::LGUIMOD), into_modifiers(Mod::RGUIMOD));
    }

    #[test]
    fn modifiers_combined() {
        assert_eq!(
            into_modifiers(Mod::LCTRLMOD | Mod::RSHIFTMOD),
            Modifiers::CONTROL | Modifiers::SHIFT
        );
    }

    #[test]
    fn code_letter_digit_and_named() {
        assert_eq!(into_code(Scancode::A), Code::KeyA);
        assert_eq!(into_code(Scancode::Num1), Code::Digit1);
        assert_eq!(into_code(Scancode::Return), Code::Enter);
        assert_eq!(into_code(Scancode::Left), Code::ArrowLeft);
    }

    #[test]
    fn code_numpad_and_modifier() {
        assert_eq!(into_code(Scancode::Kp0), Code::Numpad0);
        assert_eq!(into_code(Scancode::KpEnter), Code::NumpadEnter);
        assert_eq!(into_code(Scancode::LCtrl), Code::ControlLeft);
        assert_eq!(into_code(Scancode::RGui), Code::MetaRight);
    }

    #[test]
    fn code_unmapped_is_unidentified() {
        assert_eq!(into_code(Scancode::Mute), Code::Unidentified);
    }

    #[test]
    fn key_character_is_bare_lowercase() {
        assert_eq!(into_key(Keycode::A), Key::Character("a".into()));
        assert_eq!(into_key(Keycode::Num1), Key::Character("1".into()));
        assert_eq!(into_key(Keycode::Space), Key::Character(" ".into()));
        assert_eq!(into_key(Keycode::Minus), Key::Character("-".into()));
    }

    #[test]
    fn key_named() {
        assert_eq!(into_key(Keycode::Return), Key::Named(NamedKey::Enter));
        assert_eq!(into_key(Keycode::Up), Key::Named(NamedKey::ArrowUp));
        assert_eq!(into_key(Keycode::F1), Key::Named(NamedKey::F1));
    }

    #[test]
    fn key_numpad_emits_characters() {
        assert_eq!(into_key(Keycode::Kp0), Key::Character("0".into()));
        assert_eq!(into_key(Keycode::KpEnter), Key::Named(NamedKey::Enter));
    }

    #[test]
    fn key_left_right_modifiers_collapse() {
        assert_eq!(into_key(Keycode::LCtrl), Key::Named(NamedKey::Control));
        assert_eq!(into_key(Keycode::RCtrl), Key::Named(NamedKey::Control));
    }

    #[test]
    fn key_unmapped_is_unidentified() {
        assert_eq!(into_key(Keycode::Mute), Key::Named(NamedKey::Unidentified));
    }
}
