#![allow(dead_code)]

use crate::{button::Modifier, keys::Key};
use bytemuck::{cast_ref, Pod, Zeroable};
use core::mem::size_of;

/// KeyboardReport describes a report and its companion descriptor that can be
/// used to send keyboard button presses to a host and receive the status of the
/// keyboard LEDs.
///
/// Unlike usbd_hids KeyboardReport, this one supports N-key rollover.
#[derive(Clone, Copy, PartialEq, Eq, Zeroable, Pod)]
#[cfg(feature = "n-key-rollover")]
#[repr(C, packed)]
pub struct KeyboardReport {
    pub modifier: u8,

    /// Bitmap representing all keycodes from 0 to 215
    pub keycodes: [u8; 27],
}

#[cfg(not(feature = "n-key-rollover"))]
pub use ::usbd_hid::descriptor::KeyboardReport;

#[cfg(feature = "n-key-rollover")]
pub const EMPTY_KEYBOARD_REPORT: KeyboardReport = KeyboardReport {
    modifier: 0,
    keycodes: [0; 27],
};

#[cfg(not(feature = "n-key-rollover"))]
pub const EMPTY_KEYBOARD_REPORT: KeyboardReport = KeyboardReport {
    modifier: 0,
    leds: 0,
    reserved: 0,
    keycodes: [0; 6],
};

/// Get the byte index, and the mask for that byte, in the keycode bitmap.
fn key_to_byte_mask(key: Key) -> (usize, u8) {
    let keycode = u8::from(key);
    let byte = keycode >> 3;
    let bit = keycode & 0b111;
    let mask = 1 << bit;

    (usize::from(byte), mask)
}

#[cfg(feature = "n-key-rollover")]
impl KeyboardReport {
    #[inline(always)]
    pub fn set_key(&mut self, key: Key, pressed: bool) {
        let (byte_i, mask) = key_to_byte_mask(key);

        if let Some(k) = self.keycodes.get_mut(byte_i) {
            if pressed {
                *k |= mask;
            } else {
                *k &= !mask;
            }
        } else {
            log::warn!("Tried to set out-of-range keycode: 0x{:x}", u8::from(key));
        }
    }

    #[inline(always)]
    pub fn press_key(&mut self, key: Key) {
        self.set_key(key, true)
    }

    #[inline(always)]
    pub fn release_key(&mut self, key: Key) {
        self.set_key(key, false)
    }

    #[inline(always)]
    pub fn key_pressed(&mut self, key: Key) -> bool {
        let (byte_i, mask) = key_to_byte_mask(key);
        if let Some(k) = self.keycodes.get_mut(byte_i) {
            (*k & mask) != 0
        } else {
            log::warn!("Tried to get out-of-range keycode: 0x{:x}", u8::from(key));
            false
        }
    }

    #[inline(always)]
    pub fn set_modifier(&mut self, modifier: Modifier, pressed: bool) {
        if pressed {
            self.modifier |= u8::from(modifier);
        } else {
            self.modifier &= !u8::from(modifier);
        }
    }

    #[inline(always)]
    pub fn press_modifier(&mut self, modifier: Modifier) {
        self.set_modifier(modifier, true)
    }

    #[inline(always)]
    pub fn release_modifier(&mut self, modifier: Modifier) {
        self.set_modifier(modifier, false)
    }

    #[inline(always)]
    pub fn modifier_pressed(&mut self, modifier: Modifier) -> bool {
        (self.modifier & u8::from(modifier)) != 0
    }

    #[inline(always)]
    pub fn as_bytes(&self) -> &[u8; size_of::<KeyboardReport>()] {
        cast_ref(self)
    }
}

// bitmasks for the `modifier` field

#[cfg(feature = "n-key-rollover")]
impl usbd_hid::descriptor::SerializedDescriptor for KeyboardReport {
    fn desc() -> &'static [u8] {
        // Manually define the descriptor since I can't figure out how to get
        // gen_hid_descriptor to generate the correct one.
        &[
            0x05, 0x01, // usage page 1 (generic desktop)
            0x09, 0x06, // usage (keyboard)
            0xa1, 0x01, // collection (application)
            //
            0x05, 0x07, // usage page 7 (keyboard/keypad)
            0x19, 0xe0, // local usage minimum
            0x29, 0xe7, // local usage maximum
            0x15, 0x00, // local minimum
            0x25, 0x01, // local maximum
            0x75, 0x01, // report size (1 bit)
            0x95, 0x08, // report count (8)
            0x81, 0x02, // input (variable)
            //
            0x19, 0x00, // local usage minimum
            //0x29, 0x67, // local usage maximum (0x67)
            //0x95, 0x68, // report count (0x68)
            0x29, 215, // local usage maximum (215)
            0x95, 216, // report count (216)
            0x81, 0x02, // input (variable)
            //
            0x05, 0x08, // usage page 8 (led page)
            0x19, 0x01, // local usage minimum
            0x29, 0x05, // local usage maximum
            0x15, 0x00, // logical min
            0x25, 0x01, // logical max
            0x75, 0x01, // report size
            0x95, 0x05, // report count
            0x91, 0x02, // output (variable)
            //
            0x75, 0x03, // report size
            0x95, 0x01, // report count
            0x91, 0x01, // output (constant)
            0xc0, // end collection
        ]
    }
}
