// SPDX-License-Identifier: MIT

use core::ops::{Deref, DerefMut};
use core::{char, mem, slice};

use crate::{ClipboardAction, DragAction};

pub const EVENT_NONE: i64 = 0;
pub const EVENT_KEY: i64 = 1;
pub const EVENT_MOUSE: i64 = 2;
pub const EVENT_BUTTON: i64 = 3;
pub const EVENT_SCROLL: i64 = 4;
pub const EVENT_QUIT: i64 = 5;
pub const EVENT_FOCUS: i64 = 6;
pub const EVENT_MOVE: i64 = 7;
pub const EVENT_RESIZE: i64 = 8;
pub const EVENT_SCREEN: i64 = 9;
pub const EVENT_CLIPBOARD: i64 = 10;
pub const EVENT_MOUSE_RELATIVE: i64 = 11;
pub const EVENT_DROP: i64 = 12;
pub const EVENT_TEXT_INPUT: i64 = 13;
pub const EVENT_CLIPBOARD_UPDATE: i64 = 14;
pub const EVENT_HOVER: i64 = 15;
pub const EVENT_SCALE: i64 = 16;
pub const EVENT_DRAG_ENTER: i64 = 17;
pub const EVENT_DRAG_MOVE: i64 = 18;
pub const EVENT_DRAG_LEAVE: i64 = 19;
pub const EVENT_CONTROLLER_AXIS: i64 = 20;
pub const EVENT_CONTROLLER_BUTTON: i64 = 21;

/// An optional event
#[derive(Copy, Clone, Debug)]
pub enum EventOption {
    /// A key event
    Key(KeyEvent),
    /// A text input event
    TextInput(TextInputEvent),
    /// A mouse event (absolute)
    Mouse(MouseEvent),
    /// A mouse event (relative)
    MouseRelative(MouseRelativeEvent),
    /// A mouse button event
    Button(ButtonEvent),
    /// A mouse scroll event
    Scroll(ScrollEvent),
    /// A quit request event
    Quit(QuitEvent),
    /// A focus event
    Focus(FocusEvent),
    /// A move event
    Move(MoveEvent),
    /// A resize event
    Resize(ResizeEvent),
    /// A screen report event
    Screen(ScreenEvent),
    /// A screen scale event
    Scale(ScaleEvent),
    /// A clipboard event
    Clipboard(ClipboardEvent),
    /// A clipboard update event
    ClipboardUpdate(ClipboardUpdateEvent),
    /// A drag enter event
    DragEnter(DragEnterEvent),
    /// A drag move event
    DragMove(DragMoveEvent),
    /// A drag leave event
    DragLeave(DragLeaveEvent),
    /// A drop event
    Drop(DropEvent),
    /// A hover event
    Hover(HoverEvent),
    /// A controller axis event
    ControllerAxis(ControllerAxisEvent),
    /// A controller button event
    ControllerButton(ControllerButtonEvent),
    /// An unknown event
    Unknown(Event),
    /// No event
    None,
}

/// An event
#[derive(Copy, Clone, Debug)]
#[repr(packed)]
pub struct Event {
    pub code: i64,
    pub a: i64,
    pub b: i64,
}

#[allow(clippy::new_without_default)]
impl Event {
    /// Create a null event
    pub fn new() -> Event {
        Event {
            code: 0,
            a: 0,
            b: 0,
        }
    }

    /// Convert the event ot an optional event
    // TODO: Consider doing this via a From trait.
    pub fn to_option(self) -> EventOption {
        match self.code {
            EVENT_NONE => EventOption::None,
            EVENT_KEY => EventOption::Key(KeyEvent::from_event(self)),
            EVENT_TEXT_INPUT => EventOption::TextInput(TextInputEvent::from_event(self)),
            EVENT_MOUSE => EventOption::Mouse(MouseEvent::from_event(self)),
            EVENT_MOUSE_RELATIVE => {
                EventOption::MouseRelative(MouseRelativeEvent::from_event(self))
            }
            EVENT_BUTTON => EventOption::Button(ButtonEvent::from_event(self)),
            EVENT_SCROLL => EventOption::Scroll(ScrollEvent::from_event(self)),
            EVENT_QUIT => EventOption::Quit(QuitEvent::from_event(self)),
            EVENT_FOCUS => EventOption::Focus(FocusEvent::from_event(self)),
            EVENT_MOVE => EventOption::Move(MoveEvent::from_event(self)),
            EVENT_RESIZE => EventOption::Resize(ResizeEvent::from_event(self)),
            EVENT_SCREEN => EventOption::Screen(ScreenEvent::from_event(self)),
            EVENT_CLIPBOARD => EventOption::Clipboard(ClipboardEvent::from_event(self)),
            EVENT_CLIPBOARD_UPDATE => {
                EventOption::ClipboardUpdate(ClipboardUpdateEvent::from_event(self))
            }
            EVENT_DROP => EventOption::Drop(DropEvent::from_event(self)),
            EVENT_HOVER => EventOption::Hover(HoverEvent::from_event(self)),
            EVENT_SCALE => EventOption::Scale(ScaleEvent::from_event(self)),
            EVENT_DRAG_ENTER => EventOption::DragEnter(DragEnterEvent::from_event(self)),
            EVENT_DRAG_MOVE => EventOption::DragMove(DragMoveEvent::from_event(self)),
            EVENT_DRAG_LEAVE => EventOption::DragLeave(DragLeaveEvent::from_event(self)),
            EVENT_CONTROLLER_AXIS => {
                EventOption::ControllerAxis(ControllerAxisEvent::from_event(self))
            }
            EVENT_CONTROLLER_BUTTON => {
                EventOption::ControllerButton(ControllerButtonEvent::from_event(self))
            }
            _ => EventOption::Unknown(self),
        }
    }
}

impl Deref for Event {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        unsafe {
            slice::from_raw_parts(self as *const Event as *const u8, mem::size_of::<Event>())
                as &[u8]
        }
    }
}

impl DerefMut for Event {
    fn deref_mut(&mut self) -> &mut [u8] {
        unsafe {
            slice::from_raw_parts_mut(self as *mut Event as *mut u8, mem::size_of::<Event>())
                as &mut [u8]
        }
    }
}

// Mapped closely with scancodes from XT keyboard.
// Unconventional ranges should be in 0x60-0x7F, 0x80 and above are for media keys
// See https://aeb.win.tue.nl/linux/kbd/scancodes-10.html

pub const K_A: u8 = 0x1E;
pub const K_B: u8 = 0x30;
pub const K_C: u8 = 0x2E;
pub const K_D: u8 = 0x20;
pub const K_E: u8 = 0x12;
pub const K_F: u8 = 0x21;
pub const K_G: u8 = 0x22;
pub const K_H: u8 = 0x23;
pub const K_I: u8 = 0x17;
pub const K_J: u8 = 0x24;
pub const K_K: u8 = 0x25;
pub const K_L: u8 = 0x26;
pub const K_M: u8 = 0x32;
pub const K_N: u8 = 0x31;
pub const K_O: u8 = 0x18;
pub const K_P: u8 = 0x19;
pub const K_Q: u8 = 0x10;
pub const K_R: u8 = 0x13;
pub const K_S: u8 = 0x1F;
pub const K_T: u8 = 0x14;
pub const K_U: u8 = 0x16;
pub const K_V: u8 = 0x2F;
pub const K_W: u8 = 0x11;
pub const K_X: u8 = 0x2D;
pub const K_Y: u8 = 0x15;
pub const K_Z: u8 = 0x2C;
pub const K_0: u8 = 0x0B;
pub const K_1: u8 = 0x02;
pub const K_2: u8 = 0x03;
pub const K_3: u8 = 0x04;
pub const K_4: u8 = 0x05;
pub const K_5: u8 = 0x06;
pub const K_6: u8 = 0x07;
pub const K_7: u8 = 0x08;
pub const K_8: u8 = 0x09;
pub const K_9: u8 = 0x0A;

// Numpad keys (codes 0x70-0x7f)

pub const K_NUM_0: u8 = 0x70;
pub const K_NUM_1: u8 = 0x71;
pub const K_NUM_2: u8 = 0x72;
pub const K_NUM_3: u8 = 0x73;
pub const K_NUM_4: u8 = 0x74;
pub const K_NUM_5: u8 = 0x75;
pub const K_NUM_6: u8 = 0x76;
pub const K_NUM_7: u8 = 0x77;
pub const K_NUM_8: u8 = 0x78;
pub const K_NUM_9: u8 = 0x79;
pub const K_NUM_PERIOD: u8 = 0x7a;
pub const K_NUM_PLUS: u8 = 0x7c;
pub const K_NUM_MINUS: u8 = 0x7b;
pub const K_NUM_ASTERISK: u8 = 0x7d;
pub const K_NUM_SLASH: u8 = 0x7e;
pub const K_NUM_ENTER: u8 = 0x7f;

/// Tick/tilde key
pub const K_TICK: u8 = 0x29;
/// Minus/underline key
pub const K_MINUS: u8 = 0x0C;
/// Equals/plus key
pub const K_EQUALS: u8 = 0x0D;
/// Backslash/pipe key
pub const K_BACKSLASH: u8 = 0x2B;
/// Bracket open key
pub const K_BRACE_OPEN: u8 = 0x1A;
/// Bracket close key
pub const K_BRACE_CLOSE: u8 = 0x1B;
/// Semicolon key
pub const K_SEMICOLON: u8 = 0x27;
/// Quote key
pub const K_QUOTE: u8 = 0x28;
/// Comma key
pub const K_COMMA: u8 = 0x33;
/// Period key
pub const K_PERIOD: u8 = 0x34;
/// Slash key
pub const K_SLASH: u8 = 0x35;
/// Backspace key
pub const K_BKSP: u8 = 0x0E;
/// Space key
pub const K_SPACE: u8 = 0x39;
/// Tab key
pub const K_TAB: u8 = 0x0F;
/// Capslock
pub const K_CAPS: u8 = 0x3A;
/// Left shift
pub const K_LEFT_SHIFT: u8 = 0x2A;
/// Right shift
pub const K_RIGHT_SHIFT: u8 = 0x36;
/// Control key (deprecated)
pub const K_CTRL: u8 = 0x1D;
/// Left control key
pub const K_LEFT_CTRL: u8 = 0x1D;
/// Right control key
pub const K_RIGHT_CTRL: u8 = 0x64;
/// Alt key
pub const K_ALT: u8 = 0x38;
/// AltGr key
pub const K_ALT_GR: u8 = 0x64;
/// Enter key
pub const K_ENTER: u8 = 0x1C;
/// Escape key
pub const K_ESC: u8 = 0x01;
/// Print screen key
pub const K_PRTSC: u8 = 0x37;
/// F1 key
pub const K_F1: u8 = 0x3B;
/// F2 key
pub const K_F2: u8 = 0x3C;
/// F3 key
pub const K_F3: u8 = 0x3D;
/// F4 key
pub const K_F4: u8 = 0x3E;
/// F5 key
pub const K_F5: u8 = 0x3F;
/// F6 key
pub const K_F6: u8 = 0x40;
/// F7 key
pub const K_F7: u8 = 0x41;
/// F8 key
pub const K_F8: u8 = 0x42;
/// F9 key
pub const K_F9: u8 = 0x43;
/// F10 key
pub const K_F10: u8 = 0x44;
/// Num lock key
pub const K_NUM: u8 = 0x45;
/// Scroll lock key
pub const K_SCROLL: u8 = 0x46;
/// Home key
pub const K_HOME: u8 = 0x47;
/// Up key
pub const K_UP: u8 = 0x48;
/// Page up key
pub const K_PGUP: u8 = 0x49;
/// Left key
pub const K_LEFT: u8 = 0x4B;
/// Right key
pub const K_RIGHT: u8 = 0x4D;
/// End key
pub const K_END: u8 = 0x4F;
/// Down key
pub const K_DOWN: u8 = 0x50;
/// Page down key
pub const K_PGDN: u8 = 0x51;
/// Insert key (deprecated)
pub const K_INS: u8 = 0x52;
/// Insert key
pub const K_INSERT: u8 = 0x52;
/// Delete key
pub const K_DEL: u8 = 0x53;
/// F11 key
pub const K_F11: u8 = 0x57;
/// F12 key
pub const K_F12: u8 = 0x58;
/// SUPER/META/WIN Key (deprecated)
pub const K_SUPER: u8 = 0x5B;
/// SUPER/META/WIN Left Key
pub const K_LEFT_SUPER: u8 = 0x5B;
/// SUPER/META/WIN Right Key
pub const K_RIGHT_SUPER: u8 = 0x5C;
/// Application key
pub const K_APP: u8 = 0x5D;
/// Power off key
pub const K_POWER: u8 = 0x5E;
/// Sleep key
pub const K_SLEEP: u8 = 0x5F;

// Media keys are derived from MF II keyboards
// See https://www.scs.stanford.edu/10wi-cs140/pintos/specs/kbd/scancodes-5.html

/// Media Key for Volume toggle (mute/unmute)
pub const K_VOLUME_TOGGLE: u8 = 0x80 + 0x20;
/// Media Key for Volume Down
pub const K_VOLUME_DOWN: u8 = 0x80 + 0x2E;
/// Media Key for Volume Up
pub const K_VOLUME_UP: u8 = 0x80 + 0x30;
/// Media Key for Play/pause
pub const K_MEDIA_PLAY_PAUSE: u8 = 0x80 + 0x22;
/// Media Key for Stop
pub const K_MEDIA_STOP: u8 = 0x80 + 0x24;
/// Media Key for Fast forward/next track
pub const K_MEDIA_FAST_FORWARD: u8 = 0x80 + 0x19;
/// Media Key for Rewind/previous track
pub const K_MEDIA_REWIND: u8 = 0x80 + 0x10;

/// A key event (such as a pressed key)
#[derive(Copy, Clone, Debug)]
pub struct KeyEvent {
    /// Deprecated, always zero. use TextInputEvent instead
    pub character: char,
    /// The scancode of the key
    pub scancode: u8,
    /// Was it pressed?
    pub pressed: bool,
}

impl KeyEvent {
    /// Convert to an `Event`
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_KEY,
            a: self.character as i64,
            b: self.scancode as i64 | (self.pressed as i64) << 8,
        }
    }

    /// Convert from an `Event`
    pub fn from_event(event: Event) -> KeyEvent {
        KeyEvent {
            character: char::from_u32(event.a as u32).unwrap_or('\0'),
            scancode: event.b as u8,
            pressed: event.b & 1 << 8 == 1 << 8,
        }
    }
}

/// A supplement to KeyEvent that report a character.
/// Affected by modifier keys and keyboard layout.
/// Control character (e.g. Backspace and Enter key) does not count as a TextInputEvent.
#[derive(Copy, Clone, Debug)]
pub struct TextInputEvent {
    /// Character being sent.
    pub character: char,
}

impl TextInputEvent {
    /// Convert to an `Event`
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_TEXT_INPUT,
            a: self.character as i64,
            b: 0,
        }
    }

    /// Convert from an `Event`
    pub fn from_event(event: Event) -> TextInputEvent {
        TextInputEvent {
            character: char::from_u32(event.a as u32).unwrap_or('\0'),
        }
    }
}

/// A event related to the mouse (absolute position)
#[derive(Copy, Clone, Debug)]
pub struct MouseEvent {
    /// The x coordinate of the mouse
    pub x: i32,
    /// The y coordinate of the mouse
    pub y: i32,
}

impl MouseEvent {
    /// Convert to an `Event`
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_MOUSE,
            a: self.x as i64,
            b: self.y as i64,
        }
    }

    /// Convert an `Event` to a `MouseEvent`
    pub fn from_event(event: Event) -> MouseEvent {
        MouseEvent {
            x: event.a as i32,
            y: event.b as i32,
        }
    }
}

/// A event related to the mouse (relative position)
#[derive(Copy, Clone, Debug)]
pub struct MouseRelativeEvent {
    /// The x coordinate of the mouse
    pub dx: i32,
    /// The y coordinate of the mouse
    pub dy: i32,
}

impl MouseRelativeEvent {
    /// Convert to an `Event`
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_MOUSE_RELATIVE,
            a: self.dx as i64,
            b: self.dy as i64,
        }
    }

    /// Convert an `Event` to a `MouseRelativeEvent`
    pub fn from_event(event: Event) -> MouseRelativeEvent {
        MouseRelativeEvent {
            dx: event.a as i32,
            dy: event.b as i32,
        }
    }
}

/// A event for clicking the mouse
#[derive(Copy, Clone, Debug)]
pub struct ButtonEvent {
    /// Was the left button pressed?
    pub left: bool,
    /// Was the middle button pressed?
    pub middle: bool,
    /// Was the right button pressed?
    pub right: bool,
}

impl ButtonEvent {
    /// Convert to an `Event`
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_BUTTON,
            a: self.left as i64 | (self.middle as i64) << 1 | (self.right as i64) << 2,
            b: 0,
        }
    }

    /// Convert an `Event` to a `ButtonEvent`
    pub fn from_event(event: Event) -> ButtonEvent {
        ButtonEvent {
            left: event.a & 1 == 1,
            middle: event.a & 2 == 2,
            right: event.a & 4 == 4,
        }
    }
}

/// A event for scrolling the mouse
#[derive(Copy, Clone, Debug)]
pub struct ScrollEvent {
    /// The x distance of the scroll
    pub x: i32,
    /// The y distance of the scroll
    pub y: i32,
}

impl ScrollEvent {
    /// Convert to an `Event`
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_SCROLL,
            a: self.x as i64,
            b: self.y as i64,
        }
    }

    /// Convert an `Event` to a `ScrollEvent`
    pub fn from_event(event: Event) -> ScrollEvent {
        ScrollEvent {
            x: event.a as i32,
            y: event.b as i32,
        }
    }
}

#[derive(Copy, Clone, Debug)]
pub struct QuitEvent;

impl QuitEvent {
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_QUIT,
            a: 0,
            b: 0,
        }
    }

    pub fn from_event(_: Event) -> QuitEvent {
        QuitEvent
    }
}

/// A focus event
#[derive(Copy, Clone, Debug)]
pub struct FocusEvent {
    /// True if window has been focused, false if not
    pub focused: bool,
}

impl FocusEvent {
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_FOCUS,
            a: self.focused as i64,
            b: 0,
        }
    }

    pub fn from_event(event: Event) -> FocusEvent {
        FocusEvent {
            focused: event.a > 0,
        }
    }
}

/// A move event
#[derive(Copy, Clone, Debug)]
pub struct MoveEvent {
    /// New window X position
    pub x: i32,
    /// New window Y position
    pub y: i32,
}

impl MoveEvent {
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_MOVE,
            a: self.x as i64,
            b: self.y as i64,
        }
    }

    pub fn from_event(event: Event) -> MoveEvent {
        MoveEvent {
            x: event.a as i32,
            y: event.b as i32,
        }
    }
}

/// A resize event
#[derive(Copy, Clone, Debug)]
pub struct ResizeEvent {
    /// New window width
    pub width: u32,
    /// New window height
    pub height: u32,
}

impl ResizeEvent {
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_RESIZE,
            a: self.width as i64,
            b: self.height as i64,
        }
    }

    pub fn from_event(event: Event) -> ResizeEvent {
        ResizeEvent {
            width: event.a as u32,
            height: event.b as u32,
        }
    }
}

/// A screen report event
#[derive(Copy, Clone, Debug)]
pub struct ScreenEvent {
    /// New screen width
    pub width: u32,
    /// New screen height
    pub height: u32,
}

impl ScreenEvent {
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_SCREEN,
            a: self.width as i64,
            b: self.height as i64,
        }
    }

    pub fn from_event(event: Event) -> ScreenEvent {
        ScreenEvent {
            width: event.a as u32,
            height: event.b as u32,
        }
    }
}

/// Not used
#[derive(Copy, Clone, Debug)]
pub struct ClipboardUpdateEvent;

impl ClipboardUpdateEvent {
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_CLIPBOARD_UPDATE,
            a: 0,
            b: 0,
        }
    }

    pub fn from_event(_: Event) -> ClipboardUpdateEvent {
        ClipboardUpdateEvent
    }
}

/// A clipboard event
#[derive(Copy, Clone, Debug)]
pub struct ClipboardEvent {
    pub kind: ClipboardAction,
    pub size: usize,
}

impl ClipboardEvent {
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_CLIPBOARD,
            a: self.kind.to_u8() as i64,
            b: self.size as i64,
        }
    }

    pub fn from_event(event: Event) -> ClipboardEvent {
        ClipboardEvent {
            kind: ClipboardAction::from_u8(event.a as u8).unwrap_or(ClipboardAction::Copy),
            size: event.b as usize,
        }
    }
}

fn pack_dnd(kind: DragAction, size: usize, x: i32, y: i32) -> (i64, i64) {
    (
        (kind.to_u8() as i64) | (size as i64) << 8,
        (x as i64) | (y as i64) << 32,
    )
}

fn unpack_dnd(ev: Event) -> (DragAction, usize, i32, i32) {
    (
        DragAction::from_u8((ev.a & 0xFF) as u8).unwrap_or(DragAction::Copy),
        (ev.a >> 8) as usize,
        ev.b as i32,
        (ev.b >> 32) as i32,
    )
}

/// A drag enter file event.
/// App must call peek_drop_content to receive DragMoveEvent, DragLeaveEvent, DropEvent.
/// App can ignore DND by doing nothing or peek_drop_content + pop_drop_content before DropEvent.
#[derive(Copy, Clone, Debug)]
pub struct DragEnterEvent {
    pub kind: DragAction,
    pub size: usize,
}

impl DragEnterEvent {
    pub fn to_event(&self) -> Event {
        let (a, b) = pack_dnd(self.kind, self.size, 0, 0);
        Event {
            code: EVENT_DRAG_ENTER,
            a,
            b,
        }
    }

    pub fn from_event(event: Event) -> Self {
        let (kind, size, _, _) = unpack_dnd(event);
        Self { kind, size }
    }
}

/// A DND drag move event.
#[derive(Copy, Clone, Debug)]
pub struct DragMoveEvent {
    pub kind: DragAction,
    pub size: usize,
    pub x: i32,
    pub y: i32,
}

impl DragMoveEvent {
    pub fn to_event(&self) -> Event {
        let (a, b) = pack_dnd(self.kind, self.size, self.x, self.y);
        Event {
            code: EVENT_DRAG_MOVE,
            a,
            b,
        }
    }

    pub fn from_event(event: Event) -> Self {
        let (kind, size, x, y) = unpack_dnd(event);
        Self { kind, size, x, y }
    }
}

/// A DND drag move event.
#[derive(Copy, Clone, Debug)]
pub struct DragLeaveEvent {}

impl DragLeaveEvent {
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_DRAG_LEAVE,
            a: 0,
            b: 0,
        }
    }

    pub fn from_event(_event: Event) -> Self {
        Self {}
    }
}

/// A DND drop event.
#[derive(Copy, Clone, Debug)]
pub struct DropEvent {
    pub kind: DragAction,
    pub size: usize,
    pub x: i32,
    pub y: i32,
}

impl DropEvent {
    pub fn to_event(&self) -> Event {
        let (a, b) = pack_dnd(self.kind, self.size, self.x, self.y);
        Event {
            code: EVENT_DROP,
            a,
            b,
        }
    }

    pub fn from_event(event: Event) -> Self {
        let (kind, size, x, y) = unpack_dnd(event);
        Self { kind, size, x, y }
    }
}

/// A window hover event
#[derive(Copy, Clone, Debug)]
pub struct HoverEvent {
    /// True if window has been entered, false if exited
    pub entered: bool,
}

impl HoverEvent {
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_HOVER,
            a: self.entered as i64,
            b: 0,
        }
    }

    pub fn from_event(event: Event) -> HoverEvent {
        HoverEvent {
            entered: event.a > 0,
        }
    }
}

/// A screen scale event
#[derive(Copy, Clone, Debug)]
pub struct ScaleEvent {
    /// Integer scale factor (1 or more)
    pub scale: i32,
    // TODO: fractional scaling?
}

impl ScaleEvent {
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_SCALE,
            a: self.scale as i64,
            b: 0,
        }
    }

    pub fn from_event(event: Event) -> ScaleEvent {
        ScaleEvent {
            scale: event.a as i32,
        }
    }
}

// Taken from generic desktop HID definitions
pub const CONTROLLER_AXIS_X: u16 = 0x30;
pub const CONTROLLER_AXIS_Y: u16 = 0x31;
pub const CONTROLLER_AXIS_Z: u16 = 0x32;
pub const CONTROLLER_AXIS_RX: u16 = 0x33;
pub const CONTROLLER_AXIS_RY: u16 = 0x34;
pub const CONTROLLER_AXIS_RZ: u16 = 0x35;

/// A controller axis event
#[derive(Copy, Clone, Debug)]
pub struct ControllerAxisEvent {
    /// Controller ID
    pub id: u32,
    pub axis: u16,
    pub value: i32,
}

impl ControllerAxisEvent {
    /// Convert to an `Event`
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_CONTROLLER_AXIS,
            a: self.id as i64 | ((self.axis as i64) << 32),
            b: self.value as i64,
        }
    }

    /// Convert from an `Event`
    pub fn from_event(event: Event) -> Self {
        Self {
            id: event.a as u32,
            axis: (event.a >> 32) as u16,
            value: event.b as i32,
        }
    }
}

// Button layout from Xbox controller
pub const CONTROLLER_A: u16 = 1;
pub const CONTROLLER_B: u16 = 2;
pub const CONTROLLER_X: u16 = 3;
pub const CONTROLLER_Y: u16 = 4;
pub const CONTROLLER_LB: u16 = 5;
pub const CONTROLLER_RB: u16 = 6;
pub const CONTROLLER_BACK: u16 = 7;
pub const CONTROLLER_START: u16 = 8;
pub const CONTROLLER_LS: u16 = 9;
pub const CONTROLLER_RS: u16 = 10;
pub const CONTROLLER_GUIDE: u16 = 11;
// Taken from generic desktop page
pub const CONTROLLER_DPAD_UP: u16 = 0x90;
pub const CONTROLLER_DPAD_DOWN: u16 = 0x91;
pub const CONTROLLER_DPAD_RIGHT: u16 = 0x92;
pub const CONTROLLER_DPAD_LEFT: u16 = 0x93;

/// A controller button event
#[derive(Copy, Clone, Debug)]
pub struct ControllerButtonEvent {
    /// Controller ID
    pub id: u32,
    pub button: u16,
    pub pressed: bool,
}

impl ControllerButtonEvent {
    /// Convert to an `Event`
    pub fn to_event(&self) -> Event {
        Event {
            code: EVENT_CONTROLLER_BUTTON,
            a: self.id as i64 | ((self.button as i64) << 32),
            b: if self.pressed { 1 } else { 0 },
        }
    }

    /// Convert from an `Event`
    pub fn from_event(event: Event) -> Self {
        Self {
            id: event.a as u32,
            button: (event.a >> 32) as u16,
            pressed: event.b != 0,
        }
    }
}
