//! Bluetooth SIG assigned UUIDs for the Fitness Machine Service (FTMS).
//!
//! All of these are 16-bit assigned numbers expanded onto the Bluetooth Base
//! UUID `0000xxxx-0000-1000-8000-00805F9B34FB`.

use uuid::Uuid;

/// The tail of the Bluetooth Base UUID, `0000xxxx-0000-1000-8000-00805F9B34FB`.
const BASE_D2: u16 = 0x0000;
const BASE_D3: u16 = 0x1000;
const BASE_D4: [u8; 8] = [0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb];

/// Expand a 16-bit Bluetooth SIG assigned number onto the Bluetooth Base UUID.
pub const fn uuid16(assigned: u16) -> Uuid {
    Uuid::from_fields(assigned as u32, BASE_D2, BASE_D3, &BASE_D4)
}

/// Fitness Machine Service — `0x1826`. Trainers are identified by advertising
/// this (FR-1.2).
pub const FITNESS_MACHINE_SERVICE: Uuid = uuid16(0x1826);

/// Fitness Machine Feature — `0x2ACC`, read.
pub const FITNESS_MACHINE_FEATURE: Uuid = uuid16(0x2ACC);

/// Indoor Bike Data — `0x2AD2`, notify.
pub const INDOOR_BIKE_DATA: Uuid = uuid16(0x2AD2);

/// Training Status — `0x2AD3`, read/notify.
pub const TRAINING_STATUS: Uuid = uuid16(0x2AD3);

/// Supported Speed Range — `0x2AD4`, read.
pub const SUPPORTED_SPEED_RANGE: Uuid = uuid16(0x2AD4);

/// Supported Inclination Range — `0x2AD5`, read.
pub const SUPPORTED_INCLINATION_RANGE: Uuid = uuid16(0x2AD5);

/// Supported Resistance Level Range — `0x2AD6`, read.
pub const SUPPORTED_RESISTANCE_LEVEL_RANGE: Uuid = uuid16(0x2AD6);

/// Supported Heart Rate Range — `0x2AD7`, read.
pub const SUPPORTED_HEART_RATE_RANGE: Uuid = uuid16(0x2AD7);

/// Supported Power Range — `0x2AD8`, read.
pub const SUPPORTED_POWER_RANGE: Uuid = uuid16(0x2AD8);

/// Fitness Machine Control Point — `0x2AD9`, write + indicate.
pub const FITNESS_MACHINE_CONTROL_POINT: Uuid = uuid16(0x2AD9);

/// Fitness Machine Status — `0x2ADA`, notify.
pub const FITNESS_MACHINE_STATUS: Uuid = uuid16(0x2ADA);

/// Device Information Service — `0x180A`. Useful for the probe.
pub const DEVICE_INFORMATION_SERVICE: Uuid = uuid16(0x180A);

/// Battery Service — `0x180F`.
pub const BATTERY_SERVICE: Uuid = uuid16(0x180F);

/// Battery Level — `0x2A19`, read.
pub const BATTERY_LEVEL: Uuid = uuid16(0x2A19);

/// Heart Rate Service — `0x180D`. What a strap — or a watch in broadcast
/// mode — advertises.
pub const HEART_RATE_SERVICE: Uuid = uuid16(0x180D);

/// Heart Rate Measurement — `0x2A37`, notify.
pub const HEART_RATE_MEASUREMENT: Uuid = uuid16(0x2A37);

/// Human-readable name for a well-known UUID, for logging and the probe CLI.
/// Returns `None` for anything not recognised.
pub fn well_known_name(uuid: Uuid) -> Option<&'static str> {
    let name = match short_id(uuid)? {
        0x1826 => "Fitness Machine Service",
        0x2ACC => "Fitness Machine Feature",
        0x2AD2 => "Indoor Bike Data",
        0x2AD3 => "Training Status",
        0x2AD4 => "Supported Speed Range",
        0x2AD5 => "Supported Inclination Range",
        0x2AD6 => "Supported Resistance Level Range",
        0x2AD7 => "Supported Heart Rate Range",
        0x2AD8 => "Supported Power Range",
        0x2AD9 => "Fitness Machine Control Point",
        0x2ADA => "Fitness Machine Status",
        0x180A => "Device Information",
        0x180F => "Battery Service",
        0x1800 => "Generic Access",
        0x1801 => "Generic Attribute",
        0x180D => "Heart Rate",
        0x1818 => "Cycling Power",
        0x1816 => "Cycling Speed and Cadence",
        0x2A00 => "Device Name",
        0x2A19 => "Battery Level",
        0x2A37 => "Heart Rate Measurement",
        0x2A38 => "Body Sensor Location",
        0x2A24 => "Model Number String",
        0x2A25 => "Serial Number String",
        0x2A26 => "Firmware Revision String",
        0x2A27 => "Hardware Revision String",
        0x2A29 => "Manufacturer Name String",
        _ => return None,
    };
    Some(name)
}

/// If `uuid` sits on the Bluetooth Base UUID, return its 16-bit assigned number.
pub fn short_id(uuid: Uuid) -> Option<u16> {
    let (d1, d2, d3, d4) = uuid.as_fields();
    if d2 == BASE_D2 && d3 == BASE_D3 && *d4 == BASE_D4 && d1 <= u16::MAX as u32 {
        Some(d1 as u16)
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn ftms_service_uuid_is_correct() {
        assert_eq!(
            FITNESS_MACHINE_SERVICE.to_string(),
            "00001826-0000-1000-8000-00805f9b34fb"
        );
    }

    #[test]
    fn characteristic_uuids_are_correct() {
        assert_eq!(
            INDOOR_BIKE_DATA.to_string(),
            "00002ad2-0000-1000-8000-00805f9b34fb"
        );
        assert_eq!(
            FITNESS_MACHINE_CONTROL_POINT.to_string(),
            "00002ad9-0000-1000-8000-00805f9b34fb"
        );
        assert_eq!(
            FITNESS_MACHINE_FEATURE.to_string(),
            "00002acc-0000-1000-8000-00805f9b34fb"
        );
        assert_eq!(
            SUPPORTED_RESISTANCE_LEVEL_RANGE.to_string(),
            "00002ad6-0000-1000-8000-00805f9b34fb"
        );
    }

    #[test]
    fn short_id_round_trips() {
        assert_eq!(short_id(uuid16(0x2AD2)), Some(0x2AD2));
        assert_eq!(short_id(FITNESS_MACHINE_SERVICE), Some(0x1826));
        // A vendor UUID (the Zwift custom service) is not on the base UUID.
        let zwift = Uuid::parse_str("00000001-19CA-4651-86E5-FA29DCDD09D1").unwrap();
        assert_eq!(short_id(zwift), None);
    }

    #[test]
    fn well_known_names_resolve() {
        assert_eq!(
            well_known_name(INDOOR_BIKE_DATA),
            Some("Indoor Bike Data")
        );
        assert_eq!(well_known_name(uuid16(0x2A19)), Some("Battery Level"));
        assert_eq!(well_known_name(uuid16(0xFF01)), None);
    }
}
