use std::collections::BTreeMap;
use crate::link::catch::FilterSet;
use crate::protocol::opcode::{
LOCK_CLS_AXIS, LOCK_CLS_BTN, LOCK_CLS_MEDIA, LOCK_DIR_AGAINST, LOCK_DIR_BOTH, LOCK_DIR_NEG,
LOCK_DIR_POS, LOCK_DIR_WITH, LOCK_ID_ALL, LOCK_SCALE_BLOCK, LOCK_SCALE_PASS,
};
use crate::types::{Action, Class, Usage};
pub(crate) type LockKey = (u8, u16, u8);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum Override {
#[default]
None,
Press,
Force,
}
impl Override {
pub(crate) fn as_action(self) -> Option<Action> {
match self {
Override::None => None,
Override::Press => Some(Action::Press),
Override::Force => Some(Action::ForceRelease),
}
}
fn applied(action: Action) -> Override {
match action {
Action::Press => Override::Press,
Action::ForceRelease => Override::Force,
Action::SoftRelease => Override::None,
}
}
}
const SLOT_DIRS: [u8; 4] = [LOCK_DIR_POS, LOCK_DIR_NEG, LOCK_DIR_WITH, LOCK_DIR_AGAINST];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Slots([u8; 4]);
impl Default for Slots {
fn default() -> Slots {
Slots([LOCK_SCALE_PASS; 4])
}
}
impl Slots {
fn write(&mut self, dir: u8, scale: u8) {
if dir == LOCK_DIR_BOTH {
self.0 = [scale, scale, LOCK_SCALE_PASS, LOCK_SCALE_PASS];
} else if let Some(i) = SLOT_DIRS.iter().position(|&d| d == dir) {
self.0[i] = scale;
}
}
fn is_clear(self) -> bool {
self.0.iter().all(|&s| s == LOCK_SCALE_PASS)
}
fn commands(self) -> Vec<(u8, u8)> {
let [p, n, w, a] = self.0;
if p == n && w == LOCK_SCALE_PASS && a == LOCK_SCALE_PASS {
return if p == LOCK_SCALE_PASS {
Vec::new()
} else {
vec![(LOCK_DIR_BOTH, p)]
};
}
(0..4)
.filter(|&i| self.0[i] != LOCK_SCALE_PASS)
.map(|i| (SLOT_DIRS[i], self.0[i]))
.collect()
}
}
#[derive(Debug)]
pub(crate) struct LockUndo {
rows: Vec<((u8, u16), Option<Slots>)>,
media_order: Vec<u16>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub(crate) struct DesiredState {
overrides: BTreeMap<(u8, u16), Override>, locks: BTreeMap<(u8, u16), Slots>,
media_order: Vec<u16>,
catch: FilterSet,
}
impl DesiredState {
pub(crate) fn apply(&mut self, usage: Usage, action: Action) {
let key = usage.class_id();
match Override::applied(action) {
Override::None => {
self.overrides.remove(&key);
}
ov => {
self.overrides.insert(key, ov);
}
}
}
pub(crate) fn apply_lock(&mut self, key: LockKey, scale: u8) -> LockUndo {
let (class, id, dir) = key;
let scale = if class == LOCK_CLS_AXIS {
scale
} else if scale < LOCK_SCALE_PASS {
LOCK_SCALE_BLOCK
} else {
LOCK_SCALE_PASS
};
let mut undo = LockUndo {
rows: Vec::new(),
media_order: self.media_order.clone(),
};
for id in expand_blanket(class, id) {
let key = (class, id);
undo.rows.push((key, self.locks.get(&key).copied()));
let row = self.locks.entry(key).or_default();
row.write(dir, scale);
if row.is_clear() {
self.locks.remove(&key);
if is_media_slot(class, id) {
self.media_order.retain(|&m| m != id);
}
} else if is_media_slot(class, id) && !self.media_order.contains(&id) {
self.media_order.push(id);
}
}
undo
}
pub(crate) fn restore_lock(&mut self, undo: LockUndo) {
for (key, row) in undo.rows {
match row {
Some(row) => self.locks.insert(key, row),
None => self.locks.remove(&key),
};
}
self.media_order = undo.media_order;
}
pub(crate) fn clear(&mut self) {
self.overrides.clear();
self.locks.clear();
self.media_order.clear();
}
pub(crate) fn set_catch(&mut self, filters: FilterSet) {
self.catch = filters;
}
pub(crate) fn catch(&self) -> FilterSet {
self.catch.clone()
}
pub(crate) fn is_idle(&self) -> bool {
self.catch.is_empty() && self.overrides.is_empty() && self.locks.is_empty()
}
pub(crate) fn held(&self) -> impl Iterator<Item = (Usage, Action)> + '_ {
self.overrides.iter().filter_map(|(&(cls, id), ov)| {
let action = ov.as_action()?;
let class = Class::from_u8(cls)?;
Some((Usage::new(class, id), action))
})
}
pub(crate) fn held_locks(&self) -> Vec<(LockKey, u8)> {
let mut rows: Vec<(&(u8, u16), &Slots)> = self.locks.iter().collect();
rows.sort_by_key(|((class, id), _)| (*class, self.media_rank(*class, *id), *id));
rows.into_iter()
.flat_map(|(&(class, id), row)| {
row.commands()
.into_iter()
.map(move |(dir, scale)| ((class, id, dir), scale))
})
.collect()
}
fn media_rank(&self, class: u8, id: u16) -> usize {
if class != LOCK_CLS_MEDIA {
return 0;
}
self.media_order
.iter()
.position(|&m| m == id)
.unwrap_or(usize::MAX)
}
}
fn is_media_slot(class: u8, id: u16) -> bool {
class == LOCK_CLS_MEDIA && id != LOCK_ID_ALL
}
fn expand_blanket(class: u8, id: u16) -> Vec<u16> {
if class == LOCK_CLS_BTN && id == LOCK_ID_ALL {
(0..crate::protocol::opcode::BTN_COUNT as u16).collect()
} else {
vec![id]
}
}