//! PS/2 scancode set 2 → USB HID usage decoding, plus the keyboard state a driver
//! needs on top of it (pressed keys, modifier tracking, caps-lock toggle).
//!
//! Set 2 is what a keyboard sends when the 8042's legacy set-1 translation is off —
//! which is how ps2-bus.zig deliberately configures the controller. A key's **make**
//! code is one byte (two with an `E0` prefix for the "extended" keys added after the
//! original AT layout); its **break** code is the same code behind an `F0` prefix.
//! Pause alone is an eight-byte `E1` sequence with no break.
//!
//! The output vocabulary is USB HID keyboard-page usages (a=4, enter=40, ...), the
//! same numbering the input protocol's `Keycode` and the xkeyboard-config layout
//! tables use — so a decoded usage indexes a layout directly.
//!
//! Everything here is pure (no imports beyond `std`, no IO), so it is host-testable:
//! the tests at the bottom run under `zig build test`.

const std = @import("std");

// --- USB HID usages the state machine itself needs to recognize --------------

pub const usage_caps_lock: u8 = 0x39;
pub const usage_left_control: u8 = 0xE0;
pub const usage_left_shift: u8 = 0xE1;
pub const usage_left_alt: u8 = 0xE2;
pub const usage_right_control: u8 = 0xE4;
pub const usage_right_shift: u8 = 0xE5;
pub const usage_right_alt: u8 = 0xE6; // AltGr — selects XKB level 3

// --- scancode set 2 → HID usage tables ---------------------------------------

/// Single-byte (non-`E0`) make codes. Zero means "no key" — protocol bytes (ACK,
/// BAT results) and reserved codes land there and decode to nothing.
pub const set2_base: [256]u8 = blk: {
    var table = [_]u8{0} ** 256;
    // function row
    table[0x01] = 0x42; // F9
    table[0x03] = 0x3E; // F5
    table[0x04] = 0x3C; // F3
    table[0x05] = 0x3A; // F1
    table[0x06] = 0x3B; // F2
    table[0x07] = 0x45; // F12
    table[0x09] = 0x43; // F10
    table[0x0A] = 0x41; // F8
    table[0x0B] = 0x3F; // F6
    table[0x0C] = 0x3D; // F4
    table[0x78] = 0x44; // F11
    table[0x83] = 0x40; // F7
    // letters
    table[0x1C] = 0x04; // A
    table[0x32] = 0x05; // B
    table[0x21] = 0x06; // C
    table[0x23] = 0x07; // D
    table[0x24] = 0x08; // E
    table[0x2B] = 0x09; // F
    table[0x34] = 0x0A; // G
    table[0x33] = 0x0B; // H
    table[0x43] = 0x0C; // I
    table[0x3B] = 0x0D; // J
    table[0x42] = 0x0E; // K
    table[0x4B] = 0x0F; // L
    table[0x3A] = 0x10; // M
    table[0x31] = 0x11; // N
    table[0x44] = 0x12; // O
    table[0x4D] = 0x13; // P
    table[0x15] = 0x14; // Q
    table[0x2D] = 0x15; // R
    table[0x1B] = 0x16; // S
    table[0x2C] = 0x17; // T
    table[0x3C] = 0x18; // U
    table[0x2A] = 0x19; // V
    table[0x1D] = 0x1A; // W
    table[0x22] = 0x1B; // X
    table[0x35] = 0x1C; // Y
    table[0x1A] = 0x1D; // Z
    // digit row
    table[0x16] = 0x1E; // 1
    table[0x1E] = 0x1F; // 2
    table[0x26] = 0x20; // 3
    table[0x25] = 0x21; // 4
    table[0x2E] = 0x22; // 5
    table[0x36] = 0x23; // 6
    table[0x3D] = 0x24; // 7
    table[0x3E] = 0x25; // 8
    table[0x46] = 0x26; // 9
    table[0x45] = 0x27; // 0
    // control and whitespace
    table[0x5A] = 0x28; // Enter
    table[0x76] = 0x29; // Escape
    table[0x66] = 0x2A; // Backspace
    table[0x0D] = 0x2B; // Tab
    table[0x29] = 0x2C; // Space
    // punctuation
    table[0x4E] = 0x2D; // - _
    table[0x55] = 0x2E; // = +
    table[0x54] = 0x2F; // [ {
    table[0x5B] = 0x30; // ] }
    table[0x5D] = 0x31; // \ | (non-US hash on ISO boards, same position)
    table[0x4C] = 0x33; // ; :
    table[0x52] = 0x34; // ' "
    table[0x0E] = 0x35; // ` ~
    table[0x41] = 0x36; // , <
    table[0x49] = 0x37; // . >
    table[0x4A] = 0x38; // / ?
    table[0x61] = 0x64; // non-US backslash (the extra ISO key between shift and Z)
    // locks
    table[0x58] = usage_caps_lock;
    table[0x77] = 0x53; // Num Lock
    table[0x7E] = 0x47; // Scroll Lock
    // keypad
    table[0x7C] = 0x55; // keypad *
    table[0x7B] = 0x56; // keypad -
    table[0x79] = 0x57; // keypad +
    table[0x69] = 0x59; // keypad 1
    table[0x72] = 0x5A; // keypad 2
    table[0x7A] = 0x5B; // keypad 3
    table[0x6B] = 0x5C; // keypad 4
    table[0x73] = 0x5D; // keypad 5
    table[0x74] = 0x5E; // keypad 6
    table[0x6C] = 0x5F; // keypad 7
    table[0x75] = 0x60; // keypad 8
    table[0x7D] = 0x61; // keypad 9
    table[0x70] = 0x62; // keypad 0
    table[0x71] = 0x63; // keypad .
    // modifiers
    table[0x14] = usage_left_control;
    table[0x12] = usage_left_shift;
    table[0x11] = usage_left_alt;
    table[0x59] = usage_right_shift;
    break :blk table;
};

/// `E0`-prefixed make codes. `E0 12` is the "fake shift" the keyboard wraps around
/// Print Screen and navigation keys when a real shift is involved; it maps to zero
/// here, so it decodes to nothing and only the real key comes through.
pub const set2_extended: [256]u8 = blk: {
    var table = [_]u8{0} ** 256;
    table[0x11] = usage_right_alt;
    table[0x14] = usage_right_control;
    table[0x1F] = 0xE3; // left GUI
    table[0x27] = 0xE7; // right GUI
    table[0x2F] = 0x65; // application (menu)
    table[0x7C] = 0x46; // Print Screen (arrives as E0 12 E0 7C; the E0 12 decodes to nothing)
    table[0x4A] = 0x54; // keypad /
    table[0x5A] = 0x58; // keypad Enter
    table[0x70] = 0x49; // Insert
    table[0x6C] = 0x4A; // Home
    table[0x7D] = 0x4B; // Page Up
    table[0x71] = 0x4C; // Delete
    table[0x69] = 0x4D; // End
    table[0x7A] = 0x4E; // Page Down
    table[0x74] = 0x4F; // right arrow
    table[0x6B] = 0x50; // left arrow
    table[0x72] = 0x51; // down arrow
    table[0x75] = 0x52; // up arrow
    break :blk table;
};

// --- the byte-stream decoder --------------------------------------------------

/// One decoded key transition: which key (as a USB HID usage) and whether this is
/// a make (press or typematic repeat) or a break (release).
pub const DecodedKey = struct {
    usage: u8,
    make: bool,
};

/// Turns the raw set-2 byte stream into `DecodedKey`s. Feed it every byte the
/// keyboard sends; most bytes complete a key and return one, prefix bytes return
/// null and arm the state machine for the next byte.
pub const Decoder = struct {
    const State = enum {
        idle,
        extended, // saw E0
        break_prefix, // saw F0
        extended_break, // saw E0 F0
        pause_skip, // inside the 8-byte E1 Pause sequence
    };

    state: State = .idle,
    /// Bytes still to swallow in `pause_skip`.
    skip: u8 = 0,

    /// The whole Pause make sequence is `E1 14 77 E1 F0 14 F0 77` — seven bytes
    /// after the leading `E1`, and no break sequence ever follows.
    const pause_bytes_after_e1: u8 = 7;

    pub fn feed(self: *Decoder, byte: u8) ?DecodedKey {
        switch (self.state) {
            .idle => switch (byte) {
                0xE0 => self.state = .extended,
                0xF0 => self.state = .break_prefix,
                0xE1 => {
                    self.state = .pause_skip;
                    self.skip = pause_bytes_after_e1;
                },
                // Anything else is a make code — or a protocol byte (0xFA ACK,
                // 0xAA BAT-passed, 0xEE echo, ...), which the tables map to zero.
                else => return decoded(set2_base[byte], true),
            },
            .extended => switch (byte) {
                0xF0 => self.state = .extended_break,
                else => {
                    self.state = .idle;
                    return decoded(set2_extended[byte], true);
                },
            },
            .break_prefix => {
                self.state = .idle;
                return decoded(set2_base[byte], false);
            },
            .extended_break => {
                self.state = .idle;
                return decoded(set2_extended[byte], false);
            },
            .pause_skip => {
                self.skip -= 1;
                if (self.skip == 0) self.state = .idle;
            },
        }
        return null;
    }

    fn decoded(usage: u8, make: bool) ?DecodedKey {
        if (usage == 0) return null; // unmapped or a protocol byte
        return .{ .usage = usage, .make = make };
    }
};

// --- driver-side keyboard state -----------------------------------------------

/// What a key transition did, plus the modifier state to stamp on the resulting
/// events (snapshotted after the transition was applied).
pub const Transition = struct {
    pub const Action = enum {
        pressed, // physical make of a key that was up
        repeated, // typematic make of a key already down — no new key_down
        released, // physical break
    };
    action: Action,
    modifiers: ModifierSnapshot,
};

/// The modifier state at one instant, in both vocabularies a driver needs: the
/// input protocol's coarse bits (shift/control/alt) and the level-selection
/// inputs xkeyboard-config takes (shift, caps_lock, AltGr as level3).
pub const ModifierSnapshot = struct {
    shift: bool, // either shift held
    control: bool, // either control held
    alt: bool, // either alt held (including AltGr)
    right_alt: bool, // AltGr specifically — the XKB level-3 selector
    caps_lock: bool, // the toggle, not the key
};

/// Tracks which keys are physically down and the caps-lock toggle, and classifies
/// each decoded transition. Pure state — no IO — so repeat detection and modifier
/// snapshots are host-testable.
pub const KeyboardState = struct {
    /// One bit per HID usage: set while the key is physically down.
    pressed: [32]u8 = [_]u8{0} ** 32,
    caps_lock: bool = false,

    pub fn apply(self: *KeyboardState, key: DecodedKey) Transition {
        const already_down = self.isPressed(key.usage);
        if (key.make) {
            if (!already_down) {
                self.setPressed(key.usage, true);
                if (key.usage == usage_caps_lock) self.caps_lock = !self.caps_lock;
            }
            return .{
                .action = if (already_down) .repeated else .pressed,
                .modifiers = self.snapshot(),
            };
        }
        self.setPressed(key.usage, false);
        return .{ .action = .released, .modifiers = self.snapshot() };
    }

    pub fn isPressed(self: *const KeyboardState, usage: u8) bool {
        return self.pressed[usage / 8] & (@as(u8, 1) << @intCast(usage % 8)) != 0;
    }

    fn setPressed(self: *KeyboardState, usage: u8, down: bool) void {
        const bit = @as(u8, 1) << @intCast(usage % 8);
        if (down) {
            self.pressed[usage / 8] |= bit;
        } else {
            self.pressed[usage / 8] &= ~bit;
        }
    }

    fn snapshot(self: *const KeyboardState) ModifierSnapshot {
        const right_alt = self.isPressed(usage_right_alt);
        return .{
            .shift = self.isPressed(usage_left_shift) or self.isPressed(usage_right_shift),
            .control = self.isPressed(usage_left_control) or self.isPressed(usage_right_control),
            .alt = self.isPressed(usage_left_alt) or right_alt,
            .right_alt = right_alt,
            .caps_lock = self.caps_lock,
        };
    }
};

// --- tests (host-run via `zig build test`) ------------------------------------

const testing = std.testing;

/// Feed `bytes` and return the single DecodedKey they should produce (fails the
/// test if they produce none or more than one).
fn feedOne(decoder: *Decoder, bytes: []const u8) !DecodedKey {
    var result: ?DecodedKey = null;
    for (bytes) |byte| {
        if (decoder.feed(byte)) |key| {
            try testing.expect(result == null);
            result = key;
        }
    }
    return result orelse error.TestExpectedResult;
}

fn feedNone(decoder: *Decoder, bytes: []const u8) !void {
    for (bytes) |byte| try testing.expectEqual(@as(?DecodedKey, null), decoder.feed(byte));
}

test "base make and break: A" {
    var decoder = Decoder{};
    try testing.expectEqual(DecodedKey{ .usage = 0x04, .make = true }, try feedOne(&decoder, &.{0x1C}));
    try testing.expectEqual(DecodedKey{ .usage = 0x04, .make = false }, try feedOne(&decoder, &.{ 0xF0, 0x1C }));
}

test "extended make and break: right arrow" {
    var decoder = Decoder{};
    try testing.expectEqual(DecodedKey{ .usage = 0x4F, .make = true }, try feedOne(&decoder, &.{ 0xE0, 0x74 }));
    try testing.expectEqual(DecodedKey{ .usage = 0x4F, .make = false }, try feedOne(&decoder, &.{ 0xE0, 0xF0, 0x74 }));
}

test "pause: the E1 sequence is consumed silently" {
    var decoder = Decoder{};
    try feedNone(&decoder, &.{ 0xE1, 0x14, 0x77, 0xE1, 0xF0, 0x14, 0xF0, 0x77 });
    // The decoder is back in idle: an ordinary key still decodes.
    try testing.expectEqual(DecodedKey{ .usage = 0x04, .make = true }, try feedOne(&decoder, &.{0x1C}));
}

test "protocol bytes decode to nothing" {
    var decoder = Decoder{};
    try feedNone(&decoder, &.{ 0xFA, 0xAA, 0xEE }); // ACK, BAT-passed, echo
}

test "print screen: the fake-shift E0 12 decodes to nothing" {
    var decoder = Decoder{};
    try feedNone(&decoder, &.{ 0xE0, 0x12 });
    try testing.expectEqual(DecodedKey{ .usage = 0x46, .make = true }, try feedOne(&decoder, &.{ 0xE0, 0x7C }));
}

test "typematic repeat is classified, not re-pressed" {
    var state = KeyboardState{};
    const a = DecodedKey{ .usage = 0x04, .make = true };
    try testing.expectEqual(Transition.Action.pressed, state.apply(a).action);
    try testing.expectEqual(Transition.Action.repeated, state.apply(a).action);
    try testing.expectEqual(Transition.Action.repeated, state.apply(a).action);
    try testing.expectEqual(Transition.Action.released, state.apply(.{ .usage = 0x04, .make = false }).action);
    try testing.expectEqual(Transition.Action.pressed, state.apply(a).action);
}

test "shift held shows in the snapshot of other keys" {
    var state = KeyboardState{};
    _ = state.apply(.{ .usage = usage_left_shift, .make = true });
    const transition = state.apply(.{ .usage = 0x04, .make = true });
    try testing.expect(transition.modifiers.shift);
    try testing.expect(!transition.modifiers.control);
    _ = state.apply(.{ .usage = usage_left_shift, .make = false });
    _ = state.apply(.{ .usage = 0x04, .make = false });
    try testing.expect(!state.apply(.{ .usage = 0x04, .make = true }).modifiers.shift);
}

test "right alt reports both alt and the level-3 selector" {
    var state = KeyboardState{};
    _ = state.apply(.{ .usage = usage_right_alt, .make = true });
    const transition = state.apply(.{ .usage = 0x04, .make = true });
    try testing.expect(transition.modifiers.alt);
    try testing.expect(transition.modifiers.right_alt);
}

test "caps lock toggles on make, not on repeat or break" {
    var state = KeyboardState{};
    try testing.expect(state.apply(.{ .usage = usage_caps_lock, .make = true }).modifiers.caps_lock);
    try testing.expect(state.apply(.{ .usage = usage_caps_lock, .make = true }).modifiers.caps_lock); // repeat
    try testing.expect(state.apply(.{ .usage = usage_caps_lock, .make = false }).modifiers.caps_lock);
    try testing.expect(!state.apply(.{ .usage = usage_caps_lock, .make = true }).modifiers.caps_lock); // second press: off
}
