use crate::actor::State;
use crate::message::Message;
use std::fmt;
use std::ops::Add;
use time::OffsetDateTime;
type OperatorResult<T> = Result<T, OperatorError>;
#[derive(Debug, Clone)]
pub struct OperatorError {
reason: String,
}
impl fmt::Display for OperatorError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "unsuccessful operation: {}", self.reason)
}
}
pub trait Operator<T: Add<Output = T>>: Sync + Send {
fn apply(state: &State<T>, idx: i32, value: T, datetime: OffsetDateTime) -> OperatorResult<T>;
}
pub struct GuageOperator {}
impl<T: Add<Output = T>> Operator<T> for GuageOperator {
fn apply(_: &State<T>, _: i32, value: T, _: OffsetDateTime) -> OperatorResult<T> {
Ok(value)
}
}
pub struct AccumOperator {}
impl<T: Add<Output = T> + Copy> Operator<T> for AccumOperator {
fn apply(state: &State<T>, idx: i32, value: T, _: OffsetDateTime) -> OperatorResult<T> {
state.get(&idx).map_or_else(
|| {
Err(OperatorError {
reason: String::from("idx invalid"),
})
},
|old_val| {
let new_val = *old_val + value;
Ok(new_val)
},
)
}
}
pub trait Gene<T: Add<Output = T>> {
fn apply_operators(
&self,
state: State<T>,
update: Message<T>,
) -> OperatorResult<State<T>>;
fn get_time_scope(&self) -> &TimeScope;
}
pub struct GuageAndAccumGene {
pub guage_first_idx: i32,
pub guage_slots: i32,
pub accumulator_first_idx: i32,
pub accumulator_slots: i32,
pub time_scope: TimeScope,
}
impl GuageAndAccumGene {
fn update_state_with_val<T: Add<Output = T> + Copy>(
&self,
in_val: T,
idx: i32,
mut state: State<T>,
datetime: OffsetDateTime,
) -> OperatorResult<State<T>> {
let new_val = if (self.guage_first_idx..self.guage_first_idx + self.guage_slots)
.contains(&idx)
{
GuageOperator::apply(&state, idx, in_val, datetime)?
} else if (self.accumulator_first_idx..self.accumulator_first_idx + self.accumulator_slots)
.contains(&idx)
{
AccumOperator::apply(&state, idx, in_val, datetime)?
} else {
return Err(OperatorError {
reason: format!("unsupported idx: {idx}"),
});
};
state.insert(idx, new_val);
Ok(state)
}
}
impl<T: Add<Output = T> + Copy> Gene<T> for GuageAndAccumGene {
fn apply_operators(&self, mut state: State<T>, update: Message<T>) -> OperatorResult<State<T>> {
match update {
Message::Update {
path: _,
datetime,
values,
} => {
for &idx in values.keys() {
let in_val = values.get(&idx).ok_or_else(|| OperatorError {
reason: format!("unsupported idx: {idx}"),
})?;
state = self.update_state_with_val(*in_val, idx, state, datetime)?;
}
}
_ => {
return Err(OperatorError {
reason: "unsupported message type".to_string(),
})
}
};
Ok(state)
}
fn get_time_scope(&self) -> &TimeScope {
&self.time_scope
}
}
impl Default for GuageAndAccumGene {
fn default() -> Self {
Self {
guage_first_idx: 0,
guage_slots: 100,
accumulator_first_idx: 100,
accumulator_slots: 100,
time_scope: TimeScope::Forever,
}
}
}
#[derive(Debug, Clone)]
pub enum TimeScope {
Forever,
Year,
Month,
Day,
HalfDay,
QuarterDay,
Hour,
QuarterHour,
TenMinutes,
Minute,
}