//! USB HID report generation for CMDR Joystick
//!
//! Converts processed axis values and button states into a `JoystickReport`
//! that matches the HID descriptor defined in `usb_joystick_device.rs`.
//! Also contains support for virtual throttle mode and HAT directions.

use crate::axis::{
    remap, GimbalAxis, GIMBAL_AXIS_LEFT_X, GIMBAL_AXIS_LEFT_Y, GIMBAL_AXIS_RIGHT_X,
    GIMBAL_AXIS_RIGHT_Y,
};
use crate::buttons::{Button, TOTAL_BUTTONS};
use crate::hardware::{ADC_MAX, ADC_MIN, AXIS_CENTER};
use crate::mapping::{USB_HAT_LEFT, USB_HAT_UP};
use crate::usb_joystick_device::JoystickReport;

// ==================== USB REPORT GENERATION ====================

/// Convert 12‑bit unsigned values to 16‑bit signed for USB HID joystick reports.
///
/// Maps 12-bit ADC values (0-4095) to 16-bit signed USB HID values (-32768 to 32767).
/// This is specifically for USB joystick axis reporting.
///
/// # Arguments
/// * `val` - 12-bit ADC value (0-4095)
///
/// # Returns
/// 16-bit signed value suitable for USB HID joystick reports
///
/// # Panics
/// Panics if val > 0x0FFF (not a valid 12-bit value)
pub fn axis_12bit_to_i16(val: u16) -> i16 {
    assert!(val <= 0x0FFF); // Ensure it's 12-bit

    // Map 0..4095 → -32768..32767 using integer math
    // Formula: ((val * 65535) / 4095) - 32768
    let scaled = ((val as u32 * 65535) / 4095) as i32 - 32768;

    scaled as i16
}

/// Generate a complete USB HID joystick report from the current system state.
///
/// # Arguments
/// * `matrix_keys` - Array of button states from the button matrix
/// * `axis` - Array of gimbal axis values
/// * `virtual_ry` - Virtual RY axis value
/// * `virtual_rz` - Virtual RZ axis value
/// * `vt_enable` - Virtual throttle mode enable flag
///
/// # Returns
/// A complete `JoystickReport` ready to be sent via USB HID
pub fn get_joystick_report(
    matrix_keys: &mut [Button; TOTAL_BUTTONS],
    axis: &mut [GimbalAxis; 4],
    virtual_ry: u16,
    virtual_rz: u16,
    vt_enable: &bool,
) -> JoystickReport {
    // Convert axis values to 16-bit signed integers for USB HID
    let x: i16 = axis_12bit_to_i16(axis[GIMBAL_AXIS_LEFT_X].value);
    let y: i16 = axis_12bit_to_i16(axis[GIMBAL_AXIS_LEFT_Y].value);
    let mut z: i16 = axis_12bit_to_i16(axis[GIMBAL_AXIS_RIGHT_X].value);
    let rx: i16 = axis_12bit_to_i16(ADC_MAX - axis[GIMBAL_AXIS_RIGHT_Y].value);
    let ry: i16 = axis_12bit_to_i16(virtual_ry);
    let rz: i16 = axis_12bit_to_i16(virtual_rz);
    let mut slider: i16 = axis_12bit_to_i16(ADC_MIN);
    let mut hat: u8 = 8; // Hat center position

    // Virtual throttle mode:
    // - Disable Z axis
    // - Map right‑X to Slider with symmetric behavior around center
    if *vt_enable {
        if axis[GIMBAL_AXIS_RIGHT_X].value >= AXIS_CENTER {
            slider = axis_12bit_to_i16(remap(
                axis[GIMBAL_AXIS_RIGHT_X].value,
                AXIS_CENTER,
                ADC_MAX,
                ADC_MIN,
                ADC_MAX,
            ));
        } else {
            slider = axis_12bit_to_i16(
                ADC_MAX
                    - remap(
                        axis[GIMBAL_AXIS_RIGHT_X].value,
                        ADC_MIN,
                        AXIS_CENTER,
                        ADC_MIN,
                        ADC_MAX,
                    ),
            );
        }
        z = 0;
    }

    // Process buttons and build the USB button bitmask + HAT value
    let mut buttons: u32 = 0;
    for key in matrix_keys.iter_mut() {
        if key.enable_long_press {
            if key.active_usb_button != 0 {
                // Check if key is assigned as hat switch
                if key.active_usb_button >= USB_HAT_UP && key.active_usb_button <= USB_HAT_LEFT {
                    hat = (key.active_usb_button as u8 - USB_HAT_UP as u8) * 2;
                } else {
                    buttons |= 1 << (key.active_usb_button - 1);
                }
            }
        } else if key.pressed && key.usb_button != 0 {
            // Check if key is assigned as hat switch
            if key.usb_button >= USB_HAT_UP && key.usb_button <= USB_HAT_LEFT {
                hat = (key.usb_button as u8 - USB_HAT_UP as u8) * 2;
            } else {
                buttons |= 1 << (key.usb_button - 1);
            }
        }
    }

    // Reset per‑button USB change flags for the next iteration
    for key in matrix_keys.iter_mut() {
        key.usb_changed = false;
    }

    JoystickReport {
        x,
        y,
        z,
        rx,
        ry,
        rz,
        slider,
        hat,
        buttons,
    }
}

// ==================== TESTS ====================

#[cfg(all(test, feature = "std"))]
mod tests {
    use super::*;
    use crate::buttons::Button;

    #[test]
    fn test_axis_12bit_to_i16_boundaries() {
        // Test minimum value
        assert_eq!(axis_12bit_to_i16(0), -32768);

        // Test maximum value
        assert_eq!(axis_12bit_to_i16(4095), 32767);

        // Test center value
        let center_result = axis_12bit_to_i16(2047);
        assert!(center_result < 100 && center_result > -100); // Should be close to 0
    }

    #[test]
    fn test_axis_12bit_to_i16_conversion() {
        // Test known conversion
        let result = axis_12bit_to_i16(2047); // Half of 4095
        assert!(result < 100 && result > -100); // Should be close to 0

        let result = axis_12bit_to_i16(1023); // Quarter
        assert!(result < -16000);

        let result = axis_12bit_to_i16(3071); // Three quarters
        assert!(result > 16000);
    }

    #[test]
    fn test_remap_function() {
        // Test basic remapping
        assert_eq!(remap(500, 0, 1000, 0, 2000), 1000);
        assert_eq!(remap(0, 0, 1000, 0, 2000), 0);
        assert_eq!(remap(1000, 0, 1000, 0, 2000), 2000);

        // Test center remapping
        assert_eq!(remap(2048, 0, 4095, 0, 4095), 2048);

        // Test reverse remapping behavior: when out_min > out_max, constrain will
        // clamp results to the "valid" range based on the constraint logic
        assert_eq!(remap(0, 0, 1000, 2000, 0), 0); // Calculated 2000, constrained to 0
        assert_eq!(remap(1000, 0, 1000, 2000, 0), 2000); // Calculated 0, constrained to 2000
    }

    #[test]
    fn test_joystick_report_basic_axes() {
        // Remap helper scales values between integer ranges with clamping.
        let mut buttons = [Button::default(); TOTAL_BUTTONS];
        let mut axes = [GimbalAxis::new(); 4];

        // Set test axis values
        axes[GIMBAL_AXIS_LEFT_X].value = 2048; // Center
        axes[GIMBAL_AXIS_LEFT_Y].value = 1024; // Quarter
        axes[GIMBAL_AXIS_RIGHT_X].value = 3072; // Three quarter
        axes[GIMBAL_AXIS_RIGHT_Y].value = 4095; // Max

        let virtual_ry = 1000;
        let virtual_rz = 2000;
        let vt_enable = false;

        let report =
            get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);

        // Verify axis conversions
        assert_eq!(report.x, axis_12bit_to_i16(2048));
        assert_eq!(report.y, axis_12bit_to_i16(1024));
        assert_eq!(report.z, axis_12bit_to_i16(3072));
        assert_eq!(report.rx, axis_12bit_to_i16(0)); // ADC_MAX - 4095 = 0
        assert_eq!(report.ry, axis_12bit_to_i16(1000));
        assert_eq!(report.rz, axis_12bit_to_i16(2000));
        assert_eq!(report.slider, axis_12bit_to_i16(ADC_MIN));
        assert_eq!(report.hat, 8); // Center
        assert_eq!(report.buttons, 0);
    }

    #[test]
    fn test_virtual_throttle_mode() {
        // Slider combines throttle hold and virtual throttle for report serialization.
        let mut buttons = [Button::default(); TOTAL_BUTTONS];
        let mut axes = [GimbalAxis::new(); 4];

        // Set right X axis above center for virtual throttle test
        axes[GIMBAL_AXIS_RIGHT_X].value = 3072; // Above center

        let virtual_ry = 0;
        let virtual_rz = 0;
        let vt_enable = true;

        let report =
            get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);

        // In VT mode, z should be 0
        assert_eq!(report.z, 0);

        // Slider should be calculated from right X axis
        let expected_slider_value = remap(3072, AXIS_CENTER, ADC_MAX, ADC_MIN, ADC_MAX);
        assert_eq!(report.slider, axis_12bit_to_i16(expected_slider_value));
    }

    #[test]
    fn test_virtual_throttle_below_center() {
        // When VT mode is enabled below center, slider should invert along the left half of travel.
        let mut buttons = [Button::default(); TOTAL_BUTTONS];
        let mut axes = [GimbalAxis::new(); 4];

        // Set right X axis below center for virtual throttle test
        axes[GIMBAL_AXIS_RIGHT_X].value = 1024; // Below center

        let virtual_ry = 0;
        let virtual_rz = 0;
        let vt_enable = true;

        let report =
            get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);

        // In VT mode, z should be 0
        assert_eq!(report.z, 0);

        // Slider should be calculated from right X axis with inversion
        let remapped_value = remap(1024, ADC_MIN, AXIS_CENTER, ADC_MIN, ADC_MAX);
        let expected_slider_value = ADC_MAX - remapped_value;
        assert_eq!(report.slider, axis_12bit_to_i16(expected_slider_value));
    }

    #[test]
    fn test_button_mapping_regular_buttons() {
        // Regular buttons should set their HID bitmask without disturbing hat state.
        let mut buttons = [Button::default(); TOTAL_BUTTONS];
        let mut axes = [GimbalAxis::new(); 4];

        // Set some buttons pressed
        buttons[0].pressed = true;
        buttons[0].usb_button = 1; // USB button 1
        buttons[1].pressed = true;
        buttons[1].usb_button = 5; // USB button 5

        let virtual_ry = 0;
        let virtual_rz = 0;
        let vt_enable = false;

        let report =
            get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);

        // Check button bits are set correctly
        assert_eq!(report.buttons & (1 << 0), 1 << 0); // Button 1
        assert_eq!(report.buttons & (1 << 4), 1 << 4); // Button 5
        assert_eq!(report.hat, 8); // Center (no hat buttons)
    }

    #[test]
    fn test_hat_switch_mapping() {
        // A single hat direction should map to the appropriate HID hat value.
        let mut buttons = [Button::default(); TOTAL_BUTTONS];
        let mut axes = [GimbalAxis::new(); 4];

        // Set a HAT switch button
        buttons[0].pressed = true;
        buttons[0].usb_button = USB_HAT_UP;

        let virtual_ry = 0;
        let virtual_rz = 0;
        let vt_enable = false;

        let report =
            get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);

        // Check HAT direction is set correctly
        let expected_hat = (USB_HAT_UP as u8 - USB_HAT_UP as u8) * 2; // Should be 0 (up)
        assert_eq!(report.hat, expected_hat);
        assert_eq!(report.buttons, 0); // No regular buttons
    }

    #[test]
    fn test_long_press_button_handling() {
        // Buttons configured for long press should surface the alternate USB id.
        let mut buttons = [Button::default(); TOTAL_BUTTONS];
        let mut axes = [GimbalAxis::new(); 4];

        // Set a button with long press enabled
        buttons[0].enable_long_press = true;
        buttons[0].active_usb_button = 3; // USB button 3

        let virtual_ry = 0;
        let virtual_rz = 0;
        let vt_enable = false;

        let report =
            get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);

        // Check long press button is handled
        assert_eq!(report.buttons & (1 << 2), 1 << 2); // Button 3
    }

    #[test]
    fn test_usb_changed_flag_reset() {
        // Packaging a report should clear the usb_changed flags once consumed.
        let mut buttons = [Button::default(); TOTAL_BUTTONS];
        let mut axes = [GimbalAxis::new(); 4];

        // Set USB changed flags
        buttons[0].usb_changed = true;
        buttons[1].usb_changed = true;

        let virtual_ry = 0;
        let virtual_rz = 0;
        let vt_enable = false;

        get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);

        // Verify flags are reset
        assert!(!buttons[0].usb_changed);
        assert!(!buttons[1].usb_changed);
    }

    #[test]
    fn test_edge_case_hat_values() {
        // Additional hat directions should map to the correct encoded value.
        let mut buttons = [Button::default(); TOTAL_BUTTONS];
        let mut axes = [GimbalAxis::new(); 4];

        // Test different HAT switch values
        buttons[0].pressed = true;
        buttons[0].usb_button = USB_HAT_LEFT;

        let virtual_ry = 0;
        let virtual_rz = 0;
        let vt_enable = false;

        let report =
            get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);

        // Check HAT left direction
        let expected_hat = (USB_HAT_LEFT as u8 - USB_HAT_UP as u8) * 2;
        assert_eq!(report.hat, expected_hat);
    }

    #[test]
    fn test_multiple_buttons_and_hat() {
        // Report should accommodate simultaneous button presses and hat direction.
        let mut buttons = [Button::default(); TOTAL_BUTTONS];
        let mut axes = [GimbalAxis::new(); 4];

        // Mix regular buttons and HAT switch
        buttons[0].pressed = true;
        buttons[0].usb_button = 1; // Regular button
        buttons[1].pressed = true;
        buttons[1].usb_button = USB_HAT_UP; // HAT switch
        buttons[2].pressed = true;
        buttons[2].usb_button = 8; // Another regular button

        let virtual_ry = 0;
        let virtual_rz = 0;
        let vt_enable = false;

        let report =
            get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);

        // Check both regular buttons and HAT
        assert_eq!(report.buttons & (1 << 0), 1 << 0); // Button 1
        assert_eq!(report.buttons & (1 << 7), 1 << 7); // Button 8
        assert_eq!(report.hat, 0); // HAT up direction
    }
}
