//! Human input device system.
//!
//! Holds the guest-facing `X_INPUT_*` struct layouts (big-endian, matching the
//! Xbox 360 ABI exactly) and the host-neutral `GamepadState` snapshot used by
//! both the UI (writer) and the kernel's `XamInputGetState` handler (reader).

use xenia_memory::{GuestMemory, MemoryAccess};

/// Human input device system stub.
pub struct InputSystem {
    pub gamepad: GamepadState,
}

/// Host-side gamepad snapshot.
///
/// Kept as POD so it can be dropped into an `AtomicCell<GamepadState>` for
/// lock-free UI→CPU state transfer. Layout intentionally matches the in-memory
/// subset of `X_INPUT_GAMEPAD` (minus endianness), but the wire serializer in
/// [`write_input_state`] handles the big-endian conversion explicitly.
#[derive(Default, Clone, Copy, Debug)]
#[repr(C)]
pub struct GamepadState {
    pub buttons: u16,
    pub left_trigger: u8,
    pub right_trigger: u8,
    pub left_stick_x: i16,
    pub left_stick_y: i16,
    pub right_stick_x: i16,
    pub right_stick_y: i16,
}

/// Xbox 360 button flags. Values match `X_INPUT_GAMEPAD_BUTTON` in
/// `xenia-canary/src/xenia/hid/input.h`.
pub mod buttons {
    pub const DPAD_UP: u16 = 0x0001;
    pub const DPAD_DOWN: u16 = 0x0002;
    pub const DPAD_LEFT: u16 = 0x0004;
    pub const DPAD_RIGHT: u16 = 0x0008;
    pub const START: u16 = 0x0010;
    pub const BACK: u16 = 0x0020;
    pub const LEFT_THUMB: u16 = 0x0040;
    pub const RIGHT_THUMB: u16 = 0x0080;
    pub const LEFT_SHOULDER: u16 = 0x0100;
    pub const RIGHT_SHOULDER: u16 = 0x0200;
    pub const GUIDE: u16 = 0x0400;
    pub const A: u16 = 0x1000;
    pub const B: u16 = 0x2000;
    pub const X: u16 = 0x4000;
    pub const Y: u16 = 0x8000;
}

/// Xbox guest error codes returned by `XamInput*`.
pub mod errors {
    /// ERROR_SUCCESS
    pub const SUCCESS: u32 = 0;
    /// ERROR_DEVICE_NOT_CONNECTED
    pub const DEVICE_NOT_CONNECTED: u32 = 0x48F;
    /// ERROR_EMPTY (used by XamInputGetKeystroke when no event queued)
    pub const EMPTY: u32 = 0x10D2;
}

/// Sub-types that games query via `X_INPUT_CAPABILITIES::SubType`.
pub mod subtype {
    pub const GAMEPAD: u8 = 0x01;
}

impl InputSystem {
    pub fn new() -> Self {
        Self {
            gamepad: GamepadState::default(),
        }
    }
}

impl Default for InputSystem {
    fn default() -> Self {
        Self::new()
    }
}

/// Serialize a [`GamepadState`] into the 16-byte big-endian `X_INPUT_STATE`
/// struct at `out_ptr` in guest memory.
///
/// Layout (matches `xenia-canary/src/xenia/hid/input.h`):
///
/// | Offset | Size | Field          | Endianness |
/// |--------|------|----------------|------------|
/// | 0x00   | 4    | packet_number  | BE u32     |
/// | 0x04   | 2    | buttons        | BE u16     |
/// | 0x06   | 1    | left_trigger   | u8         |
/// | 0x07   | 1    | right_trigger  | u8         |
/// | 0x08   | 2    | thumb_lx       | BE i16     |
/// | 0x0A   | 2    | thumb_ly       | BE i16     |
/// | 0x0C   | 2    | thumb_rx       | BE i16     |
/// | 0x0E   | 2    | thumb_ry       | BE i16     |
pub fn write_input_state(
    mem: &GuestMemory,
    out_ptr: u32,
    packet_number: u32,
    state: &GamepadState,
) {
    if out_ptr == 0 {
        return;
    }
    mem.write_u32(out_ptr, packet_number);
    mem.write_u16(out_ptr + 0x04, state.buttons);
    mem.write_u8(out_ptr + 0x06, state.left_trigger);
    mem.write_u8(out_ptr + 0x07, state.right_trigger);
    mem.write_u16(out_ptr + 0x08, state.left_stick_x as u16);
    mem.write_u16(out_ptr + 0x0A, state.left_stick_y as u16);
    mem.write_u16(out_ptr + 0x0C, state.right_stick_x as u16);
    mem.write_u16(out_ptr + 0x0E, state.right_stick_y as u16);
}

/// Serialize an `X_INPUT_CAPABILITIES` block for a standard wired controller.
///
/// Layout (20 bytes, matches `xenia-canary`):
///
/// | Offset | Size | Field            |
/// |--------|------|------------------|
/// | 0x00   | 1    | Type (gamepad=1) |
/// | 0x01   | 1    | SubType          |
/// | 0x02   | 2    | Flags            |
/// | 0x04   | 12   | Gamepad state    |
/// | 0x10   | 2    | Vibration.left   |
/// | 0x12   | 2    | Vibration.right  |
pub fn write_input_capabilities(mem: &GuestMemory, out_ptr: u32) {
    if out_ptr == 0 {
        return;
    }
    // Type = DEVTYPE_GAMEPAD (1), SubType = STANDARD (1), Flags = 0
    mem.write_u8(out_ptr, 1);
    mem.write_u8(out_ptr + 0x01, subtype::GAMEPAD);
    mem.write_u16(out_ptr + 0x02, 0);
    // Gamepad capabilities: buttons = all standard bits, triggers+sticks = full range.
    // Games typically AND the gamepad mask to decide which controls exist; advertising
    // everything is the safe default.
    mem.write_u16(out_ptr + 0x04, 0xF3FF); // buttons: all except GUIDE
    mem.write_u8(out_ptr + 0x06, 0xFF); // left_trigger range
    mem.write_u8(out_ptr + 0x07, 0xFF); // right_trigger range
    mem.write_u16(out_ptr + 0x08, 0xFFFFu16); // lx
    mem.write_u16(out_ptr + 0x0A, 0xFFFFu16); // ly
    mem.write_u16(out_ptr + 0x0C, 0xFFFFu16); // rx
    mem.write_u16(out_ptr + 0x0E, 0xFFFFu16); // ry
    // Vibration: both motors full range
    mem.write_u16(out_ptr + 0x10, 0xFFFFu16);
    mem.write_u16(out_ptr + 0x12, 0xFFFFu16);
}

#[cfg(test)]
mod tests {
    use super::*;
    use xenia_memory::page_table::MemoryProtect;

    #[test]
    fn write_input_state_layout_is_big_endian() {
        let mut mem = GuestMemory::new().unwrap();
        let rw = MemoryProtect::READ | MemoryProtect::WRITE;
        mem.alloc(0x4000_0000, 0x1000, rw).unwrap();
        let state = GamepadState {
            buttons: buttons::A | buttons::START, // 0x1010
            left_trigger: 0x80,
            right_trigger: 0x40,
            left_stick_x: 0x0102,
            left_stick_y: -1,
            right_stick_x: 0x0304,
            right_stick_y: 0x0506,
        };
        write_input_state(&mut mem, 0x4000_0000, 0xDEAD_BEEF, &state);
        // packet_number BE
        assert_eq!(mem.read_u8(0x4000_0000), 0xDE);
        assert_eq!(mem.read_u8(0x4000_0001), 0xAD);
        assert_eq!(mem.read_u8(0x4000_0002), 0xBE);
        assert_eq!(mem.read_u8(0x4000_0003), 0xEF);
        // buttons BE (0x1010 → high byte 0x10 first)
        assert_eq!(mem.read_u8(0x4000_0004), 0x10);
        assert_eq!(mem.read_u8(0x4000_0005), 0x10);
        // triggers
        assert_eq!(mem.read_u8(0x4000_0006), 0x80);
        assert_eq!(mem.read_u8(0x4000_0007), 0x40);
        // thumb_lx BE
        assert_eq!(mem.read_u8(0x4000_0008), 0x01);
        assert_eq!(mem.read_u8(0x4000_0009), 0x02);
        // thumb_ly = -1 → 0xFFFF
        assert_eq!(mem.read_u8(0x4000_000A), 0xFF);
        assert_eq!(mem.read_u8(0x4000_000B), 0xFF);
        // thumb_rx BE
        assert_eq!(mem.read_u8(0x4000_000C), 0x03);
        assert_eq!(mem.read_u8(0x4000_000D), 0x04);
        assert_eq!(mem.read_u8(0x4000_000E), 0x05);
        assert_eq!(mem.read_u8(0x4000_000F), 0x06);
    }
}
