//! Hardware configuration for CMDR Joystick (RP2040)
//!
//! Mirrors the structure introduced for the CMDR Keyboard firmware so that
//! bring-up, pin management, and timing constants follow the same layout.

use rp2040_hal::gpio::Pins;
use rp2040_hal::gpio::{
    self, DynPinId, FunctionI2C, FunctionPio0, FunctionSioInput, FunctionSioOutput, Pin, PullNone,
    PullUp,
};

/// External crystal frequency (Hz).
pub const XTAL_FREQ_HZ: u32 = 12_000_000;

/// USB Vendor ID/Product ID.
pub const USB_VID: u16 = 0x1209;
pub const USB_PID: u16 = 0x0002;

/// Button matrix geometry (rows/cols) and count.
pub const BUTTON_ROWS: usize = 5;
pub const BUTTON_COLS: usize = 5;
pub const NUMBER_OF_BUTTONS: usize = BUTTON_ROWS * BUTTON_COLS;

/// ADC characteristics.
pub const ADC_MIN: u16 = 0;
pub const ADC_MAX: u16 = 4095;
pub const AXIS_CENTER: u16 = (ADC_MIN + ADC_MAX) / 2;
pub const NBR_OF_GIMBAL_AXIS: usize = 4;

/// Debounce thresholds.
pub const MATRIX_DEBOUNCE_SCANS: u8 = 15;
pub const MIN_PRESS_SPACING_SCANS: u32 = 25; // ~5ms @ 200µs cadence

/// EEPROM storage length (calibration data + gimbal mode).
pub const EEPROM_DATA_LENGTH: usize = 25;

/// USB string descriptors.
pub mod usb {
    pub const MANUFACTURER: &str = "CMtec";
    pub const PRODUCT: &str = "CMDR Joystick";
    pub const SERIAL_NUMBER: &str = "0001";
}

/// Timing cadences.
pub mod timers {
    pub const STATUS_LED_INTERVAL_MS: u32 = 40;
    pub const SCAN_INTERVAL_US: u32 = 200;
    pub const USB_UPDATE_INTERVAL_MS: u32 = 1;
    pub const USB_ACTIVITY_TIMEOUT_MS: u32 = 5_000;
}

/// I2C helpers.
pub mod i2c {
    use eeprom24x::SlaveAddr;
    use fugit::{Rate, RateExtU32};

    pub const FREQUENCY_HZ: u32 = 400_000;
    pub const SYSTEM_CLOCK_HZ: u32 = 125_000_000;

    pub fn frequency() -> Rate<u32, 1, 1> {
        FREQUENCY_HZ.Hz()
    }

    pub fn system_clock() -> Rate<u32, 1, 1> {
        SYSTEM_CLOCK_HZ.Hz()
    }

    pub const EEPROM_ADDRESS: SlaveAddr = SlaveAddr::Alternative(false, false, false);
}

/// Raw GPIO constants retained for documentation/reference.
pub mod pins {
    pub const LEFT_EXTRA_BUTTON: u8 = 1;
    pub const RIGHT_EXTRA_BUTTON: u8 = 0;
    pub const BUTTON_ROW_0: u8 = 6;
    pub const BUTTON_ROW_1: u8 = 8;
    pub const BUTTON_ROW_2: u8 = 4;
    pub const BUTTON_ROW_3: u8 = 7;
    pub const BUTTON_ROW_4: u8 = 5;
    pub const BUTTON_COL_0: u8 = 9;
    pub const BUTTON_COL_1: u8 = 10;
    pub const BUTTON_COL_2: u8 = 11;
    pub const BUTTON_COL_3: u8 = 12;
    pub const BUTTON_COL_4: u8 = 13;
    pub const ADC_LEFT_X: u8 = 29;
    pub const ADC_LEFT_Y: u8 = 28;
    pub const ADC_RIGHT_X: u8 = 27;
    pub const ADC_RIGHT_Y: u8 = 26;
    pub const STATUS_LED: u8 = 16;
    pub const I2C_SDA: u8 = 14;
    pub const I2C_SCL: u8 = 15;
}

/// Matrix row pins (dynamic to simplify scanning code).
pub type MatrixRowPin = Pin<DynPinId, FunctionSioInput, PullUp>;
/// Matrix column pins (dynamic push-pull outputs).
pub type MatrixColPin = Pin<DynPinId, FunctionSioOutput, PullNone>;
/// Extra buttons (pull-up inputs).
pub type ExtraButtonPin = Pin<DynPinId, FunctionSioInput, PullUp>;
/// Status LED pin configured for PIO output.
pub type StatusLedPin = Pin<gpio::bank0::Gpio16, FunctionPio0, PullNone>;
/// I2C SDA/SCL pins after reconfiguration.
pub type I2cSdaPin = Pin<gpio::bank0::Gpio14, FunctionI2C, PullUp>;
pub type I2cSclPin = Pin<gpio::bank0::Gpio15, FunctionI2C, PullUp>;

/// Analog axis input pins (remain as SIO inputs until wrapped by `AdcPin`).
pub struct AxisInputs {
    pub left_x: Pin<gpio::bank0::Gpio29, FunctionSioInput, PullNone>,
    pub left_y: Pin<gpio::bank0::Gpio28, FunctionSioInput, PullNone>,
    pub right_x: Pin<gpio::bank0::Gpio27, FunctionSioInput, PullNone>,
    pub right_y: Pin<gpio::bank0::Gpio26, FunctionSioInput, PullNone>,
}

/// Bundle returned by `split_board_pins`.
pub struct BoardPins {
    pub matrix_rows: [MatrixRowPin; BUTTON_ROWS],
    pub matrix_cols: [MatrixColPin; BUTTON_COLS],
    pub left_extra_button: ExtraButtonPin,
    pub right_extra_button: ExtraButtonPin,
    pub axis_inputs: AxisInputs,
    pub status_led: StatusLedPin,
    pub i2c_sda: I2cSdaPin,
    pub i2c_scl: I2cSclPin,
}

impl BoardPins {
    pub fn new(pins: Pins) -> Self {
        let row0 = pins.gpio6.into_pull_up_input().into_dyn_pin();
        let row1 = pins.gpio8.into_pull_up_input().into_dyn_pin();
        let row2 = pins.gpio4.into_pull_up_input().into_dyn_pin();
        let row3 = pins.gpio7.into_pull_up_input().into_dyn_pin();
        let row4 = pins.gpio5.into_pull_up_input().into_dyn_pin();

        let col0 = pins
            .gpio9
            .into_push_pull_output()
            .into_pull_type::<PullNone>()
            .into_dyn_pin();
        let col1 = pins
            .gpio10
            .into_push_pull_output()
            .into_pull_type::<PullNone>()
            .into_dyn_pin();
        let col2 = pins
            .gpio11
            .into_push_pull_output()
            .into_pull_type::<PullNone>()
            .into_dyn_pin();
        let col3 = pins
            .gpio12
            .into_push_pull_output()
            .into_pull_type::<PullNone>()
            .into_dyn_pin();
        let col4 = pins
            .gpio13
            .into_push_pull_output()
            .into_pull_type::<PullNone>()
            .into_dyn_pin();

        let left_extra = pins.gpio1.into_pull_up_input().into_dyn_pin();
        let right_extra = pins.gpio0.into_pull_up_input().into_dyn_pin();

        let axis_inputs = AxisInputs {
            left_x: pins.gpio29.into_floating_input(),
            left_y: pins.gpio28.into_floating_input(),
            right_x: pins.gpio27.into_floating_input(),
            right_y: pins.gpio26.into_floating_input(),
        };

        let status_led = pins
            .gpio16
            .into_function::<FunctionPio0>()
            .into_pull_type::<PullNone>();

        let i2c_sda = pins
            .gpio14
            .into_function::<FunctionI2C>()
            .into_pull_type::<PullUp>();
        let i2c_scl = pins
            .gpio15
            .into_function::<FunctionI2C>()
            .into_pull_type::<PullUp>();

        Self {
            matrix_rows: [row0, row1, row2, row3, row4],
            matrix_cols: [col0, col1, col2, col3, col4],
            left_extra_button: left_extra,
            right_extra_button: right_extra,
            axis_inputs,
            status_led,
            i2c_sda,
            i2c_scl,
        }
    }
}
