//! Project: CMtec CMDR joystick 24
//! Date:    2023-08-01
//! Author:  Christoffer Martinsson
//! Email:   cm@cmtec.se
//! License: Please refer to LICENSE in root directory

use core::default::Default;
use fugit::ExtU32;
// use packed_struct::prelude::*;
use usb_device::bus::UsbBus;
use usb_device::class_prelude::UsbBusAllocator;
use usbd_human_interface_device::usb_class::prelude::*;

macro_rules! unwrap {
    ($arg:expr) => {
        match $crate::usb_joystick_device::Try::into_result($arg) {
            ::core::result::Result::Ok(t) => t,
            ::core::result::Result::Err(e) => {
                ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
            }
        }
    };
    ($arg:expr, $($msg:expr),+ $(,)? ) => {
        match $crate::usb_joystick_device::Try::into_result($arg) {
            ::core::result::Result::Ok(t) => t,
            ::core::result::Result::Err(e) => {
                ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
            }
        }
    }
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;

pub trait Try {
    type Ok;
    type Error;
    fn into_result(self) -> Result<Self::Ok, Self::Error>;
    // Trait shim replicating `core::Try` for use in no_std contexts.
}

impl<T> Try for Option<T> {
    type Ok = T;
    type Error = NoneError;

    #[inline]
    fn into_result(self) -> Result<T, NoneError> {
        // Convert an optional value into a result with a unit error.
        self.ok_or(NoneError)
    }
}

impl<T, E> Try for Result<T, E> {
    type Ok = T;
    type Error = E;

    #[inline]
    fn into_result(self) -> Self {
        // `Result` already matches the desired signature; return it untouched.
        self
    }
}

// Based on example device from https://github.com/dlkj/usbd-human-interface-device/blob/main/src/device/joystick.rs
// Updated to 6x 12bit axis, 32x buttons and 4x hat switches
#[rustfmt::skip]
pub const JOYSTICK_DESCRIPTOR: &[u8] = &[
    0x05, 0x01,        // Usage Page (Generic Desktop)
    0x09, 0x04,        // Usage (Joystick)
    0xA1, 0x01,        // Collection (Application)

    // 7 signed 16-bit axes: X, Y, Z, Rx, Ry, Rz, Slider
    0x09, 0x30,        //   Usage (X)
    0x09, 0x31,        //   Usage (Y)
    0x09, 0x32,        //   Usage (Z)
    0x09, 0x33,        //   Usage (Rx)
    0x09, 0x34,        //   Usage (Ry)
    0x09, 0x35,        //   Usage (Rz)
    0x09, 0x36,        //   Usage (Slider)
    0x16, 0x00, 0x80,  //   Logical Minimum (-32768)
    0x26, 0xFF, 0x7F,  //   Logical Maximum (32767)
    0x75, 0x10,        //   Report Size (16)
    0x95, 0x07,        //   Report Count (7)
    0x81, 0x02,        //   Input (Data,Var,Abs)

    // 1 Hat Switch
    0x09, 0x39,        //   Usage (Hat switch)
    0x15, 0x00,        //   Logical Minimum (0)
    0x25, 0x07,        //   Logical Maximum (7)
    0x35, 0x00,        //   Physical Minimum (0)
    0x46, 0x3B, 0x01,  //   Physical Maximum (315)
    0x65, 0x14,        //   Unit (Eng Rot: Degrees)
    0x75, 0x04,        //   Report Size (4)
    0x95, 0x01,        //   Report Count (1)
    0x81, 0x42,        //   Input (Data,Var,Abs,Null)

    // Padding for 4 bits to align to byte
    0x75, 0x04,        //   Report Size (4)
    0x95, 0x01,        //   Report Count (1)
    0x81, 0x03,        //   Input (Const,Var,Abs) – padding

    // 32 Buttons (1-bit each)
    0x05, 0x09,        //   Usage Page (Button)
    0x19, 0x01,        //   Usage Minimum (1)
    0x29, 0x20,        //   Usage Maximum (32)
    0x15, 0x00,        //   Logical Minimum (0)
    0x25, 0x01,        //   Logical Maximum (1)
    0x75, 0x01,        //   Report Size (1)
    0x95, 0x20,        //   Report Count (32)
    0x81, 0x02,        //   Input (Data,Var,Abs)

    0xC0               // End Collection
];

#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
/// HID report payload matching `JOYSTICK_DESCRIPTOR`.
pub struct JoystickReport {
    pub x: i16,       // 16bit
    pub y: i16,       // 16bit
    pub z: i16,       // 16bit
    pub rx: i16,      // 16bit
    pub ry: i16,      // 16bit
    pub rz: i16,      // 16bit
    pub slider: i16,  // 16bit
    pub hat: u8,      // 8bit
    pub buttons: u32, // 32bit
}

pub struct Joystick<'a, B: UsbBus> {
    interface: Interface<'a, B, InBytes32, OutNone, ReportSingle>,
}

impl<B: UsbBus> Joystick<'_, B> {
    /// Serialize and send a joystick report matching the HID descriptor.
    pub fn write_report(&mut self, report: &JoystickReport) -> Result<(), UsbHidError> {
        let mut data: [u8; 19] = [0; 19];

        // Pack fields manually to match the descriptor layout (little‑endian i16).
        data[0] = report.x as u8;
        data[1] = (report.x >> 8) as u8;
        data[2] = report.y as u8;
        data[3] = (report.y >> 8) as u8;
        data[4] = report.z as u8;
        data[5] = (report.z >> 8) as u8;
        data[6] = report.rx as u8;
        data[7] = (report.rx >> 8) as u8;
        data[8] = report.ry as u8;
        data[9] = (report.ry >> 8) as u8;
        data[10] = report.rz as u8;
        data[11] = (report.rz >> 8) as u8;
        data[12] = report.slider as u8;
        data[13] = (report.slider >> 8) as u8;
        data[14] = report.hat;
        data[15] = report.buttons as u8;
        data[16] = (report.buttons >> 8) as u8;
        data[17] = (report.buttons >> 16) as u8;
        data[18] = (report.buttons >> 24) as u8;

        self.interface
            .write_report(&data)
            .map(|_| ())
            .map_err(UsbHidError::from)
    }
}

impl<'a, B: UsbBus> DeviceClass<'a> for Joystick<'a, B> {
    type I = Interface<'a, B, InBytes32, OutNone, ReportSingle>;

    fn interface(&mut self) -> &mut Self::I {
        // Expose the HID interface so the USB stack can enqueue reports.
        &mut self.interface
    }

    fn reset(&mut self) {
        // Nothing to reset for this simple HID device.
    }

    fn tick(&mut self) -> Result<(), UsbHidError> {
        // Flush pending HID data and poll the USB stack for new requests.
        Ok(())
    }
}

pub struct JoystickConfig<'a> {
    interface: InterfaceConfig<'a, InBytes32, OutNone, ReportSingle>,
}

impl Default for JoystickConfig<'_> {
    fn default() -> Self {
        // Construct the HID interface with the default joystick descriptor and endpoints.
        Self::new(
            unwrap!(unwrap!(InterfaceBuilder::new(JOYSTICK_DESCRIPTOR))
                .boot_device(InterfaceProtocol::None)
                .description("Joystick")
                .in_endpoint(1.millis()))
            .without_out_endpoint()
            .build(),
        )
    }
}

impl<'a> JoystickConfig<'a> {
    #[must_use]
    pub fn new(interface: InterfaceConfig<'a, InBytes32, OutNone, ReportSingle>) -> Self {
        Self { interface }
    }
}

impl<'a, B: UsbBus + 'a> UsbAllocatable<'a, B> for JoystickConfig<'a> {
    type Allocated = Joystick<'a, B>;

    fn allocate(self, usb_alloc: &'a UsbBusAllocator<B>) -> Self::Allocated {
        // Allocate the HID interface using the provided USB bus allocator.
        Self::Allocated {
            interface: Interface::new(usb_alloc, self.interface),
        }
    }
}
