use evdev::Key;
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::path::Path;
use std::sync::Arc;
use tokio::fs;
use tokio::sync::RwLock;
use tracing::{debug, info};
use crate::analog_calibration::AnalogCalibration;
use crate::analog_processor::{AnalogMode, CameraOutputMode};
fn serialize_remaps<S>(
remaps: &HashMap<Key, Key>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let pairs: Vec<(u16, u16)> = remaps.iter().map(|(k, v)| (k.0, v.0)).collect();
pairs.serialize(serializer)
}
fn deserialize_remaps<'de, D>(
deserializer: D,
) -> Result<HashMap<Key, Key>, D::Error>
where
D: Deserializer<'de>,
{
struct RemapsVisitor;
impl<'de> Visitor<'de> for RemapsVisitor {
type Value = HashMap<Key, Key>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence of key pairs")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut remaps = HashMap::new();
while let Some((input, output)) = seq.next_element::<(u16, u16)>()? {
remaps.insert(Key(input), Key(output));
}
Ok(remaps)
}
}
deserializer.deserialize_seq(RemapsVisitor)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LayerStateSnapshot {
pub device_id: String,
pub base_layer: usize,
pub active_toggle_layers: Vec<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LayerMode {
Hold,
Toggle,
}
impl Default for LayerMode {
fn default() -> Self {
LayerMode::Hold
}
}
impl fmt::Display for LayerMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LayerMode::Hold => write!(f, "hold"),
LayerMode::Toggle => write!(f, "toggle"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LayerConfig {
pub layer_id: usize,
pub name: String,
#[serde(default, serialize_with = "serialize_remaps", deserialize_with = "deserialize_remaps")]
pub remaps: HashMap<Key, Key>,
#[serde(default)]
pub mode: LayerMode,
#[serde(default = "default_layer_color")]
pub led_color: (u8, u8, u8),
#[serde(default)]
pub led_zone: Option<crate::led_controller::LedZone>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub analog_calibration: Option<AnalogCalibration>,
#[serde(default)]
pub analog_mode: AnalogMode,
#[serde(default)]
pub camera_output_mode: CameraOutputMode,
}
fn default_layer_color() -> (u8, u8, u8) {
(0, 0, 255) }
impl Default for LayerConfig {
fn default() -> Self {
Self {
layer_id: 0,
name: "Base".to_string(),
remaps: HashMap::new(),
mode: LayerMode::Hold,
led_color: (255, 255, 255), led_zone: Some(crate::led_controller::LedZone::Logo),
analog_calibration: None, analog_mode: AnalogMode::Disabled, camera_output_mode: CameraOutputMode::Scroll, }
}
}
impl LayerConfig {
pub fn new(layer_id: usize, name: String, mode: LayerMode) -> Self {
Self {
layer_id,
name,
remaps: HashMap::new(),
mode,
led_color: (255, 255, 255),
led_zone: None,
analog_calibration: None,
analog_mode: AnalogMode::Disabled, camera_output_mode: CameraOutputMode::Scroll, }
}
pub fn add_remap(&mut self, input: Key, output: Key) {
self.remaps.insert(input, output);
}
pub fn get_remap(&self, key: Key) -> Option<Key> {
self.remaps.get(&key).copied()
}
pub fn is_empty(&self) -> bool {
self.remaps.is_empty()
}
pub fn len(&self) -> usize {
self.remaps.len()
}
pub fn analog_calibration(&self) -> Option<&AnalogCalibration> {
self.analog_calibration.as_ref()
}
pub fn camera_output_mode(&self) -> CameraOutputMode {
self.camera_output_mode
}
}
#[derive(Debug, Clone)]
pub struct DeviceLayerState {
pub device_id: String,
pub base_layer: usize,
pub active_layers: HashSet<usize>,
pub layer_configs: Vec<LayerConfig>,
pub active_toggle_layers: HashSet<usize>,
pub active_hold_layers: HashSet<usize>,
}
impl DeviceLayerState {
pub fn new(device_id: String) -> Self {
let layer_configs = vec![
LayerConfig {
layer_id: 0,
name: "Base".to_string(),
remaps: HashMap::new(),
mode: LayerMode::Hold,
led_color: (255, 255, 255), led_zone: Some(crate::led_controller::LedZone::Logo),
analog_calibration: None, analog_mode: AnalogMode::Disabled, camera_output_mode: CameraOutputMode::Scroll, },
LayerConfig {
layer_id: 1,
name: "Layer 1".to_string(),
remaps: HashMap::new(),
mode: LayerMode::Hold,
led_color: (0, 0, 255), led_zone: Some(crate::led_controller::LedZone::Logo),
analog_calibration: None, analog_mode: AnalogMode::Disabled, camera_output_mode: CameraOutputMode::Scroll, },
LayerConfig {
layer_id: 2,
name: "Layer 2".to_string(),
remaps: HashMap::new(),
mode: LayerMode::Hold,
led_color: (0, 255, 0), led_zone: Some(crate::led_controller::LedZone::Logo),
analog_calibration: None, analog_mode: AnalogMode::Disabled, camera_output_mode: CameraOutputMode::Scroll, },
];
Self {
device_id,
base_layer: 0,
active_layers: HashSet::new(),
layer_configs,
active_toggle_layers: HashSet::new(),
active_hold_layers: HashSet::new(),
}
}
pub fn get_effective_layer(&self) -> usize {
self.active_hold_layers
.iter()
.chain(self.active_toggle_layers.iter())
.copied()
.max()
.unwrap_or(self.base_layer)
}
pub fn activate_layer(&mut self, layer_id: usize) {
if let Some(config) = self.get_layer_config(layer_id) {
if config.mode == LayerMode::Toggle {
self.active_toggle_layers.insert(layer_id);
} else {
self.active_hold_layers.insert(layer_id);
}
}
self.active_layers.insert(layer_id);
debug!(
"Device {}: Activated layer {}, effective layer now {}",
self.device_id,
layer_id,
self.get_effective_layer()
);
}
pub fn deactivate_layer(&mut self, layer_id: usize) {
self.active_layers.remove(&layer_id);
self.active_toggle_layers.remove(&layer_id);
self.active_hold_layers.remove(&layer_id);
debug!(
"Device {}: Deactivated layer {}, effective layer now {}",
self.device_id,
layer_id,
self.get_effective_layer()
);
}
pub fn is_layer_active(&self, layer_id: usize) -> bool {
self.active_layers.contains(&layer_id)
}
pub fn toggle_layer(&mut self, layer_id: usize) -> bool {
if self.active_toggle_layers.contains(&layer_id) {
self.active_toggle_layers.remove(&layer_id);
self.active_layers.remove(&layer_id);
debug!(
"Device {}: Toggled layer {} OFF, effective layer now {}",
self.device_id,
layer_id,
self.get_effective_layer()
);
false
} else {
self.active_toggle_layers.insert(layer_id);
self.active_layers.insert(layer_id);
debug!(
"Device {}: Toggled layer {} ON, effective layer now {}",
self.device_id,
layer_id,
self.get_effective_layer()
);
true
}
}
pub fn is_toggle_layer_active(&self, layer_id: usize) -> bool {
self.active_toggle_layers.contains(&layer_id)
}
pub fn activate_hold_layer(&mut self, layer_id: usize) {
self.active_hold_layers.insert(layer_id);
debug!(
"Device {}: Activated hold layer {}, effective layer now {}",
self.device_id,
layer_id,
self.get_effective_layer()
);
}
pub fn deactivate_hold_layer(&mut self, layer_id: usize) -> bool {
let was_active = self.active_hold_layers.remove(&layer_id);
debug!(
"Device {}: Deactivated hold layer {} (was active: {}), effective layer now {}",
self.device_id,
layer_id,
was_active,
self.get_effective_layer()
);
was_active
}
pub fn is_hold_layer_active(&self, layer_id: usize) -> bool {
self.active_hold_layers.contains(&layer_id)
}
pub fn get_layer_config(&self, layer_id: usize) -> Option<&LayerConfig> {
self.layer_configs.iter().find(|c| c.layer_id == layer_id)
}
pub fn get_layer_config_mut(&mut self, layer_id: usize) -> Option<&mut LayerConfig> {
self.layer_configs.iter_mut().find(|c| c.layer_id == layer_id)
}
pub fn add_layer_config(&mut self, config: LayerConfig) {
self.layer_configs.retain(|c| c.layer_id != config.layer_id);
self.layer_configs.push(config);
}
pub fn get_all_layer_configs(&self) -> &[LayerConfig] {
&self.layer_configs
}
pub fn layer_count(&self) -> usize {
self.layer_configs.len()
}
pub fn has_minimum_layers(&self) -> bool {
self.layer_configs.len() >= 3
}
pub fn to_snapshot(&self) -> LayerStateSnapshot {
LayerStateSnapshot {
device_id: self.device_id.clone(),
base_layer: self.base_layer,
active_toggle_layers: self.active_toggle_layers.iter().copied().collect(),
}
}
pub fn apply_snapshot(&mut self, snapshot: LayerStateSnapshot) {
self.base_layer = snapshot.base_layer;
self.active_toggle_layers = snapshot.active_toggle_layers.into_iter().collect();
self.active_layers = self.active_toggle_layers.clone();
}
}
pub struct LayerManager {
devices: Arc<RwLock<HashMap<String, DeviceLayerState>>>,
led_controller: Option<Arc<crate::led_controller::LedController>>,
}
impl LayerManager {
pub fn new(led_controller: Option<Arc<crate::led_controller::LedController>>) -> Self {
Self {
devices: Arc::new(RwLock::new(HashMap::new())),
led_controller,
}
}
pub fn new_without_led() -> Self {
Self::new(None)
}
pub async fn get_device_state(&self, device_id: &str) -> Option<DeviceLayerState> {
let devices = self.devices.read().await;
devices.get(device_id).cloned()
}
pub async fn get_or_create_device_state(&self, device_id: &str) -> DeviceLayerState {
let mut devices = self.devices.write().await;
if !devices.contains_key(device_id) {
info!("Creating new layer state for device {}", device_id);
devices.insert(device_id.to_string(), DeviceLayerState::new(device_id.to_string()));
}
devices.get(device_id).cloned().unwrap()
}
pub async fn activate_layer(&self, device_id: &str, layer_id: usize) {
let mut devices = self.devices.write().await;
if let Some(state) = devices.get_mut(device_id) {
state.activate_layer(layer_id);
} else {
let mut new_state = DeviceLayerState::new(device_id.to_string());
new_state.activate_layer(layer_id);
devices.insert(device_id.to_string(), new_state);
}
drop(devices);
self.update_led_for_layer(device_id, layer_id).await;
}
pub async fn deactivate_layer(&self, device_id: &str, layer_id: usize) {
let mut devices = self.devices.write().await;
if let Some(state) = devices.get_mut(device_id) {
state.deactivate_layer(layer_id);
}
drop(devices);
let effective_layer = self.get_effective_layer(device_id).await;
self.update_led_for_layer(device_id, effective_layer).await;
}
pub async fn get_effective_layer(&self, device_id: &str) -> usize {
let devices = self.devices.read().await;
devices
.get(device_id)
.map(|s| s.get_effective_layer())
.unwrap_or(0)
}
pub async fn get_active_layers(&self, device_id: &str) -> Vec<usize> {
let devices = self.devices.read().await;
devices
.get(device_id)
.map(|s| {
let mut layers: Vec<usize> = s
.active_hold_layers
.iter()
.chain(s.active_toggle_layers.iter())
.copied()
.collect();
layers.sort(); layers
})
.unwrap_or_default()
}
async fn update_led_for_layer(&self, device_id: &str, layer_id: usize) {
let Some(ref led_controller) = self.led_controller else {
return; };
let devices = self.devices.read().await;
let Some(state) = devices.get(device_id) else {
return; };
let Some(layer_config) = state.get_layer_config(layer_id) else {
return; };
let (r, g, b) = layer_config.led_color;
let zone = layer_config.led_zone.unwrap_or(crate::led_controller::LedZone::Logo);
if let Err(e) = led_controller.set_zone_color(zone, r, g, b).await {
debug!("Failed to set LED color for layer {}: {}", layer_id, e);
}
led_controller.set_layer_color(layer_id, (r, g, b)).await;
debug!("Updated LED for device {} layer {} to RGB {:?}", device_id, layer_id, (r, g, b));
}
pub async fn add_layer_config(&self, device_id: &str, config: LayerConfig) {
let mut devices = self.devices.write().await;
if !devices.contains_key(device_id) {
devices.insert(device_id.to_string(), DeviceLayerState::new(device_id.to_string()));
}
if let Some(state) = devices.get_mut(device_id) {
state.add_layer_config(config);
}
}
pub async fn get_device_ids(&self) -> Vec<String> {
let devices = self.devices.read().await;
devices.keys().cloned().collect()
}
pub async fn device_count(&self) -> usize {
let devices = self.devices.read().await;
devices.len()
}
pub async fn toggle_layer(
&self,
device_id: &str,
layer_id: usize,
) -> Result<bool, String> {
let mut devices = self.devices.write().await;
if !devices.contains_key(device_id) {
info!("Creating new layer state for device {}", device_id);
devices.insert(device_id.to_string(), DeviceLayerState::new(device_id.to_string()));
}
if let Some(state) = devices.get_mut(device_id) {
if layer_id >= state.layer_count() {
return Err(format!(
"Layer ID {} exceeds configured layers (device has {} layers)",
layer_id,
state.layer_count()
));
}
Ok(state.toggle_layer(layer_id))
} else {
Err(format!("Failed to get or create device state for {}", device_id))
}
}
pub async fn is_toggle_layer_active(&self, device_id: &str, layer_id: usize) -> bool {
let devices = self.devices.read().await;
devices
.get(device_id)
.map(|s| s.is_toggle_layer_active(layer_id))
.unwrap_or(false)
}
pub async fn activate_hold_layer(
&self,
device_id: &str,
layer_id: usize,
) -> Result<(), String> {
let mut devices = self.devices.write().await;
if !devices.contains_key(device_id) {
info!("Creating new layer state for device {}", device_id);
devices.insert(device_id.to_string(), DeviceLayerState::new(device_id.to_string()));
}
if let Some(state) = devices.get_mut(device_id) {
if layer_id >= state.layer_count() {
return Err(format!(
"Layer ID {} exceeds configured layers (device has {} layers)",
layer_id,
state.layer_count()
));
}
state.activate_hold_layer(layer_id);
Ok(())
} else {
Err(format!("Failed to get or create device state for {}", device_id))
}
}
pub async fn deactivate_hold_layer(
&self,
device_id: &str,
layer_id: usize,
) -> Result<bool, String> {
let mut devices = self.devices.write().await;
if let Some(state) = devices.get_mut(device_id) {
Ok(state.deactivate_hold_layer(layer_id))
} else {
Err(format!("Device {} not found", device_id))
}
}
pub async fn is_hold_layer_active(&self, device_id: &str, layer_id: usize) -> bool {
let devices = self.devices.read().await;
devices
.get(device_id)
.map(|s| s.is_hold_layer_active(layer_id))
.unwrap_or(false)
}
pub fn to_snapshots(&self) -> Vec<LayerStateSnapshot> {
Vec::new()
}
pub async fn to_snapshots_async(&self) -> Vec<LayerStateSnapshot> {
let devices = self.devices.read().await;
devices.values().map(|state| state.to_snapshot()).collect()
}
pub async fn apply_snapshot(&self, snapshot: LayerStateSnapshot) {
self.get_or_create_device_state(&snapshot.device_id).await;
let mut devices = self.devices.write().await;
if let Some(state) = devices.get_mut(&snapshot.device_id) {
state.apply_snapshot(snapshot);
}
}
pub async fn save_to_path(&self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
let snapshots = self.to_snapshots_async().await;
let yaml = serde_yaml::to_string(&snapshots)?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).await?;
}
fs::write(path, yaml).await?;
info!(
"Saved layer state for {} devices to {}",
snapshots.len(),
path.display()
);
Ok(())
}
pub async fn load_from_path(&mut self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
if !path.exists() {
info!(
"Layer state file not found at {}, starting with empty state",
path.display()
);
return Ok(());
}
let yaml = fs::read_to_string(path).await?;
let snapshots: Vec<LayerStateSnapshot> = serde_yaml::from_str(&yaml)?;
let snapshot_count = snapshots.len();
for snapshot in snapshots {
self.apply_snapshot(snapshot).await;
}
info!(
"Loaded layer state for {} devices from {}",
snapshot_count,
path.display()
);
Ok(())
}
pub async fn set_layer_config(
&self,
device_id: &str,
layer_id: usize,
config: LayerConfig,
) -> Result<(), String> {
let mut devices = self.devices.write().await;
if !devices.contains_key(device_id) {
info!("Creating new layer state for device {}", device_id);
devices.insert(device_id.to_string(), DeviceLayerState::new(device_id.to_string()));
}
if let Some(device_state) = devices.get_mut(device_id) {
if layer_id >= device_state.layer_configs.len() {
return Err(format!(
"Layer ID {} exceeds configured layers (device has {} layers)",
layer_id,
device_state.layer_configs.len()
));
}
device_state.layer_configs[layer_id].name = config.name;
device_state.layer_configs[layer_id].mode = config.mode;
device_state.layer_configs[layer_id].led_color = config.led_color;
device_state.layer_configs[layer_id].led_zone = config.led_zone;
info!(
"Updated layer {} configuration for device {}: name={}, mode={:?}, led_color={:?}",
layer_id, device_id, device_state.layer_configs[layer_id].name,
device_state.layer_configs[layer_id].mode,
device_state.layer_configs[layer_id].led_color
);
Ok(())
} else {
Err(format!("Failed to get or create device state for {}", device_id))
}
}
}
impl Default for LayerManager {
fn default() -> Self {
Self::new(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_layer_manager_creation() {
let manager = LayerManager::new(None);
assert_eq!(manager.device_count().await, 0);
let device_ids = manager.get_device_ids().await;
assert!(device_ids.is_empty());
}
#[tokio::test]
async fn test_device_layer_state_creation() {
let state = DeviceLayerState::new("1532:0220".to_string());
assert_eq!(state.device_id, "1532:0220");
assert_eq!(state.base_layer, 0);
assert!(state.active_layers.is_empty());
assert!(state.active_toggle_layers.is_empty());
assert!(state.active_hold_layers.is_empty());
assert_eq!(state.get_effective_layer(), 0);
}
#[tokio::test]
async fn test_activate_layer_hold_mode() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_layer(1);
assert!(state.is_layer_active(1));
assert_eq!(state.get_effective_layer(), 1);
state.activate_layer(2);
assert!(state.is_layer_active(2));
assert_eq!(state.get_effective_layer(), 2);
state.deactivate_layer(2);
assert!(!state.is_layer_active(2));
assert_eq!(state.get_effective_layer(), 1);
}
#[tokio::test]
async fn test_get_effective_layer() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
assert_eq!(state.get_effective_layer(), 0);
state.activate_layer(1);
assert_eq!(state.get_effective_layer(), 1);
state.activate_layer(2);
assert_eq!(state.get_effective_layer(), 2);
state.deactivate_layer(2);
assert_eq!(state.get_effective_layer(), 1);
state.deactivate_layer(1);
assert_eq!(state.get_effective_layer(), 0);
}
#[tokio::test]
async fn test_minimum_three_layers() {
let state = DeviceLayerState::new("1532:0220".to_string());
assert!(state.has_minimum_layers());
assert_eq!(state.layer_count(), 3);
let configs = state.get_all_layer_configs();
assert_eq!(configs[0].layer_id, 0);
assert_eq!(configs[1].layer_id, 1);
assert_eq!(configs[2].layer_id, 2);
}
#[tokio::test]
async fn test_layer_config_default() {
let config = LayerConfig::default();
assert_eq!(config.layer_id, 0);
assert_eq!(config.name, "Base");
assert_eq!(config.mode, LayerMode::Hold);
assert!(config.is_empty());
assert_eq!(config.len(), 0);
}
#[tokio::test]
async fn test_layer_config_remaps() {
let mut config = LayerConfig::new(1, "Test".to_string(), LayerMode::Toggle);
assert_eq!(config.layer_id, 1);
assert_eq!(config.name, "Test");
assert_eq!(config.mode, LayerMode::Toggle);
config.add_remap(Key::KEY_A, Key::KEY_B);
assert!(!config.is_empty());
assert_eq!(config.len(), 1);
assert_eq!(config.get_remap(Key::KEY_A), Some(Key::KEY_B));
assert_eq!(config.get_remap(Key::KEY_C), None);
}
#[tokio::test]
async fn test_layer_manager_get_or_create() {
let manager = LayerManager::new(None);
let state1 = manager.get_or_create_device_state("1532:0220").await;
assert_eq!(state1.device_id, "1532:0220");
assert_eq!(manager.device_count().await, 1);
let state2 = manager.get_or_create_device_state("1532:0220").await;
assert_eq!(state2.device_id, "1532:0220");
assert_eq!(manager.device_count().await, 1);
}
#[tokio::test]
async fn test_layer_manager_activate_deactivate() {
let manager = LayerManager::new(None);
manager.activate_layer("1532:0220", 1).await;
assert_eq!(manager.get_effective_layer("1532:0220").await, 1);
manager.deactivate_layer("1532:0220", 1).await;
assert_eq!(manager.get_effective_layer("1532:0220").await, 0);
}
#[tokio::test]
async fn test_toggle_layer() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
let active = state.toggle_layer(1);
assert!(active);
assert!(state.is_layer_active(1));
let active = state.toggle_layer(1);
assert!(!active);
assert!(!state.is_layer_active(1));
}
#[tokio::test]
async fn test_add_layer_config() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
let new_config = LayerConfig::new(3, "Custom".to_string(), LayerMode::Toggle);
state.add_layer_config(new_config);
assert_eq!(state.layer_count(), 4);
let config = state.get_layer_config(3);
assert!(config.is_some());
assert_eq!(config.unwrap().name, "Custom");
}
#[tokio::test]
async fn test_layer_mode_display() {
assert_eq!(LayerMode::Hold.to_string(), "hold");
assert_eq!(LayerMode::Toggle.to_string(), "toggle");
}
#[tokio::test]
async fn test_toggle_layer_on() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
let active = state.toggle_layer(1);
assert!(active, "toggle_layer should return true when turning on");
assert!(state.is_layer_active(1), "layer 1 should be active");
assert!(
state.active_toggle_layers.contains(&1),
"layer 1 should be in active_toggle_layers"
);
assert!(
state.is_toggle_layer_active(1),
"is_toggle_layer_active should return true"
);
}
#[tokio::test]
async fn test_toggle_layer_off() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.toggle_layer(1);
assert!(state.is_toggle_layer_active(1));
let active = state.toggle_layer(1);
assert!(!active, "toggle_layer should return false when turning off");
assert!(!state.is_layer_active(1), "layer 1 should not be active");
assert!(
!state.active_toggle_layers.contains(&1),
"layer 1 should not be in active_toggle_layers"
);
assert!(
!state.is_toggle_layer_active(1),
"is_toggle_layer_active should return false"
);
}
#[tokio::test]
async fn test_toggle_layer_persistence() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
let mut expected = true;
for i in 0..6 {
let active = state.toggle_layer(1);
assert_eq!(
active, expected,
"Toggle {} should return {}",
i, expected
);
assert_eq!(
state.is_toggle_layer_active(1),
expected,
"After toggle {}, is_toggle_layer_active should be {}",
i, expected
);
expected = !expected;
}
}
#[tokio::test]
async fn test_toggle_layer_affects_effective_layer() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
assert_eq!(state.get_effective_layer(), 0);
state.toggle_layer(2);
assert_eq!(state.get_effective_layer(), 2, "effective layer should be 2");
state.toggle_layer(1);
assert_eq!(state.get_effective_layer(), 2, "effective layer should still be 2");
state.toggle_layer(2);
assert_eq!(state.get_effective_layer(), 1, "effective layer should be 1");
}
#[tokio::test]
async fn test_multiple_toggle_layers() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.toggle_layer(1);
state.toggle_layer(2);
assert!(
state.active_toggle_layers.contains(&1),
"layer 1 should be in active_toggle_layers"
);
assert!(
state.active_toggle_layers.contains(&2),
"layer 2 should be in active_toggle_layers"
);
assert_eq!(state.active_toggle_layers.len(), 2);
assert!(state.is_toggle_layer_active(1));
assert!(state.is_toggle_layer_active(2));
}
#[tokio::test]
async fn test_is_toggle_layer_active() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
assert!(!state.is_toggle_layer_active(1));
assert!(!state.is_toggle_layer_active(2));
state.toggle_layer(1);
assert!(state.is_toggle_layer_active(1));
assert!(!state.is_toggle_layer_active(2));
state.toggle_layer(2);
assert!(state.is_toggle_layer_active(1));
assert!(state.is_toggle_layer_active(2));
state.toggle_layer(1);
assert!(!state.is_toggle_layer_active(1));
assert!(state.is_toggle_layer_active(2));
}
#[tokio::test]
async fn test_layer_manager_toggle_layer() {
let manager = LayerManager::new(None);
let result = manager.toggle_layer("1532:0220", 1).await;
assert!(result.is_ok());
assert!(result.unwrap(), "toggle_layer should return true (on)");
assert!(
manager
.is_toggle_layer_active("1532:0220", 1)
.await,
"layer 1 should be toggle active"
);
assert_eq!(
manager.get_effective_layer("1532:0220").await,
1,
"effective layer should be 1"
);
let result = manager.toggle_layer("1532:0220", 1).await;
assert!(result.is_ok());
assert!(!result.unwrap(), "toggle_layer should return false (off)");
assert!(
!manager
.is_toggle_layer_active("1532:0220", 1)
.await,
"layer 1 should not be toggle active"
);
assert_eq!(
manager.get_effective_layer("1532:0220").await,
0,
"effective layer should be back to base"
);
}
#[tokio::test]
async fn test_layer_manager_toggle_invalid_layer() {
let manager = LayerManager::new(None);
let result = manager.toggle_layer("1532:0220", 5).await;
assert!(result.is_err(), "should return error for invalid layer ID");
assert!(
result.unwrap_err().contains("exceeds configured layers"),
"error message should mention exceeding configured layers"
);
}
#[tokio::test]
async fn test_layer_manager_is_toggle_layer_active_not_found() {
let manager = LayerManager::new(None);
assert!(
!manager
.is_toggle_layer_active("nonexistent", 1)
.await,
"should return false for non-existent device"
);
}
#[tokio::test]
async fn test_activate_hold_layer() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_hold_layer(1);
assert!(state.is_hold_layer_active(1));
assert!(state.active_hold_layers.contains(&1));
assert_eq!(state.get_effective_layer(), 1);
}
#[tokio::test]
async fn test_deactivate_hold_layer() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_hold_layer(1);
assert!(state.is_hold_layer_active(1));
let was_active = state.deactivate_hold_layer(1);
assert!(was_active);
assert!(!state.is_hold_layer_active(1));
assert!(state.active_hold_layers.is_empty());
assert_eq!(state.get_effective_layer(), 0);
let was_active = state.deactivate_hold_layer(1);
assert!(!was_active);
}
#[tokio::test]
async fn test_hold_layer_affects_effective_layer() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
assert_eq!(state.get_effective_layer(), 0);
state.activate_hold_layer(1);
assert_eq!(state.get_effective_layer(), 1);
state.activate_hold_layer(2);
assert_eq!(state.get_effective_layer(), 2);
state.deactivate_hold_layer(2);
assert_eq!(state.get_effective_layer(), 1);
state.deactivate_hold_layer(1);
assert_eq!(state.get_effective_layer(), 0);
}
#[tokio::test]
async fn test_multiple_hold_layers() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_hold_layer(1);
state.activate_hold_layer(2);
assert!(state.is_hold_layer_active(1));
assert!(state.is_hold_layer_active(2));
assert_eq!(state.active_hold_layers.len(), 2);
assert_eq!(state.get_effective_layer(), 2);
}
#[tokio::test]
async fn test_hold_layer_cleanup() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_hold_layer(1);
assert!(state.active_hold_layers.contains(&1));
state.deactivate_hold_layer(1);
assert!(!state.active_hold_layers.contains(&1));
assert!(state.active_hold_layers.is_empty());
}
#[tokio::test]
async fn test_hold_and_toggle_layers_combined() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_hold_layer(1);
state.toggle_layer(2);
assert!(state.is_hold_layer_active(1));
assert!(state.is_toggle_layer_active(2));
assert_eq!(state.get_effective_layer(), 2);
state.deactivate_hold_layer(1);
assert_eq!(state.get_effective_layer(), 2);
state.toggle_layer(2);
assert_eq!(state.get_effective_layer(), 0);
}
#[tokio::test]
async fn test_layer_manager_hold_activation() {
let manager = LayerManager::new(None);
let result = manager.activate_hold_layer("1532:0220", 1).await;
assert!(result.is_ok());
assert_eq!(manager.get_effective_layer("1532:0220").await, 1);
assert!(manager.is_hold_layer_active("1532:0220", 1).await);
let result = manager.deactivate_hold_layer("1532:0220", 1).await;
assert!(result.is_ok());
assert!(result.unwrap());
assert_eq!(manager.get_effective_layer("1532:0220").await, 0);
assert!(!manager.is_hold_layer_active("1532:0220", 1).await);
}
#[tokio::test]
async fn test_layer_manager_hold_layer_invalid_id() {
let manager = LayerManager::new(None);
let result = manager.activate_hold_layer("1532:0220", 5).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("exceeds configured layers"));
}
#[tokio::test]
async fn test_layer_manager_deactivate_hold_nonexistent_device() {
let manager = LayerManager::new(None);
let result = manager.deactivate_hold_layer("nonexistent", 1).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_layer_stack_composition() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_hold_layer(1);
state.toggle_layer(2);
assert_eq!(state.get_effective_layer(), 2);
}
#[tokio::test]
async fn test_highest_layer_wins() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_hold_layer(1);
state.activate_hold_layer(2);
assert_eq!(state.get_effective_layer(), 2);
}
#[tokio::test]
async fn test_empty_layer_stack() {
let state = DeviceLayerState::new("1532:0220".to_string());
assert_eq!(state.get_effective_layer(), 0);
assert_eq!(state.base_layer, 0);
}
#[tokio::test]
async fn test_get_active_layers() {
let manager = LayerManager::new(None);
manager.activate_hold_layer("1532:0220", 1).await.unwrap();
manager.toggle_layer("1532:0220", 2).await.unwrap();
let active = manager.get_active_layers("1532:0220").await;
assert_eq!(active, vec![1, 2]);
let empty = manager.get_active_layers("nonexistent").await;
assert!(empty.is_empty());
}
#[tokio::test]
async fn test_hold_and_toggle_overlap() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_hold_layer(1);
state.toggle_layer(1);
assert!(state.active_hold_layers.contains(&1));
assert!(state.active_toggle_layers.contains(&1));
assert_eq!(state.get_effective_layer(), 1);
let manager = LayerManager::new(None);
manager.get_or_create_device_state("1532:0220").await;
manager.activate_hold_layer("1532:0220", 1).await.unwrap();
manager.toggle_layer("1532:0220", 1).await.unwrap();
let active = manager.get_active_layers("1532:0220").await;
assert!(active.contains(&1));
}
#[tokio::test]
async fn test_layer_priority_ordering() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_hold_layer(1);
assert_eq!(state.get_effective_layer(), 1);
state.activate_hold_layer(2);
assert_eq!(state.get_effective_layer(), 2); }
#[tokio::test]
async fn test_layer_stack_efficient_iteration() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_hold_layer(1);
state.toggle_layer(2);
assert_eq!(state.get_effective_layer(), 2);
state.deactivate_hold_layer(2); state.toggle_layer(2); assert_eq!(state.get_effective_layer(), 1);
}
#[tokio::test]
async fn test_layer_manager_get_active_layers_sorted() {
let manager = LayerManager::new(None);
manager.toggle_layer("1532:0220", 2).await.unwrap();
manager.toggle_layer("1532:0220", 1).await.unwrap();
let active = manager.get_active_layers("1532:0220").await;
assert_eq!(active, vec![1, 2]);
}
#[tokio::test]
async fn test_layer_state_snapshot() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.toggle_layer(1);
state.toggle_layer(2);
let snapshot = state.to_snapshot();
assert_eq!(snapshot.device_id, "1532:0220");
assert_eq!(snapshot.base_layer, 0);
assert_eq!(snapshot.active_toggle_layers.len(), 2);
assert!(snapshot.active_toggle_layers.contains(&1));
assert!(snapshot.active_toggle_layers.contains(&2));
}
#[tokio::test]
async fn test_to_snapshot_excludes_hold_layers() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
state.activate_hold_layer(1);
let snapshot = state.to_snapshot();
assert!(!snapshot.active_toggle_layers.contains(&1));
assert_eq!(snapshot.active_toggle_layers.len(), 0);
}
#[tokio::test]
async fn test_apply_snapshot_restores_toggle_layers() {
let mut state = DeviceLayerState::new("1532:0220".to_string());
let snapshot = LayerStateSnapshot {
device_id: "1532:0220".to_string(),
base_layer: 0,
active_toggle_layers: vec![1, 2],
};
state.apply_snapshot(snapshot);
assert!(state.is_toggle_layer_active(1));
assert!(state.is_toggle_layer_active(2));
assert_eq!(state.active_toggle_layers.len(), 2);
}
#[tokio::test]
async fn test_save_load_roundtrip() {
use tempfile::TempDir;
let manager = LayerManager::new(None);
manager.toggle_layer("1532:0220", 1).await.unwrap();
manager.toggle_layer("1532:0220", 2).await.unwrap();
let temp_dir = TempDir::new().unwrap();
let state_path = temp_dir.path().join("layer_state.yaml");
manager.save_to_path(&state_path).await.unwrap();
let mut new_manager = LayerManager::new(None);
new_manager.load_from_path(&state_path).await.unwrap();
assert!(new_manager.is_toggle_layer_active("1532:0220", 1).await);
assert!(new_manager.is_toggle_layer_active("1532:0220", 2).await);
assert_eq!(new_manager.get_effective_layer("1532:0220").await, 2);
}
#[tokio::test]
async fn test_empty_snapshot_load() {
use tempfile::TempDir;
let mut manager = LayerManager::new(None);
let temp_dir = TempDir::new().unwrap();
let state_path = temp_dir.path().join("nonexistent.yaml");
let result = manager.load_from_path(&state_path).await;
assert!(result.is_ok(), "Loading from non-existent file should succeed");
}
#[tokio::test]
async fn test_layer_manager_with_led_none() {
let manager = LayerManager::new(None);
assert_eq!(manager.device_count().await, 0);
manager.activate_layer("test_device", 1).await;
assert_eq!(manager.get_effective_layer("test_device").await, 1);
}
#[tokio::test]
async fn test_new_without_led() {
let manager = LayerManager::new_without_led();
assert_eq!(manager.device_count().await, 0);
}
#[tokio::test]
async fn test_default_creates_manager_without_led() {
let manager = LayerManager::default();
assert_eq!(manager.device_count().await, 0);
}
#[tokio::test]
async fn test_activate_layer_without_led() {
let manager = LayerManager::new(None);
manager.activate_layer("test_device", 2).await;
assert_eq!(manager.get_effective_layer("test_device").await, 2);
manager.deactivate_layer("test_device", 2).await;
assert_eq!(manager.get_effective_layer("test_device").await, 0);
}
}