use crate::enums::{AttributionType, EventPayload, EventType, HexacoPath};
use crate::event::event_spec::EventSpec;
use crate::types::{Duration, EntityId, EventId, MicrosystemId};
use uuid::Uuid;
fn generate_event_id() -> EventId {
let uuid = Uuid::new_v4();
EventId::new(format!("evt_{uuid}")).unwrap()
}
#[derive(Debug, Clone, PartialEq)]
pub struct Event {
id: EventId,
event_type: EventType,
source: Option<EntityId>,
target: Option<EntityId>,
severity: f64,
payload: EventPayload,
trust_attribution: Option<AttributionType>,
timestamp: Duration,
microsystem_context: Option<MicrosystemId>,
base_shifts: Vec<(HexacoPath, f32)>,
custom_spec: Option<EventSpec>,
}
impl Event {
pub(crate) fn new(event_type: EventType) -> Self {
Event {
id: generate_event_id(),
event_type,
source: None,
target: None,
severity: 0.5,
payload: EventPayload::Empty,
trust_attribution: None,
timestamp: Duration::zero(),
microsystem_context: None,
base_shifts: Vec::new(),
custom_spec: None,
}
}
#[must_use]
pub fn with_id(id: EventId, event_type: EventType) -> Self {
Event {
id,
event_type,
source: None,
target: None,
severity: 0.5,
payload: EventPayload::Empty,
trust_attribution: None,
timestamp: Duration::zero(),
microsystem_context: None,
base_shifts: Vec::new(),
custom_spec: None,
}
}
#[must_use]
pub fn custom(spec: EventSpec) -> Self {
Event {
id: generate_event_id(),
event_type: EventType::Custom,
source: None,
target: None,
severity: 1.0,
payload: EventPayload::Empty,
trust_attribution: None,
timestamp: Duration::zero(),
microsystem_context: None,
base_shifts: Vec::new(),
custom_spec: Some(spec),
}
}
#[must_use]
pub fn id(&self) -> &EventId {
&self.id
}
#[must_use]
pub fn event_type(&self) -> EventType {
self.event_type
}
#[must_use]
pub fn spec(&self) -> EventSpec {
self.custom_spec.unwrap_or_else(|| self.event_type.spec())
}
#[must_use]
pub fn source(&self) -> Option<&EntityId> {
self.source.as_ref()
}
#[must_use]
pub fn target(&self) -> Option<&EntityId> {
self.target.as_ref()
}
#[must_use]
pub fn severity(&self) -> f64 {
self.severity
}
#[must_use]
pub fn payload(&self) -> &EventPayload {
&self.payload
}
#[must_use]
pub fn trust_attribution(&self) -> Option<AttributionType> {
self.trust_attribution
}
#[must_use]
pub fn has_payload_data(&self) -> bool {
!matches!(self.payload, EventPayload::Empty)
}
#[must_use]
pub fn timestamp(&self) -> Duration {
self.timestamp
}
#[must_use]
pub fn microsystem_context(&self) -> Option<&MicrosystemId> {
self.microsystem_context.as_ref()
}
#[must_use]
pub fn base_shifts(&self) -> &[(HexacoPath, f32)] {
&self.base_shifts
}
#[must_use]
pub fn has_base_shifts(&self) -> bool {
!self.base_shifts.is_empty()
}
#[allow(dead_code)]
pub(crate) fn set_id(&mut self, id: EventId) {
self.id = id;
}
pub(crate) fn set_source(&mut self, source: Option<EntityId>) {
self.source = source;
}
pub(crate) fn set_target(&mut self, target: Option<EntityId>) {
self.target = target;
}
pub(crate) fn set_severity(&mut self, severity: f64) {
self.severity = severity.clamp(0.0, 1.0);
}
pub(crate) fn set_payload(&mut self, payload: EventPayload) {
self.payload = payload;
}
pub(crate) fn set_trust_attribution(&mut self, attribution: Option<AttributionType>) {
self.trust_attribution = attribution;
}
pub(crate) fn set_timestamp(&mut self, timestamp: Duration) {
self.timestamp = timestamp;
}
pub(crate) fn set_microsystem_context(&mut self, context: Option<MicrosystemId>) {
self.microsystem_context = context;
}
pub(crate) fn set_base_shifts(&mut self, shifts: Vec<(HexacoPath, f32)>) {
self.base_shifts = shifts;
}
pub(crate) fn set_custom_spec(&mut self, spec: EventSpec) {
self.custom_spec = Some(spec);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::event_spec::{ChronicFlags, EventImpact, PermanenceValues};
#[test]
fn event_creation_with_type() {
let event = Event::new(EventType::EndRelationshipRomantic);
assert_eq!(event.event_type(), EventType::EndRelationshipRomantic);
}
#[test]
fn event_source_target_specification() {
let mut event = Event::new(EventType::ExperienceBetrayalTrust);
let source = EntityId::new("attacker").unwrap();
let target = EntityId::new("victim").unwrap();
event.set_source(Some(source.clone()));
event.set_target(Some(target.clone()));
assert_eq!(event.source(), Some(&source));
assert_eq!(event.target(), Some(&target));
}
#[test]
fn event_id_auto_generated() {
let event1 = Event::new(EventType::AchieveGoalMajor);
let event2 = Event::new(EventType::AchieveGoalMajor);
assert_ne!(event1.id(), event2.id());
assert!(event1.id().as_str().starts_with("evt_"));
}
#[test]
fn event_set_id_overrides_existing_id() {
let mut event = Event::new(EventType::AchieveGoalMajor);
let new_id = EventId::new("custom_event_override").unwrap();
event.set_id(new_id.clone());
assert_eq!(event.id(), &new_id);
}
#[test]
fn event_with_id_sets_specific_id() {
let id = EventId::new("custom_event").unwrap();
let event = Event::with_id(id.clone(), EventType::AchieveGoalMajor);
assert_eq!(event.id(), &id);
}
#[test]
fn event_severity_clamped() {
let mut event = Event::new(EventType::ExperienceCombatMilitary);
event.set_severity(1.5);
assert!((event.severity() - 1.0).abs() < f64::EPSILON);
event.set_severity(-0.5);
assert!(event.severity().abs() < f64::EPSILON);
}
#[test]
fn event_payload_access() {
let mut event = Event::new(EventType::ExperienceCombatMilitary);
let payload = EventPayload::Violence {
weapon: None,
injury_severity: 0.5,
};
event.set_payload(payload.clone());
assert_eq!(event.payload(), &payload);
assert!(event.has_payload_data());
}
#[test]
fn event_empty_payload() {
let event = Event::new(EventType::ExperienceCombatMilitary);
assert!(!event.has_payload_data());
assert_eq!(event.payload(), &EventPayload::Empty);
}
#[test]
fn event_timestamp_works() {
let mut event = Event::new(EventType::AchieveGoalMajor);
event.set_timestamp(Duration::days(100));
assert_eq!(event.timestamp().as_days(), 100);
}
#[test]
fn event_microsystem_context_works() {
let mut event = Event::new(EventType::AchieveGoalMajor);
let context = MicrosystemId::new("work_001").unwrap();
event.set_microsystem_context(Some(context.clone()));
assert_eq!(event.microsystem_context(), Some(&context));
}
#[test]
fn event_clone() {
let mut event = Event::new(EventType::ExperienceCombatMilitary);
event.set_severity(0.8);
let cloned = event.clone();
assert_eq!(event, cloned);
}
#[test]
fn event_debug_format() {
let event = Event::new(EventType::ExperienceCombatMilitary);
let debug = format!("{:?}", event);
assert!(debug.contains("Event"));
assert!(debug.contains("ExperienceCombatMilitary"));
}
#[test]
fn event_default_values() {
let event = Event::new(EventType::AchieveGoalMajor);
assert!(event.source().is_none());
assert!(event.target().is_none());
assert!((event.severity() - 0.5).abs() < f64::EPSILON);
assert_eq!(event.payload(), &EventPayload::Empty);
assert_eq!(event.timestamp().as_seconds(), 0);
assert!(event.microsystem_context().is_none());
}
#[test]
fn event_default_has_no_base_shifts() {
let event = Event::new(EventType::ExperienceCombatMilitary);
assert!(!event.has_base_shifts());
assert!(event.base_shifts().is_empty());
}
#[test]
fn event_with_base_shifts() {
let mut event = Event::new(EventType::ExperienceCombatMilitary);
event.set_base_shifts(vec![
(HexacoPath::Neuroticism, 0.25),
(HexacoPath::Agreeableness, -0.15),
]);
assert!(event.has_base_shifts());
assert_eq!(event.base_shifts().len(), 2);
assert_eq!(event.base_shifts()[0], (HexacoPath::Neuroticism, 0.25));
assert_eq!(event.base_shifts()[1], (HexacoPath::Agreeableness, -0.15));
}
#[test]
fn event_spec_returns_type_spec() {
let event = Event::new(EventType::EndRelationshipRomantic);
let spec = event.spec();
assert!(spec.impact.valence < 0.0);
}
#[test]
fn custom_event_uses_custom_spec() {
let custom_spec = EventSpec {
impact: EventImpact {
valence: 0.99,
..Default::default()
},
chronic: ChronicFlags::default(),
permanence: PermanenceValues::default(),
};
let event = Event::custom(custom_spec);
assert_eq!(event.event_type(), EventType::Custom);
let spec = event.spec();
assert!((spec.impact.valence - 0.99).abs() < f32::EPSILON);
}
#[test]
fn custom_event_has_default_severity_one() {
let custom_spec = EventSpec::default();
let event = Event::custom(custom_spec);
assert!((event.severity() - 1.0).abs() < f64::EPSILON);
}
}