use crate::collidable::Collidable;
use crate::spam_score::SpamScore;
use crate::user_value::AnyUserValue;
use crate::UserValue;
use chrono::Local;
use chrono::NaiveDate;
use chrono::NaiveDateTime;
use itertools::*;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Hash)]
pub struct User {
fid: usize,
reaction_times: Option<Vec<NaiveDateTime>>,
latest_reaction_time_update_date: Option<NaiveDateTime>,
latest_cast_record_check_date: Option<NaiveDate>,
user_values: Option<Vec<(AnyUserValue, NaiveDateTime)>>,
}
impl User {
pub fn has<T: UserValue>(&self) -> bool {
if let Some(user_values) = &self.user_values {
user_values
.iter()
.any(|(val, _)| T::from_any_user_value_ref(val).is_some())
} else {
false
}
}
pub fn try_add_user_value<T>(&mut self, value: T) -> Result<(), UserValueError>
where
T: UserValue + Collidable,
{
if self
.user_values_of_kind::<T>()
.iter()
.all(|x| !Collidable::is_collision(*x, &value))
{
self.add_user_value(value)
} else {
Err(UserValueError::CollisionError)
}
}
pub fn add_user_value<T>(&mut self, value: T) -> Result<(), UserValueError>
where
T: UserValue,
{
if self.update_time_if_duplicate(&value).is_some() {
return Ok(());
};
let any_user_value = value.into_any_user_value();
if let Some(value_vec) = &mut self.user_values {
value_vec.push((any_user_value, Self::now()))
} else {
self.user_values = Some(vec![(any_user_value, Self::now())]);
}
Ok(())
}
pub fn all_user_values(&self) -> &Option<Vec<(AnyUserValue, NaiveDateTime)>> {
&self.user_values
}
pub fn user_values_of_kind<T>(&self) -> Vec<&T>
where
T: UserValue,
{
if let Some(user_values) = &self.user_values {
user_values
.iter()
.flat_map(|(user_value, _)| user_value.specify_ref::<T>())
.collect_vec()
} else {
Vec::new()
}
}
fn now() -> NaiveDateTime {
Local::now().naive_local()
}
fn update_time_if_duplicate<T>(&mut self, value: &T) -> Option<NaiveDateTime>
where
T: UserValue,
{
if let Some(user_values) = &mut self.user_values {
let duplicate_position = user_values
.iter()
.position(|prev| prev.0 == value.as_any_user_value())?;
let new_time = Self::now();
let old_time = std::mem::replace(&mut user_values[duplicate_position].1, new_time);
Some(old_time)
} else {
None
}
}
pub fn new_without_labels(fid: usize) -> Self {
Self {
fid,
latest_cast_record_check_date: None,
reaction_times: None,
latest_reaction_time_update_date: None,
user_values: None,
}
}
pub fn update_reaction_times(
&mut self,
reaction_times: Vec<NaiveDateTime>,
) -> Option<Vec<NaiveDateTime>> {
self.latest_reaction_time_update_date = Some(Local::now().naive_utc());
self.reaction_times.replace(reaction_times)
}
pub fn latest_reaction_time(&self) -> Option<&NaiveDateTime> {
if let Some(reaction_times) = &self.reaction_times {
Some(reaction_times.iter().max()?)
} else {
None
}
}
pub fn fid(&self) -> usize {
self.fid
}
pub fn latest_reaction_time_update_date(&self) -> Option<NaiveDateTime> {
self.latest_reaction_time_update_date
}
pub fn reaction_times(&self) -> &Option<Vec<NaiveDateTime>> {
&self.reaction_times
}
pub fn latest_cast_record_check_date(&self) -> Option<NaiveDate> {
self.latest_cast_record_check_date
}
}
#[derive(Error, Debug, PartialEq)]
pub enum UserValueError {
#[error(
"User Value collides with existing user value. A User cannot contain colliding user values"
)]
CollisionError,
}
#[derive(Error, Debug, PartialEq)]
pub enum UserError {
#[error("User {0} already has a spam record for date {1}. The existing spam score at the date is {} but a spamscore of {} is now trying to be set.", .fid, . date) ]
SpamScoreCollision {
fid: usize,
date: NaiveDate,
old_spam_score: SpamScore,
new_spam_score: SpamScore,
},
#[error("Trying to merge users with different fids. For merge_user to work both input users must have the same fid. provided fid_1: {} and provided fid_2 {}", .fid_1, .fid_2)]
DifferentFidMerge { fid_1: usize, fid_2: usize },
}
#[derive(Error, Debug, PartialEq)]
pub enum InvalidInputError {
#[error("SpamScore was {0}, not zero, one or two.", .label)]
SpamScoreError { label: usize },
#[error("Timestamp was {0}, which is invalid.", . timestamp)]
DateError { timestamp: usize },
}
#[cfg(test)]
pub mod tests {
use crate::spam_score::DatedSpamUpdate;
use super::*;
pub mod test_fid {
use super::*;
pub fn is_fid(user: &User, fid: usize) -> bool {
user.fid() == fid
}
}
pub fn create_user(fid: usize) -> User {
User::new_without_labels(fid)
}
pub fn valid_user_value_add<T: UserValue>(user: &mut User, value: T) {
user.add_user_value::<T>(value).unwrap()
}
#[test]
fn test_user_values_of_kind_is_none_on_none_user_values() {
let user = User::new_without_labels(1);
assert!(user.user_values_of_kind::<DatedSpamUpdate>().is_empty());
}
}