//! Uses rusb (upto date libusb fork) to get system USB information - same lib as lsusb. Requires 'libusb' feature. Uses [`crate::profiler::types`] types to hold data so that it is cross-compatible with macOS system_profiler command.
use super::*;
use crate::error::{Error, ErrorKind};
use crate::lsusb::names;
use crate::types::NumericalUnit;
use rusb as libusb;
use usb_ids::{self, FromId};

#[derive(Debug)]
pub(crate) struct LibUsbProfiler;

pub(crate) struct UsbDevice<T: libusb::UsbContext> {
    handle: libusb::DeviceHandle<T>,
    language: libusb::Language,
    vidpid: (u16, u16),
    location: DeviceLocation,
    timeout: std::time::Duration,
}

impl<T: libusb::UsbContext> std::fmt::Debug for UsbDevice<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "UsbDevice {{ vidpid: {:#04x}:{:#04x}, location: {:?} }}",
            self.vidpid.0, self.vidpid.1, self.location
        )
    }
}

/// Set log level for rusb
pub fn set_log_level(debug: u8) {
    let log_level = match debug {
        0 => rusb::LogLevel::None,
        1 => rusb::LogLevel::Warning,
        2 => rusb::LogLevel::Info,
        _ => rusb::LogLevel::Debug,
    };

    rusb::set_log_level(log_level);
}

impl ControlRequest {
    fn get_request_type_in(&self) -> u8 {
        libusb::request_type(
            libusb::Direction::In,
            self.control_type.into(),
            self.recipient.into(),
        )
    }
}

impl From<ControlType> for libusb::RequestType {
    fn from(ct: ControlType) -> Self {
        match ct {
            ControlType::Standard => libusb::RequestType::Standard,
            ControlType::Class => libusb::RequestType::Class,
            ControlType::Vendor => libusb::RequestType::Vendor,
        }
    }
}

impl From<Recipient> for libusb::Recipient {
    fn from(r: Recipient) -> Self {
        match r {
            Recipient::Device => libusb::Recipient::Device,
            Recipient::Interface => libusb::Recipient::Interface,
            Recipient::Endpoint => libusb::Recipient::Endpoint,
            Recipient::Other => libusb::Recipient::Other,
        }
    }
}

impl From<libusb::Error> for Error {
    fn from(error: libusb::Error) -> Self {
        Error {
            kind: ErrorKind::LibUSB,
            message: format!(
                "Failed to gather system USB data from libusb: Error({})",
                &error.to_string()
            ),
        }
    }
}

/// Convert to our crate speed
impl From<libusb::Speed> for usb::Speed {
    fn from(libusb: libusb::Speed) -> Self {
        match libusb {
            libusb::Speed::SuperPlus => usb::Speed::SuperSpeedPlus,
            libusb::Speed::Super => usb::Speed::SuperSpeed,
            libusb::Speed::High => usb::Speed::HighSpeed,
            libusb::Speed::Full => usb::Speed::FullSpeed,
            libusb::Speed::Low => usb::Speed::LowSpeed,
            _ => usb::Speed::Unknown,
        }
    }
}

impl From<libusb::Direction> for usb::Direction {
    fn from(libusb: libusb::Direction) -> Self {
        match libusb {
            libusb::Direction::Out => usb::Direction::Out,
            libusb::Direction::In => usb::Direction::In,
        }
    }
}

impl From<libusb::TransferType> for usb::TransferType {
    fn from(libusb: libusb::TransferType) -> Self {
        match libusb {
            libusb::TransferType::Control => usb::TransferType::Control,
            libusb::TransferType::Isochronous => usb::TransferType::Isochronous,
            libusb::TransferType::Bulk => usb::TransferType::Bulk,
            libusb::TransferType::Interrupt => usb::TransferType::Interrupt,
        }
    }
}

impl From<libusb::UsageType> for usb::UsageType {
    fn from(libusb: libusb::UsageType) -> Self {
        match libusb {
            libusb::UsageType::Data => usb::UsageType::Data,
            libusb::UsageType::Feedback => usb::UsageType::Feedback,
            libusb::UsageType::FeedbackData => usb::UsageType::FeedbackData,
            libusb::UsageType::Reserved => usb::UsageType::Reserved,
        }
    }
}

impl From<libusb::SyncType> for usb::SyncType {
    fn from(libusb: libusb::SyncType) -> Self {
        match libusb {
            libusb::SyncType::NoSync => usb::SyncType::None,
            libusb::SyncType::Asynchronous => usb::SyncType::Asynchronous,
            libusb::SyncType::Adaptive => usb::SyncType::Adaptive,
            libusb::SyncType::Synchronous => usb::SyncType::Synchronous,
        }
    }
}

impl From<libusb::Version> for usb::Version {
    fn from(libusb: libusb::Version) -> Self {
        usb::Version(libusb.major(), libusb.minor(), libusb.sub_minor())
    }
}

/// Attempt to retrieve the current bConfigurationValue and iConfiguration for a device
/// This will only return the current configuration, not all possible configurations
/// If there are any failures in retrieving the data, None is returned
#[allow(unused_variables)]
fn get_sysfs_configuration_string(sysfs_name: &str) -> Option<(u8, String)> {
    #[cfg(target_os = "linux")]
    // Determine bConfigurationValue value on linux
    match get_sysfs_string(sysfs_name, "bConfigurationValue") {
        Some(s) => match s.parse::<u8>() {
            Ok(v) => {
                // Determine iConfiguration
                get_sysfs_string(sysfs_name, "configuration").map(|s| (v, s))
            }
            Err(_) => None,
        },
        None => None,
    }

    #[cfg(not(target_os = "linux"))]
    None
}

impl<T: libusb::UsbContext> UsbOperations for UsbDevice<T> {
    /// Get string descriptor from device
    ///
    /// Returns None if string_index is 0 - reserved for language codes
    fn get_descriptor_string(&self, string_index: u8) -> Option<String> {
        if string_index == 0 {
            return None;
        }
        self.handle
            .read_string_descriptor(self.language, string_index, self.timeout)
            .map(|s| s.trim().trim_end_matches('\0').to_string())
            .ok()
    }

    /// Get control message from device, ensuring message of [`ControlRequest`] length is read
    fn get_control_msg(&self, control_request: ControlRequest) -> Result<Vec<u8>> {
        let mut buf = vec![0; control_request.length];
        if control_request.claim_interface {
            self.handle.claim_interface(control_request.index as u8)?;
        }

        let n = self
            .handle
            .read_control(
                control_request.get_request_type_in(),
                control_request.request,
                control_request.value,
                control_request.index,
                &mut buf,
                self.timeout,
            )
            .map_err(|e| Error {
                kind: ErrorKind::LibUSB,
                message: format!("Failed to get control message: {e}"),
            })?;
        if n < control_request.length {
            log::warn!(
                "Failed to read full control message for {}: {} < {}",
                control_request.request,
                n,
                control_request.length
            );
            Err(Error {
                kind: ErrorKind::LibUSB,
                message: "Control message too short".to_string(),
            })
        } else {
            Ok(buf)
        }
    }
}

impl LibUsbProfiler {
    fn build_endpoints<T: libusb::UsbContext>(
        &self,
        handle: Option<&UsbDevice<T>>,
        interface_path: &usb::DevicePath,
        interface_desc: &libusb::InterfaceDescriptor,
    ) -> Vec<usb::Endpoint> {
        let mut ret: Vec<usb::Endpoint> = Vec::new();

        for endpoint_desc in interface_desc.endpoint_descriptors() {
            let extra_desc = handle.and_then(|h| {
                endpoint_desc.extra().and_then(|extra| {
                    self.build_endpoint_descriptor_extra(
                        h,
                        (
                            interface_desc.class_code(),
                            interface_desc.sub_class_code(),
                            interface_desc.protocol_code(),
                        ),
                        interface_desc.interface_number(),
                        extra.to_vec(),
                    )
                    .ok()
                    .flatten()
                })
            });
            let endpoint_path = usb::EndpointPath::new_with_device_path(
                interface_path.to_owned(),
                endpoint_desc.number(),
            );

            ret.push(usb::Endpoint {
                address: usb::EndpointAddress {
                    address: endpoint_desc.address(),
                    number: endpoint_desc.number(),
                    direction: usb::Direction::from(endpoint_desc.direction()),
                },
                transfer_type: usb::TransferType::from(endpoint_desc.transfer_type()),
                sync_type: usb::SyncType::from(endpoint_desc.sync_type()),
                usage_type: usb::UsageType::from(endpoint_desc.usage_type()),
                max_packet_size: endpoint_desc.max_packet_size(),
                interval: endpoint_desc.interval(),
                length: endpoint_desc.length(),
                extra: extra_desc,
                internal: InternalData::default(),
                endpoint_path: Some(endpoint_path),
            });
        }

        ret
    }

    fn build_interfaces<T: libusb::UsbContext>(
        &self,
        handle: Option<&UsbDevice<T>>,
        location: &DeviceLocation,
        config_desc: &libusb::ConfigDescriptor,
    ) -> Result<Vec<usb::Interface>> {
        let mut ret: Vec<usb::Interface> = Vec::new();

        for interface in config_desc.interfaces() {
            // Determine active alternate setting from sysfs if on Linux
            // The path for an interface in sysfs is bus-port:config.interface
            // All alternate settings share the same directory
            let sample_path = usb::DevicePath::new_with_port_path(
                location.clone().into(),
                Some(config_desc.number()),
                Some(interface.number()),
                None, // No alt setting in sysfs dir name
            )
            .to_string();
            let active_alt = get_sysfs_active_alternate_setting(&sample_path);

            for interface_desc in interface.descriptors() {
                let device_path = usb::DevicePath::new_with_port_path(
                    location.clone().into(),
                    Some(config_desc.number()),
                    Some(interface_desc.interface_number()),
                    Some(interface_desc.setting_number()),
                );
                let path = device_path.to_string();

                let mut interface = usb::Interface {
                    name: get_sysfs_string(&path, "interface").or_else(|| {
                        interface_desc
                            .description_string_index()
                            .and_then(|i| handle.and_then(|h| h.get_descriptor_string(i)))
                    }),
                    string_index: interface_desc.description_string_index().unwrap_or(0),
                    number: interface_desc.interface_number(),
                    class: usb::BaseClass::from(interface_desc.class_code()),
                    sub_class: interface_desc.sub_class_code(),
                    protocol: interface_desc.protocol_code(),
                    alt_setting: interface_desc.setting_number(),
                    active: active_alt.is_some_and(|a| a == interface_desc.setting_number()),
                    driver: get_sysfs_readlink(&path, "driver")
                        .or_else(|| get_udev_driver_name(&path).ok().flatten()),
                    syspath: get_syspath(&path).or_else(|| get_udev_syspath(&path).ok().flatten()),
                    devpaths: None,
                    mount_paths: None,
                    path,
                    length: interface_desc.length(),
                    endpoints: self.build_endpoints(handle, &device_path, &interface_desc),
                    extra: handle.and_then(|h| {
                        self.build_interface_descriptor_extra(
                            h,
                            (
                                interface_desc.class_code(),
                                interface_desc.sub_class_code(),
                                interface_desc.protocol_code(),
                            ),
                            interface_desc.interface_number(),
                            interface_desc.extra().to_vec(),
                        )
                        .ok()
                    }),
                    internal: InternalData::default(),
                    device_path: Some(device_path),
                };

                interface.devpaths = interface.dev_paths();
                interface.mount_paths = interface.block_mount_paths();

                ret.push(interface);
            }
        }

        Ok(ret)
    }

    fn build_configurations<T: libusb::UsbContext>(
        &self,
        device: &libusb::Device<T>,
        handle: Option<&UsbDevice<T>>,
        device_desc: &libusb::DeviceDescriptor,
        sp_device: &Device,
    ) -> Result<Vec<usb::Configuration>> {
        // Retrieve the current configuration (if available)
        let cur_config = get_sysfs_configuration_string(&sp_device.sysfs_name());
        let mut ret: Vec<usb::Configuration> = Vec::new();

        for n in 0..device_desc.num_configurations() {
            let config_desc = match device.config_descriptor(n) {
                Ok(c) => c,
                Err(_) => continue,
            };

            let mut attributes = Vec::new();
            if config_desc.remote_wakeup() {
                attributes.push(usb::ConfigAttributes::RemoteWakeup);
            }
            if config_desc.self_powered() {
                attributes.push(usb::ConfigAttributes::SelfPowered);
            } else {
                attributes.push(usb::ConfigAttributes::BusPowered);
            }

            // Check if we have a cached iConfiguration string
            let config_name = if let Some((config_num, ref config_name)) = cur_config {
                // Configs start from 1, not 0
                if config_num - 1 == n {
                    Some(config_name.clone())
                } else {
                    None
                }
            } else {
                None
            };

            // The rusb crate multiplies the raw MaxPower value by 2, which is not correct
            // for USB3 and later, so we need to multiply by 4 to get the correct number.
            let power_mult = if device_desc.usb_version().0 >= 3 {
                4
            } else {
                1
            };

            ret.push(usb::Configuration {
                name: config_desc
                    .description_string_index()
                    .and_then(|i| handle.and_then(|h| h.get_descriptor_string(i)))
                    .or(config_name)
                    .unwrap_or(String::new()),
                string_index: config_desc.description_string_index().unwrap_or(0),
                number: config_desc.number(),
                active: cur_config
                    .as_ref()
                    .is_some_and(|(c, _)| *c == config_desc.number()),
                attributes,
                max_power: NumericalUnit {
                    value: config_desc.max_power() as u32 * power_mult,
                    unit: String::from("mA"),
                    description: None,
                },
                length: config_desc.length(),
                total_length: config_desc.total_length(),
                num_interfaces: Some(config_desc.num_interfaces()),
                interfaces: self.build_interfaces(handle, &sp_device.location_id, &config_desc)?,
                extra: handle.and_then(|h| {
                    self.build_config_descriptor_extra(h, config_desc.extra().to_vec())
                        .ok()
                }),
                internal: Default::default(),
            });
        }

        Ok(ret)
    }

    #[allow(unused_variables)]
    fn build_spdevice_extra<T: libusb::UsbContext>(
        &self,
        device: &libusb::Device<T>,
        handle: &UsbDevice<T>,
        device_desc: &libusb::DeviceDescriptor,
        sp_device: &mut Device,
        more_extra: bool,
    ) -> Result<usb::DeviceExtra> {
        // attempt to get manufacturer and product strings from device itself
        sp_device.manufacturer = device_desc
            .manufacturer_string_index()
            .and_then(|i| handle.get_descriptor_string(i));

        if let Some(name) = device_desc
            .product_string_index()
            .and_then(|i| handle.get_descriptor_string(i))
        {
            sp_device.name = name;
        }

        sp_device.serial_num = device_desc
            .serial_number_string_index()
            .and_then(|i| handle.get_descriptor_string(i));
        let sysfs_name = sp_device.sysfs_name();

        let mut extra = usb::DeviceExtra {
            max_packet_size: device_desc.max_packet_size(),
            string_indexes: (
                device_desc.product_string_index().unwrap_or(0),
                device_desc.manufacturer_string_index().unwrap_or(0),
                device_desc.serial_number_string_index().unwrap_or(0),
            ),
            driver: get_sysfs_readlink(&sysfs_name, "driver")
                .or_else(|| get_udev_driver_name(&sysfs_name).ok().flatten()),
            syspath: get_syspath(&sysfs_name)
                .or_else(|| get_udev_syspath(&sysfs_name).ok().flatten()),
            // These are idProduct, idVendor in lsusb - from udev_hwdb/usb-ids
            vendor: names::vendor(device_desc.vendor_id()).or_else(|| {
                usb_ids::Vendor::from_id(device_desc.vendor_id()).map(|v| v.name().to_owned())
            }),
            product_name: names::product(device_desc.vendor_id(), device_desc.product_id())
                .or_else(|| {
                    usb_ids::Device::from_vid_pid(device_desc.vendor_id(), device_desc.product_id())
                        .map(|v| v.name().to_owned())
                }),
            configurations: self.build_configurations(
                device,
                Some(handle),
                device_desc,
                sp_device,
            )?,
            status: None,
            debug: None,
            binary_object_store: None,
            qualifier: None,
            hub: None,
            negotiated_speed: Some(usb::Speed::from(device.speed())),
        };

        if more_extra {
            extra.status = Self::get_device_status(handle).ok();
            extra.debug = Self::get_debug_descriptor(handle).ok();

            // Get device specific stuff: bos, hub, dualspeed, debug and status
            if device_desc.usb_version() >= rusb::Version::from_bcd(0x0201) {
                extra.binary_object_store = Self::get_bos_descriptor(handle).ok();
            }
            if device_desc.usb_version() >= rusb::Version::from_bcd(0x0200) {
                extra.qualifier = Self::get_device_qualifier(handle).ok();
            }
            if device_desc.class_code() == usb::BaseClass::Hub as u8 {
                let has_ssp = if let Some(bos) = &extra.binary_object_store {
                    bos.capabilities.iter().any(|c| {
                        matches!(c, usb::descriptors::bos::BosCapability::SuperSpeedPlus(_))
                    })
                } else {
                    false
                };
                let bcd = sp_device.bcd_usb.map_or(0x0100, |v| v.into());
                extra.hub =
                    Self::get_hub_descriptor(handle, device_desc.protocol_code(), bcd, has_ssp)
                        .ok();
            }
        }

        // Fallback to sysfs for hub port count if missing (especially useful for root hubs)
        if extra.hub.is_none()
            && (device_desc.class_code() == usb::BaseClass::Hub as u8 || sp_device.is_root_hub())
        {
            if let Some(ports) = get_sysfs_hub_ports(&sysfs_name) {
                extra.hub = Some(usb::HubDescriptor {
                    num_ports: ports,
                    ..Default::default()
                });
            }
        }

        Ok(extra)
    }

    fn open_device<T: libusb::UsbContext>(
        &self,
        device: &libusb::Device<T>,
        device_desc: &libusb::DeviceDescriptor,
    ) -> Result<UsbDevice<T>> {
        let timeout = std::time::Duration::from_millis(
            std::env::var("CYME_USB_TIMEOUT_MS")
                .ok()
                .and_then(|s| s.parse::<u64>().ok())
                .unwrap_or(200),
        );
        let handle = device.open()?;
        let language = match handle.read_languages(timeout) {
            Ok(l) => {
                if l.is_empty() {
                    return Err(Error {
                        kind: ErrorKind::LibUSB,
                        message: format!(
                            "Languages for {device:?} are empty, will be unable to obtain all data"
                        ),
                    });
                }
                l[0]
            }
            Err(e) => {
                return Err(Error {
                    kind: ErrorKind::LibUSB,
                    message: format!(
                        "Could not read languages for {device:?}, will be unable to obtain all data: {e}"
                    ),
                });
            }
        };

        Ok(UsbDevice {
            handle,
            language,
            vidpid: (device_desc.vendor_id(), device_desc.product_id()),
            location: DeviceLocation {
                bus: device.bus_number(),
                number: device.address(),
                tree_positions: device.port_numbers()?,
            },
            timeout,
        })
    }

    /// Builds a shallow [`Device`] from a [`libusb::Device`] without opening it or reading extra data.
    /// Uses sysfs strings if available (Linux) to populate identity fields.
    fn build_spdevice_shallow<T: libusb::UsbContext>(
        &self,
        device: &libusb::Device<T>,
    ) -> Result<Device> {
        let speed = match usb::Speed::from(device.speed()) {
            usb::Speed::Unknown => None,
            v => Some(DeviceSpeed::SpeedValue(v)),
        };

        let device_desc = device.device_descriptor()?;

        let mut sp_device = Device {
            vendor_id: Some(device_desc.vendor_id()),
            product_id: Some(device_desc.product_id()),
            device_speed: speed,
            location_id: DeviceLocation {
                bus: device.bus_number(),
                number: device.address(),
                tree_positions: device.port_numbers()?,
            },
            bcd_device: Some(device_desc.device_version().into()),
            bcd_usb: Some(device_desc.usb_version().into()),
            class: Some(usb::BaseClass::from(device_desc.class_code())),
            sub_class: Some(device_desc.sub_class_code()),
            protocol: Some(device_desc.protocol_code()),
            last_event: Some(Default::default()),
            ..Default::default()
        };

        let sysfs_name = sp_device.sysfs_name();
        sp_device.name = get_sysfs_string(&sysfs_name, "product")
            .or_else(|| names::product(device_desc.vendor_id(), device_desc.product_id()))
            .or_else(|| {
                usb_ids::Device::from_vid_pid(device_desc.vendor_id(), device_desc.product_id())
                    .map(|device| device.name().to_owned())
            })
            .unwrap_or_default();

        sp_device.manufacturer = get_sysfs_string(&sysfs_name, "manufacturer").or_else(|| {
            names::vendor(device_desc.vendor_id()).or_else(|| {
                usb_ids::Vendor::from_id(device_desc.vendor_id())
                    .map(|vendor| vendor.name().to_owned())
            })
        });

        sp_device.serial_num = get_sysfs_string(&sysfs_name, "serial");

        // Fallback extra data from sysfs - won't be as complete as with device open, but better than nothing and doesn't require permissions
        let mut extra = usb::DeviceExtra {
            max_packet_size: device_desc.max_packet_size(),
            string_indexes: (
                device_desc.product_string_index().unwrap_or(0),
                device_desc.manufacturer_string_index().unwrap_or(0),
                device_desc.serial_number_string_index().unwrap_or(0),
            ),
            driver: get_sysfs_readlink(&sysfs_name, "driver")
                .or_else(|| get_udev_driver_name(&sysfs_name).ok().flatten()),
            syspath: get_syspath(&sysfs_name)
                .or_else(|| get_udev_syspath(&sysfs_name).ok().flatten()),
            vendor: names::vendor(device_desc.vendor_id()).or_else(|| {
                usb_ids::Vendor::from_id(device_desc.vendor_id()).map(|v| v.name().to_owned())
            }),
            product_name: names::product(device_desc.vendor_id(), device_desc.product_id())
                .or_else(|| {
                    usb_ids::Device::from_vid_pid(device_desc.vendor_id(), device_desc.product_id())
                        .map(|v| v.name().to_owned())
                }),
            configurations: self
                .build_configurations(device, None, &device_desc, &sp_device)
                .unwrap_or_default(),
            status: None,
            debug: None,
            binary_object_store: None,
            qualifier: None,
            hub: None,
            negotiated_speed: None,
        };

        // Fallback to sysfs for hub port count if missing (especially useful for root hubs)
        if extra.hub.is_none()
            && (device_desc.class_code() == usb::BaseClass::Hub as u8 || sp_device.is_root_hub())
        {
            if let Some(ports) = get_sysfs_hub_ports(&sp_device.sysfs_name()) {
                extra.hub = Some(usb::HubDescriptor {
                    num_ports: ports,
                    ..Default::default()
                });
            }
        }

        sp_device.extra = Some(extra);

        Ok(sp_device)
    }

    /// Builds a [`Device`] from a [`libusb::Device`] by using `device_descriptor()` and intrograting for configuration strings. Optionally with `with_extra` will gather full device information, including from udev if feature is present.
    ///
    /// [`Device.profiler_error`] `Option<String>` will contain any non-critical error during gather of `with_extra` data - normally due to permissions preventing open of device descriptors.
    fn build_spdevice<T: libusb::UsbContext>(
        &self,
        device: &libusb::Device<T>,
        options: &ProfilerOptions,
    ) -> Result<Device> {
        let mut sp_device = self.build_spdevice_shallow(device)?;
        let device_desc = device.device_descriptor()?;

        // if we have a filter, check if it matches before doing extra
        let is_match = options
            .filter
            .as_ref()
            .is_none_or(|f| f.is_potential_match(&sp_device));

        if options.depth.includes_extra() && is_match {
            if let Ok(handle) = self.open_device(device, &device_desc) {
                sp_device.profiler_error = {
                    match self.build_spdevice_extra(
                        device,
                        &handle,
                        &device_desc,
                        &mut sp_device,
                        options.depth.includes_more_extra(),
                    ) {
                        Ok(extra) => {
                            sp_device.extra = Some(extra);
                            None
                        }
                        Err(e) => {
                            Some(format!(
                                "Failed to get some extra data for {sp_device}, probably requires elevated permissions: {e}"
                            ))
                        }
                    }
                }
            } else {
                log::warn!("Failed to open device {device:?} for extra data");
                sp_device.profiler_error = Some("Failed to open device for extra data".to_string());
            }
        }

        Ok(sp_device)
    }
}

impl<C: libusb::UsbContext> Profiler<UsbDevice<C>> for LibUsbProfiler {
    fn get_devices(&mut self, options: &ProfilerOptions) -> Result<Vec<Device>> {
        let mut devices = Vec::new();
        let raw_devices = libusb::DeviceList::new()?;

        // If filtering, determine which devices are matches or ancestors of matches
        let filter_set: Option<std::collections::HashSet<(u8, Vec<u8>)>> =
            if let Some(filter) = &options.filter {
                let mut set = std::collections::HashSet::new();
                for device in raw_devices.iter().filter(|d| d.port_number() != 0) {
                    if let Ok(sp_device) = self.build_spdevice_shallow(&device) {
                        // Potential match includes interface class potential if extra data not yet loaded
                        if filter.is_potential_match(&sp_device) {
                            let bus = sp_device.location_id.bus;
                            let ports = sp_device.location_id.tree_positions.clone();

                            // Add the match itself
                            set.insert((bus, ports.clone()));

                            // If building a tree, add all ancestors of this match
                            if options.tree {
                                for i in 1..ports.len() {
                                    set.insert((bus, ports[0..i].to_vec()));
                                }
                            }
                        }
                    }
                }
                Some(set)
            } else {
                None
            };

        // run through devices building Device types - not root_hubs (port number 0)
        for device in raw_devices.iter().filter(|d| d.port_number() != 0) {
            let bus = device.bus_number();
            let ports = device.port_numbers().unwrap_or_default();

            // Check if we should profile this device at all
            if let Some(set) = &filter_set {
                if !set.contains(&(bus, ports.clone())) {
                    continue;
                }
            }

            // Determine if this device is a potential match or just an ancestor
            // If it's just an ancestor, we override the options to skip extra data
            let mut device_options = options.clone();
            if let Some(filter) = &options.filter {
                if let Ok(sp_device) = self.build_spdevice_shallow(&device) {
                    // If it's not even a potential match, it must be an ancestor
                    if !filter.is_potential_match(&sp_device) {
                        device_options.depth = ProfileDepth::Identity;
                    }
                }
            }

            match self.build_spdevice(&device, &device_options) {
                Ok(sp_device) => {
                    devices.push(sp_device.to_owned());
                    let print_stderr =
                        std::env::var_os("CYME_PRINT_NON_CRITICAL_PROFILER_STDERR").is_some();

                    // print any non-critical error during extra capture
                    sp_device.profiler_error.iter().for_each(|e| {
                        if print_stderr {
                            eprintln!("{e}");
                        } else {
                            log::warn!("Non-critical error during profile: {e}");
                        }
                    });
                }
                Err(e) => eprintln!("Failed to get data for {device:?}: {e}"),
            }
        }

        Ok(devices)
    }

    #[cfg(target_os = "linux")]
    fn get_root_hubs(&mut self, options: &ProfilerOptions) -> Result<HashMap<u8, Device>> {
        let mut ret = HashMap::new();

        for device in libusb::DeviceList::new()?
            .iter()
            .filter(|d| d.port_number() == 0)
        {
            if let Ok(mut sp_device) = self.build_spdevice(&device, options) {
                // put self in as first device; root_hubs included in list on Linux
                sp_device.devices = Some(vec![sp_device.clone()]);
                ret.insert(sp_device.location_id.bus, sp_device);
            }
        }

        Ok(ret)
    }

    #[cfg(not(target_os = "linux"))]
    fn get_root_hubs(&mut self, _options: &ProfilerOptions) -> Result<HashMap<u8, Device>> {
        Ok(HashMap::new())
    }

    fn get_buses(&mut self, options: &ProfilerOptions) -> Result<HashMap<u8, Bus>> {
        <LibUsbProfiler as Profiler<UsbDevice<rusb::Context>>>::get_root_hubs(self, options).map(
            |hubs| {
                hubs.into_iter()
                    .filter_map(|(k, d)| Some((k, Bus::try_from(d).ok()?)))
                    .collect()
            },
        )
    }
}

pub(crate) fn fill_spusb(spusb: &mut SystemProfile, options: &ProfilerOptions) -> Result<()> {
    let mut profiler = LibUsbProfiler;
    <LibUsbProfiler as Profiler<UsbDevice<rusb::Context>>>::fill_spusb(
        &mut profiler,
        spusb,
        options,
    )
}
