use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::hash::Hash;
#[derive(Debug, PartialEq, Eq)]
pub enum StateError {
VarNotFound(String),
InvalidVarType { var: String, expected: &'static str },
}
impl fmt::Display for StateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StateError::VarNotFound(var) => write!(f, "State variable '{var}' not found"),
StateError::InvalidVarType { var, expected } => {
write!(f, "State variable '{var}' is not of type {expected}")
}
}
}
}
impl Error for StateError {}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct State {
pub vars: HashMap<String, StateVar>,
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.vars.is_empty() {
write!(f, "empty state")?;
} else {
writeln!(f, "State:")?;
for (key, value) in &self.vars {
writeln!(f, " - {key}: {value}")?;
}
}
Ok(())
}
}
impl Hash for State {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let mut keys: Vec<_> = self.vars.keys().collect();
keys.sort();
for key in keys {
key.hash(state);
self.vars.get(key).unwrap().hash(state);
}
}
}
impl Default for State {
fn default() -> Self {
Self::empty()
}
}
impl State {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> StateBuilder {
StateBuilder::new()
}
pub fn empty() -> Self {
State {
vars: HashMap::new(),
}
}
pub fn set<T: IntoStateVar>(&mut self, key: &str, value: T) {
self.vars.insert(key.to_string(), value.into_state_var());
}
pub fn get<T>(&self, key: &str) -> Option<T>
where
T: TryFromStateVar,
{
self.get_raw(key)
.and_then(|var| T::try_from_state_var(var, key).ok())
}
fn get_raw(&self, key: &str) -> Option<&StateVar> {
self.vars.get(key)
}
pub fn satisfies(&self, conditions: &State) -> bool {
for (key, value) in &conditions.vars {
match self.vars.get(key) {
Some(current_value) => {
match (current_value, value) {
(StateVar::Bool(cur), StateVar::Bool(req)) => {
if cur != req {
return false;
}
}
(StateVar::I64(cur), StateVar::I64(req)) => {
if cur < req {
return false;
}
}
(StateVar::F64(cur), StateVar::F64(req)) => {
if cur < req {
return false;
}
}
(StateVar::String(cur), StateVar::String(req)) => {
if cur != req {
return false;
}
}
_ => return false, }
}
None => return false,
}
}
true
}
pub fn apply(&mut self, changes: &HashMap<String, StateOperation>) {
for (key, operation) in changes {
match operation {
StateOperation::Set(value) => {
self.vars.insert(key.clone(), value.clone());
}
StateOperation::Add(amount) => match self.vars.get(key) {
Some(StateVar::I64(current)) => {
self.vars
.insert(key.clone(), StateVar::I64(current + amount));
}
Some(StateVar::F64(current)) => {
self.vars
.insert(key.clone(), StateVar::F64(current + amount));
}
_ => {}
},
StateOperation::Subtract(amount) => match self.vars.get(key) {
Some(StateVar::I64(current)) => {
self.vars
.insert(key.clone(), StateVar::I64(current - amount));
}
Some(StateVar::F64(current)) => {
self.vars
.insert(key.clone(), StateVar::F64(current - amount));
}
_ => {}
},
}
}
}
pub fn merge(&mut self, other: &State) {
for (key, value) in &other.vars {
self.vars.insert(key.clone(), value.clone());
}
}
}
pub struct StateBuilder {
vars: HashMap<String, StateVar>,
}
impl StateBuilder {
pub fn new() -> Self {
StateBuilder {
vars: HashMap::new(),
}
}
pub fn set<T: IntoStateVar>(mut self, key: &str, value: T) -> Self {
self.vars.insert(key.to_string(), value.into_state_var());
self
}
pub fn build(self) -> State {
State { vars: self.vars }
}
}
impl Default for StateBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum StateVar {
Bool(bool),
I64(i64),
F64(i64),
String(String),
}
impl fmt::Display for StateVar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StateVar::Bool(b) => write!(f, "{b}"),
StateVar::I64(i) => write!(f, "{i}"),
StateVar::F64(fp) => write!(f, "{:.3}", *fp as f64 / 1000.0),
StateVar::String(s) => write!(f, "{s}"),
}
}
}
impl StateVar {
pub fn from_f64(value: f64) -> Self {
StateVar::F64((value * 1000.0).round() as i64)
}
pub fn as_f64(&self) -> Option<f64> {
match self {
StateVar::F64(value) => Some(*value as f64 / 1000.0),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
StateVar::I64(value) => Some(*value),
_ => None,
}
}
pub fn as_i32(&self) -> Option<i32> {
match self {
StateVar::I64(value) => (*value).try_into().ok(),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
StateVar::Bool(value) => Some(*value),
_ => None,
}
}
pub fn as_string(&self) -> Option<&str> {
match self {
StateVar::String(value) => Some(value),
_ => None,
}
}
pub fn distance(&self, other: &StateVar) -> Result<u64, StateError> {
match (self, other) {
(StateVar::Bool(a), StateVar::Bool(b)) => Ok(if a == b { 0 } else { 1 }),
(StateVar::I64(a), StateVar::I64(b)) => Ok((*a - *b).unsigned_abs()),
(StateVar::F64(a), StateVar::F64(b)) => Ok((*a - *b).unsigned_abs()),
(StateVar::String(a), StateVar::String(b)) => Ok(if a == b { 0 } else { 1 }),
_ => Err(StateError::InvalidVarType {
var: "distance_calculation".to_string(),
expected: "matching types for distance calculation",
}),
}
}
}
impl From<bool> for StateVar {
fn from(value: bool) -> Self {
StateVar::Bool(value)
}
}
impl From<i64> for StateVar {
fn from(value: i64) -> Self {
StateVar::I64(value)
}
}
impl From<f64> for StateVar {
fn from(value: f64) -> Self {
StateVar::from_f64(value)
}
}
impl From<String> for StateVar {
fn from(value: String) -> Self {
StateVar::String(value)
}
}
impl From<&str> for StateVar {
fn from(value: &str) -> Self {
StateVar::String(value.to_string())
}
}
impl From<i32> for StateVar {
fn from(value: i32) -> Self {
StateVar::I64(value as i64)
}
}
impl From<i16> for StateVar {
fn from(value: i16) -> Self {
StateVar::I64(value as i64)
}
}
impl From<i8> for StateVar {
fn from(value: i8) -> Self {
StateVar::I64(value as i64)
}
}
pub trait IntoStateVar {
fn into_state_var(self) -> StateVar;
}
pub trait TryFromStateVar: Sized {
fn try_from_state_var(var: &StateVar, key: &str) -> Result<Self, StateError>;
}
impl TryFromStateVar for i32 {
fn try_from_state_var(var: &StateVar, key: &str) -> Result<Self, StateError> {
var.as_i32().ok_or_else(|| StateError::InvalidVarType {
var: key.to_string(),
expected: "i32",
})
}
}
impl TryFromStateVar for i64 {
fn try_from_state_var(var: &StateVar, key: &str) -> Result<Self, StateError> {
var.as_i64().ok_or_else(|| StateError::InvalidVarType {
var: key.to_string(),
expected: "i64",
})
}
}
impl TryFromStateVar for bool {
fn try_from_state_var(var: &StateVar, key: &str) -> Result<Self, StateError> {
var.as_bool().ok_or_else(|| StateError::InvalidVarType {
var: key.to_string(),
expected: "bool",
})
}
}
impl TryFromStateVar for f64 {
fn try_from_state_var(var: &StateVar, key: &str) -> Result<Self, StateError> {
var.as_f64().ok_or_else(|| StateError::InvalidVarType {
var: key.to_string(),
expected: "f64",
})
}
}
impl TryFromStateVar for String {
fn try_from_state_var(var: &StateVar, key: &str) -> Result<Self, StateError> {
var.as_string()
.map(|s| s.to_string())
.ok_or_else(|| StateError::InvalidVarType {
var: key.to_string(),
expected: "string",
})
}
}
impl IntoStateVar for bool {
fn into_state_var(self) -> StateVar {
StateVar::Bool(self)
}
}
impl IntoStateVar for i64 {
fn into_state_var(self) -> StateVar {
StateVar::I64(self)
}
}
impl IntoStateVar for i32 {
fn into_state_var(self) -> StateVar {
StateVar::I64(self as i64)
}
}
impl IntoStateVar for i16 {
fn into_state_var(self) -> StateVar {
StateVar::I64(self as i64)
}
}
impl IntoStateVar for i8 {
fn into_state_var(self) -> StateVar {
StateVar::I64(self as i64)
}
}
impl IntoStateVar for f64 {
fn into_state_var(self) -> StateVar {
StateVar::from_f64(self)
}
}
impl IntoStateVar for f32 {
fn into_state_var(self) -> StateVar {
StateVar::from_f64(self as f64)
}
}
impl IntoStateVar for String {
fn into_state_var(self) -> StateVar {
StateVar::String(self)
}
}
impl IntoStateVar for &str {
fn into_state_var(self) -> StateVar {
StateVar::String(self.to_string())
}
}
impl IntoStateVar for StateVar {
fn into_state_var(self) -> StateVar {
self
}
}
pub trait EnumStateVar: fmt::Display {}
impl<T> IntoStateVar for T
where
T: EnumStateVar,
{
fn into_state_var(self) -> StateVar {
StateVar::String(self.to_string())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum StateOperation {
Set(StateVar),
Add(i64),
Subtract(i64),
}
impl StateOperation {
pub fn set_i64(value: i64) -> Self {
StateOperation::Set(StateVar::I64(value))
}
pub fn add_i64(value: i64) -> Self {
StateOperation::Add(value)
}
pub fn subtract_i64(value: i64) -> Self {
StateOperation::Subtract(value)
}
pub fn set_f64(value: f64) -> Self {
StateOperation::Set(StateVar::from_f64(value))
}
pub fn add_f64(value: f64) -> Self {
StateOperation::Add((value * 1000.0).round() as i64)
}
pub fn subtract_f64(value: f64) -> Self {
StateOperation::Subtract((value * 1000.0).round() as i64)
}
}