use crate::capability::manager::CapabilityManager;
use crate::core::types::{
ChannelState, ChannelType, DeviceCapabilities, DeviceId, DeviceType, NetworkType,
};
use std::collections::HashSet;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use uuid::Uuid;
#[derive(Debug, Clone)]
#[allow(dead_code)]
struct DiscoveryInfo {
device_id: DeviceId,
first_seen: Instant,
last_seen: Instant,
rssi: Option<i8>, distance_meters: Option<f32>, discovery_method: DiscoveryMethod,
}
#[derive(Debug, Clone)]
enum DiscoveryMethod {
BleScan,
Mdns,
}
pub struct DiscoveryManager {
cap_manager: Arc<CapabilityManager>,
mdns_task: Arc<Mutex<Option<JoinHandle<()>>>>,
ble_task: Arc<Mutex<Option<JoinHandle<()>>>>,
discovery_cache: Arc<tokio::sync::RwLock<std::collections::HashMap<DeviceId, DiscoveryInfo>>>,
start_time: Instant,
}
impl DiscoveryManager {
pub fn new(cap_manager: Arc<CapabilityManager>) -> Self {
Self {
cap_manager,
mdns_task: Arc::new(Mutex::new(None)),
ble_task: Arc::new(Mutex::new(None)),
discovery_cache: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
start_time: Instant::now(),
}
}
pub async fn start_discovery(&self) -> (Option<JoinHandle<()>>, Option<JoinHandle<()>>) {
let mdns_task_guard = self.mdns_task.lock().await;
let ble_task_guard = self.ble_task.lock().await;
if mdns_task_guard.is_some() || ble_task_guard.is_some() {
return (None, None);
}
drop(mdns_task_guard);
drop(ble_task_guard);
let cap_manager = self.cap_manager.clone();
let discovery_cache = self.discovery_cache.clone();
let mdns_task_arc = self.mdns_task.clone();
let mdns_task = tokio::spawn(async move {
log::info!("Starting simulated mDNS discovery with <5s target...");
let start_time = Instant::now();
let discovery_timeout = Duration::from_secs(5);
for i in 0..3 {
if start_time.elapsed() > discovery_timeout {
break;
}
let device_id = DeviceId(Uuid::new_v4());
let distance_estimate = 5.0 + (i as f32 * 5.0);
let mut cache = discovery_cache.write().await;
cache.insert(
device_id,
DiscoveryInfo {
device_id,
first_seen: Instant::now(),
last_seen: Instant::now(),
rssi: None,
distance_meters: Some(distance_estimate),
discovery_method: DiscoveryMethod::Mdns,
},
);
drop(cache);
let caps = DeviceCapabilities {
device_id,
device_type: DeviceType::Laptop,
device_name: format!("test_device_{}", i),
supported_channels: HashSet::from([ChannelType::Lan]),
battery_level: None,
is_charging: true,
data_cost_sensitive: false,
};
cap_manager.register_remote_device(caps);
cap_manager.update_channel_state(
device_id,
ChannelType::Lan,
ChannelState {
available: true,
rtt_ms: 5,
network_type: NetworkType::WiFi,
bandwidth_bps: 100_000_000,
signal_strength: Some(-50),
..Default::default()
},
);
tokio::time::sleep(Duration::from_millis(500)).await;
}
log::info!(
"Simulated mDNS discovery completed in {}ms",
start_time.elapsed().as_millis()
);
});
*mdns_task_arc.lock().await = Some(mdns_task);
let cap_manager_ble = self.cap_manager.clone();
let discovery_cache_ble = self.discovery_cache.clone();
let ble_task_arc = self.ble_task.clone();
let ble_task = tokio::spawn(async move {
log::info!("Starting simulated BLE discovery for 10m range...");
let start_time = Instant::now();
let ble_timeout = Duration::from_secs(5);
for i in 0..2 {
if start_time.elapsed() > ble_timeout {
break;
}
let device_id = DeviceId(Uuid::new_v4());
let rssi = -60 - (i as i8 * 10); let distance = estimate_ble_distance(rssi);
if distance <= 10.0 {
let mut cache = discovery_cache_ble.write().await;
cache.insert(
device_id,
DiscoveryInfo {
device_id,
first_seen: Instant::now(),
last_seen: Instant::now(),
rssi: Some(rssi),
distance_meters: Some(distance),
discovery_method: DiscoveryMethod::BleScan,
},
);
drop(cache);
let caps = DeviceCapabilities {
device_id,
device_type: DeviceType::Smartphone,
device_name: format!("ble_device_{}", i),
supported_channels: HashSet::from([ChannelType::BluetoothLE]),
battery_level: None,
is_charging: true,
data_cost_sensitive: false,
};
cap_manager_ble.register_remote_device(caps);
cap_manager_ble.update_channel_state(
device_id,
ChannelType::BluetoothLE,
ChannelState {
available: true,
rtt_ms: 20,
network_type: NetworkType::Bluetooth,
bandwidth_bps: 1_000_000,
signal_strength: Some(rssi),
..Default::default()
},
);
}
tokio::time::sleep(Duration::from_millis(700)).await;
}
log::info!(
"Simulated BLE discovery completed in {}ms",
start_time.elapsed().as_millis()
);
});
*ble_task_arc.lock().await = Some(ble_task);
(None, None)
}
pub async fn stop_discovery(&self) {
if let Some(task) = self.mdns_task.lock().await.take() {
task.abort();
}
if let Some(task) = self.ble_task.lock().await.take() {
task.abort();
}
}
pub async fn clear_cache(&self) {
let mut cache = self.discovery_cache.write().await;
cache.clear();
}
pub async fn get_discovery_stats(&self) -> DiscoveryStats {
let cache = self.discovery_cache.read().await;
let elapsed = self.start_time.elapsed();
DiscoveryStats {
total_devices: cache.len(),
ble_devices: cache
.values()
.filter(|info| matches!(info.discovery_method, DiscoveryMethod::BleScan))
.count(),
mdns_devices: cache
.values()
.filter(|info| matches!(info.discovery_method, DiscoveryMethod::Mdns))
.count(),
average_distance: cache
.values()
.filter_map(|info| info.distance_meters)
.sum::<f32>()
/ cache.len().max(1) as f32,
discovery_time_ms: elapsed.as_millis() as u64,
}
}
pub async fn simulate_background_discovery(
&self,
device_id: DeviceId,
) -> crate::core::error::Result<()> {
log::info!("Simulating background discovery for device: {}", device_id);
let caps = DeviceCapabilities {
device_id,
device_type: DeviceType::Smartphone,
device_name: format!("BackgroundDevice-{}", device_id),
supported_channels: [ChannelType::BluetoothLE, ChannelType::Lan]
.into_iter()
.collect(),
battery_level: Some(85),
is_charging: false,
data_cost_sensitive: false,
};
self.cap_manager.register_remote_device(caps);
log::info!("NOTIFY: New device discovered in background: {}", device_id);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct DiscoveryStats {
pub total_devices: usize,
pub ble_devices: usize,
pub mdns_devices: usize,
pub average_distance: f32,
pub discovery_time_ms: u64,
}
fn estimate_ble_distance(rssi: i8) -> f32 {
if rssi > -50 {
2.0 } else if rssi > -60 {
5.0 } else if rssi > -70 {
8.0 } else if rssi > -80 {
12.0 } else {
20.0 }
}