/*
* Node Web Bluetooth
* Copyright (c) 2017 Rob Moran
*
* The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

/**
 * Known services enum
 */
enum bluetoothServices {
    'alert_notification' = 0x1811,
    'automation_io' = 0x1815,
    'battery_service' = 0x180F,
    'blood_pressure' = 0x1810,
    'body_composition' = 0x181B,
    'bond_management' = 0x181E,
    'continuous_glucose_monitoring' = 0x181F,
    'current_time' = 0x1805,
    'cycling_power' = 0x1818,
    'cycling_speed_and_cadence' = 0x1816,
    'device_information' = 0x180A,
    'environmental_sensing' = 0x181A,
    'generic_access' = 0x1800,
    'generic_attribute' = 0x1801,
    'glucose' = 0x1808,
    'health_thermometer' = 0x1809,
    'heart_rate' = 0x180D,
    'human_interface_device' = 0x1812,
    'immediate_alert' = 0x1802,
    'indoor_positioning' = 0x1821,
    'internet_protocol_support' = 0x1820,
    'link_loss' = 0x1803,
    'location_and_navigation' = 0x1819,
    'next_dst_change' = 0x1807,
    'phone_alert_status' = 0x180E,
    'pulse_oximeter' = 0x1822,
    'reference_time_update' = 0x1806,
    'running_speed_and_cadence' = 0x1814,
    'scan_parameters' = 0x1813,
    'tx_power' = 0x1804,
    'user_data' = 0x181C,
    'weight_scale' = 0x181D
}

/**
 * Known characteristics enum
 */
enum bluetoothCharacteristics {
    'aerobic_heart_rate_lower_limit' = 0x2A7E,
    'aerobic_heart_rate_upper_limit' = 0x2A84,
    'aerobic_threshold' = 0x2A7F,
    'age' = 0x2A80,
    'aggregate' = 0x2A5A,
    'alert_category_id' = 0x2A43,
    'alert_category_id_bit_mask' = 0x2A42,
    'alert_level' = 0x2A06,
    'alert_notification_control_point' = 0x2A44,
    'alert_status' = 0x2A3F,
    'altitude' = 0x2AB3,
    'anaerobic_heart_rate_lower_limit' = 0x2A81,
    'anaerobic_heart_rate_upper_limit' = 0x2A82,
    'anaerobic_threshold' = 0x2A83,
    'analog' = 0x2A58,
    'apparent_wind_direction' = 0x2A73,
    'apparent_wind_speed' = 0x2A72,
    'gap.appearance' = 0x2A01,
    'barometric_pressure_trend' = 0x2AA3,
    'battery_level' = 0x2A19,
    'blood_pressure_feature' = 0x2A49,
    'blood_pressure_measurement' = 0x2A35,
    'body_composition_feature' = 0x2A9B,
    'body_composition_measurement' = 0x2A9C,
    'body_sensor_location' = 0x2A38,
    'bond_management_control_point' = 0x2AA4,
    'bond_management_feature' = 0x2AA5,
    'boot_keyboard_input_report' = 0x2A22,
    'boot_keyboard_output_report' = 0x2A32,
    'boot_mouse_input_report' = 0x2A33,
    'gap.central_address_resolution_support' = 0x2AA6,
    'cgm_feature' = 0x2AA8,
    'cgm_measurement' = 0x2AA7,
    'cgm_session_run_time' = 0x2AAB,
    'cgm_session_start_time' = 0x2AAA,
    'cgm_specific_ops_control_point' = 0x2AAC,
    'cgm_status' = 0x2AA9,
    'csc_feature' = 0x2A5C,
    'csc_measurement' = 0x2A5B,
    'current_time' = 0x2A2B,
    'cycling_power_control_point' = 0x2A66,
    'cycling_power_feature' = 0x2A65,
    'cycling_power_measurement' = 0x2A63,
    'cycling_power_vector' = 0x2A64,
    'database_change_increment' = 0x2A99,
    'date_of_birth' = 0x2A85,
    'date_of_threshold_assessment' = 0x2A86,
    'date_time' = 0x2A08,
    'day_date_time' = 0x2A0A,
    'day_of_week' = 0x2A09,
    'descriptor_value_changed' = 0x2A7D,
    'gap.device_name' = 0x2A00,
    'dew_point' = 0x2A7B,
    'digital' = 0x2A56,
    'dst_offset' = 0x2A0D,
    'elevation' = 0x2A6C,
    'email_address' = 0x2A87,
    'exact_time_256' = 0x2A0C,
    'fat_burn_heart_rate_lower_limit' = 0x2A88,
    'fat_burn_heart_rate_upper_limit' = 0x2A89,
    'firmware_revision_string' = 0x2A26,
    'first_name' = 0x2A8A,
    'five_zone_heart_rate_limits' = 0x2A8B,
    'floor_number' = 0x2AB2,
    'gender' = 0x2A8C,
    'glucose_feature' = 0x2A51,
    'glucose_measurement' = 0x2A18,
    'glucose_measurement_context' = 0x2A34,
    'gust_factor' = 0x2A74,
    'hardware_revision_string' = 0x2A27,
    'heart_rate_control_point' = 0x2A39,
    'heart_rate_max' = 0x2A8D,
    'heart_rate_measurement' = 0x2A37,
    'heat_index' = 0x2A7A,
    'height' = 0x2A8E,
    'hid_control_point' = 0x2A4C,
    'hid_information' = 0x2A4A,
    'hip_circumference' = 0x2A8F,
    'humidity' = 0x2A6F,
    'ieee_11073-20601_regulatory_certification_data_list' = 0x2A2A,
    'indoor_positioning_configuration' = 0x2AAD,
    'intermediate_blood_pressure' = 0x2A36,
    'intermediate_temperature' = 0x2A1E,
    'irradiance' = 0x2A77,
    'language' = 0x2AA2,
    'last_name' = 0x2A90,
    'latitude' = 0x2AAE,
    'ln_control_point' = 0x2A6B,
    'ln_feature' = 0x2A6A,
    'local_east_coordinate.xml' = 0x2AB1,
    'local_north_coordinate' = 0x2AB0,
    'local_time_information' = 0x2A0F,
    'location_and_speed' = 0x2A67,
    'location_name' = 0x2AB5,
    'longitude' = 0x2AAF,
    'magnetic_declination' = 0x2A2C,
    'magnetic_flux_density_2D' = 0x2AA0,
    'magnetic_flux_density_3D' = 0x2AA1,
    'manufacturer_name_string' = 0x2A29,
    'maximum_recommended_heart_rate' = 0x2A91,
    'measurement_interval' = 0x2A21,
    'model_number_string' = 0x2A24,
    'navigation' = 0x2A68,
    'new_alert' = 0x2A46,
    'gap.peripheral_preferred_connection_parameters' = 0x2A04,
    'gap.peripheral_privacy_flag' = 0x2A02,
    'plx_continuous_measurement' = 0x2A5F,
    'plx_features' = 0x2A60,
    'plx_spot_check_measurement' = 0x2A5E,
    'pnp_id' = 0x2A50,
    'pollen_concentration' = 0x2A75,
    'position_quality' = 0x2A69,
    'pressure' = 0x2A6D,
    'protocol_mode' = 0x2A4E,
    'rainfall' = 0x2A78,
    'gap.reconnection_address' = 0x2A03,
    'record_access_control_point' = 0x2A52,
    'reference_time_information' = 0x2A14,
    'report' = 0x2A4D,
    'report_map' = 0x2A4B,
    'resting_heart_rate' = 0x2A92,
    'ringer_control_point' = 0x2A40,
    'ringer_setting' = 0x2A41,
    'rsc_feature' = 0x2A54,
    'rsc_measurement' = 0x2A53,
    'sc_control_point' = 0x2A55,
    'scan_interval_window' = 0x2A4F,
    'scan_refresh' = 0x2A31,
    'sensor_location' = 0x2A5D,
    'serial_number_string' = 0x2A25,
    'gatt.service_changed' = 0x2A05,
    'software_revision_string' = 0x2A28,
    'sport_type_for_aerobic_and_anaerobic_thresholds' = 0x2A93,
    'supported_new_alert_category' = 0x2A47,
    'supported_unread_alert_category' = 0x2A48,
    'system_id' = 0x2A23,
    'temperature' = 0x2A6E,
    'temperature_measurement' = 0x2A1C,
    'temperature_type' = 0x2A1D,
    'three_zone_heart_rate_limits' = 0x2A94,
    'time_accuracy' = 0x2A12,
    'time_source' = 0x2A13,
    'time_update_control_point' = 0x2A16,
    'time_update_state' = 0x2A17,
    'time_with_dst' = 0x2A11,
    'time_zone' = 0x2A0E,
    'true_wind_direction' = 0x2A71,
    'true_wind_speed' = 0x2A70,
    'two_zone_heart_rate_limit' = 0x2A95,
    'tx_power_level' = 0x2A07,
    'uncertainty' = 0x2AB4,
    'unread_alert_status' = 0x2A45,
    'user_control_point' = 0x2A9F,
    'user_index' = 0x2A9A,
    'uv_index' = 0x2A76,
    'vo2_max' = 0x2A96,
    'waist_circumference' = 0x2A97,
    'weight' = 0x2A98,
    'weight_measurement' = 0x2A9D,
    'weight_scale_feature' = 0x2A9E,
    'wind_chill' = 0x2A79
}

/**
 * Known descriptors enum
 */
enum bluetoothDescriptors {
    'gatt.characteristic_extended_properties' = 0x2900,
    'gatt.characteristic_user_description' = 0x2901,
    'gatt.client_characteristic_configuration' = 0x2902,
    'gatt.server_characteristic_configuration' = 0x2903,
    'gatt.characteristic_presentation_format' = 0x2904,
    'gatt.characteristic_aggregate_format' = 0x2905,
    'valid_range' = 0x2906,
    'external_report_reference' = 0x2907,
    'report_reference' = 0x2908,
    'number_of_digitals' = 0x2909,
    'value_trigger_setting' = 0x290A,
    'es_configuration' = 0x290B,
    'es_measurement' = 0x290C,
    'es_trigger_setting' = 0x290D,
    'time_trigger_setting' = 0x290E
}

/**
 * Gets a canonical UUID from a partial UUID in string or hex format
 * @param alias The partial UUID
 * @returns canonical UUID
 */
const canonicalUUID = (alias: string | number): string => {
    if (typeof alias === 'number') alias = alias.toString(16);
    alias = alias.toLowerCase();
    if (alias.length <= 8) alias = ('00000000' + alias).slice(-8) + '-0000-1000-8000-00805f9b34fb';
    if (alias.length === 32) alias = alias.match(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/).splice(1).join('-');
    return alias;
};

/**
 * Gets a canonical service UUID from a known service name or partial UUID in string or hex format
 * @param name The known service name
 * @returns canonical UUID
 */
const getService = (name: string | number): string => {
    // Check for string as enums also allow a reverse lookup which will match any numbers passed in
    if (typeof name === 'string' && bluetoothServices[name]) {
        name = bluetoothServices[name];
    }

    return canonicalUUID(name);
};

/**
 * Gets a canonical characteristic UUID from a known characteristic name or partial UUID in string or hex format
 * @param name The known characteristic name
 * @returns canonical UUID
 */
const getCharacteristic = (name: string | number): string => {
    // Check for string as enums also allow a reverse lookup which will match any numbers passed in
    if (typeof name === 'string' && bluetoothCharacteristics[name]) {
        name = bluetoothCharacteristics[name];
    }

    return canonicalUUID(name);
};

/**
 * Gets a canonical descriptor UUID from a known descriptor name or partial UUID in string or hex format
 * @param name The known descriptor name
 * @returns canonical UUID
 */
const getDescriptor = (name: string | number): string => {
    // Check for string as enums also allow a reverse lookup which will match any numbers passed in
    if (typeof name === 'string' && bluetoothDescriptors[name]) {
        name = bluetoothDescriptors[name];
    }

    return canonicalUUID(name);
};

export const BluetoothUUID = {
    getService,
    getCharacteristic,
    getDescriptor,
    canonicalUUID
};
