1use std::collections::HashSet;
4use std::ops::RangeInclusive;
5
6use petgraph::Direction;
7use petgraph::stable_graph::NodeIndex;
8use petgraph::visit::EdgeRef;
9
10use crate::graph::Chronicle;
11use crate::model::*;
12
13impl Chronicle {
16 fn neighbors_by_edge<F>(&self, nx: NodeIndex, dir: Direction, pred: F) -> Vec<NodeIndex>
18 where
19 F: Fn(&Relationship) -> bool,
20 {
21 self.graph
22 .edges_directed(nx, dir)
23 .filter(|e| pred(e.weight()))
24 .map(|e| match dir {
25 Direction::Outgoing => e.target(),
26 Direction::Incoming => e.source(),
27 })
28 .collect()
29 }
30}
31
32impl Chronicle {
35 pub fn actor(&self, id: &str) -> Option<ActorQuery<'_>> {
39 let &nx = self.index.get(id)?;
40 match &self.graph[nx] {
41 Entity::Actor(_) => Some(ActorQuery { chronicle: self, nx }),
42 _ => None,
43 }
44 }
45
46 pub fn event(&self, id: &str) -> Option<EventQuery<'_>> {
50 let &nx = self.index.get(id)?;
51 match &self.graph[nx] {
52 Entity::Event(_) => Some(EventQuery { chronicle: self, nx }),
53 _ => None,
54 }
55 }
56
57 pub fn place(&self, id: &str) -> Option<PlaceQuery<'_>> {
61 let &nx = self.index.get(id)?;
62 match &self.graph[nx] {
63 Entity::Place(_) => Some(PlaceQuery { chronicle: self, nx }),
64 _ => None,
65 }
66 }
67
68 pub fn concept(&self, id: &str) -> Option<ConceptQuery<'_>> {
72 let &nx = self.index.get(id)?;
73 match &self.graph[nx] {
74 Entity::Concept(_) => Some(ConceptQuery { chronicle: self, nx }),
75 _ => None,
76 }
77 }
78
79 pub fn mentions(&self, entity_id: &str) -> Vec<&Account> {
81 let Some(&target_nx) = self.index.get(entity_id) else { return vec![] };
82 let mut seen = HashSet::new();
83 self.neighbors_by_edge(target_nx, Direction::Incoming, |r| {
84 matches!(r, Relationship::Mentions)
85 })
86 .iter()
87 .filter(|&&nx| seen.insert(nx))
88 .filter_map(|&nx| match &self.graph[nx] {
89 Entity::Account(a) => Some(a),
90 _ => None,
91 })
92 .collect()
93 }
94
95 pub fn accounts_of(&self, event_id: &str) -> Vec<&Account> {
97 let Some(&target_nx) = self.index.get(event_id) else { return vec![] };
98 let mut seen = HashSet::new();
99 self.neighbors_by_edge(target_nx, Direction::Incoming, |r| {
100 matches!(r, Relationship::AccountOf)
101 })
102 .iter()
103 .filter(|&&nx| seen.insert(nx))
104 .filter_map(|&nx| match &self.graph[nx] {
105 Entity::Account(a) => Some(a),
106 _ => None,
107 })
108 .collect()
109 }
110
111 pub fn accounts_by(&self, source_id: &str) -> Vec<&Account> {
113 let Some(&source_nx) = self.index.get(source_id) else { return vec![] };
114 let mut seen = HashSet::new();
115 self.neighbors_by_edge(source_nx, Direction::Incoming, |r| {
116 matches!(r, Relationship::AuthoredBy)
117 })
118 .iter()
119 .filter(|&&nx| seen.insert(nx))
120 .filter_map(|&nx| match &self.graph[nx] {
121 Entity::Account(a) => Some(a),
122 _ => None,
123 })
124 .collect()
125 }
126}
127
128pub struct ActorQuery<'a> {
135 chronicle: &'a Chronicle,
136 nx: NodeIndex,
137}
138
139impl<'a> ActorQuery<'a> {
140 pub fn data(&self) -> &'a Actor {
142 match &self.chronicle.graph[self.nx] {
143 Entity::Actor(a) => a,
144 _ => unreachable!(),
145 }
146 }
147
148 pub fn events(&self) -> Vec<&'a Event> {
150 self.chronicle
151 .neighbors_by_edge(self.nx, Direction::Incoming, |r| {
152 matches!(r, Relationship::ParticipatedIn { .. })
153 })
154 .iter()
155 .filter_map(|&nx| match &self.chronicle.graph[nx] {
156 Entity::Event(e) => Some(e),
157 _ => None,
158 })
159 .collect()
160 }
161
162 pub fn events_during(&self, range: RangeInclusive<i32>) -> Vec<&'a Event> {
164 self.events()
165 .into_iter()
166 .filter(|e| e.time_span.start <= *range.end() && e.time_span.end >= *range.start())
167 .collect()
168 }
169
170 pub fn events_at(&self, place_id: &str) -> Vec<&'a Event> {
172 self.events()
173 .into_iter()
174 .filter(|e| e.location.as_deref() == Some(place_id))
175 .collect()
176 }
177
178 pub fn interactions(&self) -> InteractionResult<'a> {
180 let mut actors = Vec::new();
181 for event in self.events() {
182 for p in &event.participants {
183 if p.actor != self.data().id
184 && let Some(q) = self.chronicle.actor(&p.actor)
185 && !actors.iter().any(|a: &&Actor| a.id == q.data().id)
186 {
187 actors.push(q.data());
188 }
189 }
190 }
191 InteractionResult { actors }
192 }
193
194 pub fn status_at(&self, year: i32) -> Status {
196 status_at_impl(self.chronicle, &self.data().id, year)
197 }
198}
199
200pub struct InteractionResult<'a> {
205 actors: Vec<&'a Actor>,
206}
207
208impl<'a> InteractionResult<'a> {
209 pub fn all(&self) -> &[&'a Actor] {
211 &self.actors
212 }
213
214 pub fn people(&self) -> Vec<&'a Actor> {
216 self.actors.iter().filter(|a| a.actor_type == ActorType::Character).copied().collect()
217 }
218
219 pub fn factions(&self) -> Vec<&'a Actor> {
221 self.actors.iter().filter(|a| a.actor_type == ActorType::Faction).copied().collect()
222 }
223}
224
225pub struct EventQuery<'a> {
232 chronicle: &'a Chronicle,
233 nx: NodeIndex,
234}
235
236impl<'a> EventQuery<'a> {
237 pub fn data(&self) -> &'a Event {
239 match &self.chronicle.graph[self.nx] {
240 Entity::Event(e) => e,
241 _ => unreachable!(),
242 }
243 }
244
245 pub fn participants(&self) -> Vec<&'a Participant> {
247 self.data().participants.iter().collect()
248 }
249
250 pub fn participants_by_role(&self, role: Role) -> Vec<&'a Participant> {
252 self.data().participants.iter().filter(|p| p.role == role).collect()
253 }
254
255 pub fn location(&self) -> Option<&'a Place> {
257 let loc_id = self.data().location.as_ref()?;
258 let &nx = self.chronicle.index.get(loc_id.as_str())?;
259 match &self.chronicle.graph[nx] {
260 Entity::Place(p) => Some(p),
261 _ => None,
262 }
263 }
264
265 pub fn caused_by(&self) -> Vec<&'a Event> {
267 self.data()
268 .caused_by
269 .iter()
270 .filter_map(|id| {
271 let &nx = self.chronicle.index.get(id.as_str())?;
272 match &self.chronicle.graph[nx] {
273 Entity::Event(e) => Some(e),
274 _ => None,
275 }
276 })
277 .collect()
278 }
279
280 pub fn causal_chain(&self) -> Vec<&'a Event> {
282 let mut chain = Vec::new();
283 let mut queue = std::collections::VecDeque::new();
284 let mut visited = HashSet::new();
285
286 for cause in &self.data().caused_by {
287 if let Some(&nx) = self.chronicle.index.get(cause.as_str()) {
288 queue.push_back(nx);
289 }
290 }
291
292 while let Some(nx) = queue.pop_front() {
293 if !visited.insert(nx) {
294 continue;
295 }
296 if let Entity::Event(e) = &self.chronicle.graph[nx] {
297 chain.push(e);
298 for cause_id in &e.caused_by {
299 if let Some(&cause_nx) = self.chronicle.index.get(cause_id.as_str()) {
300 queue.push_back(cause_nx);
301 }
302 }
303 }
304 }
305 chain
306 }
307
308 pub fn consequences(&self) -> Vec<&'a Event> {
310 self.chronicle
311 .neighbors_by_edge(self.nx, Direction::Incoming, |r| {
312 matches!(r, Relationship::CausedBy)
313 })
314 .iter()
315 .filter_map(|&nx| match &self.chronicle.graph[nx] {
316 Entity::Event(e) => Some(e),
317 _ => None,
318 })
319 .collect()
320 }
321
322 pub fn state_changes(&self) -> &[EventStateChange] {
324 &self.data().state_changes
325 }
326}
327
328pub struct PlaceQuery<'a> {
335 chronicle: &'a Chronicle,
336 nx: NodeIndex,
337}
338
339impl<'a> PlaceQuery<'a> {
340 pub fn data(&self) -> &'a Place {
342 match &self.chronicle.graph[self.nx] {
343 Entity::Place(p) => p,
344 _ => unreachable!(),
345 }
346 }
347
348 pub fn events(&self) -> Vec<&'a Event> {
350 self.chronicle
351 .neighbors_by_edge(self.nx, Direction::Incoming, |r| {
352 matches!(r, Relationship::OccurredAt)
353 })
354 .iter()
355 .filter_map(|&nx| match &self.chronicle.graph[nx] {
356 Entity::Event(e) => Some(e),
357 _ => None,
358 })
359 .collect()
360 }
361
362 pub fn events_during(&self, range: RangeInclusive<i32>) -> Vec<&'a Event> {
364 self.events()
365 .into_iter()
366 .filter(|e| e.time_span.start <= *range.end() && e.time_span.end >= *range.start())
367 .collect()
368 }
369
370 pub fn actors_present_at(&self, year: i32) -> Vec<&'a Actor> {
372 let mut actors = Vec::new();
373 for event in self.events() {
374 if event.time_span.start <= year && event.time_span.end >= year {
375 for p in &event.participants {
376 if let Some(q) = self.chronicle.actor(&p.actor)
377 && !actors.iter().any(|a: &&Actor| a.id == q.data().id)
378 {
379 actors.push(q.data());
380 }
381 }
382 }
383 }
384 actors
385 }
386
387 pub fn status_at(&self, year: i32) -> Status {
389 status_at_impl(self.chronicle, &self.data().id, year)
390 }
391}
392
393pub struct ConceptQuery<'a> {
399 chronicle: &'a Chronicle,
400 nx: NodeIndex,
401}
402
403impl<'a> ConceptQuery<'a> {
404 pub fn data(&self) -> &'a Concept {
406 match &self.chronicle.graph[self.nx] {
407 Entity::Concept(c) => c,
408 _ => unreachable!(),
409 }
410 }
411
412 pub fn origin_event(&self) -> Option<&'a Event> {
414 let origin_id = self.data().origin_event.as_ref()?;
415 let &nx = self.chronicle.index.get(origin_id.as_str())?;
416 match &self.chronicle.graph[nx] {
417 Entity::Event(e) => Some(e),
418 _ => None,
419 }
420 }
421}
422
423fn status_at_impl(chronicle: &Chronicle, entity_id: &str, year: i32) -> Status {
428 let mut changes: Vec<(i32, &Status)> = Vec::new();
429
430 for nx in chronicle.graph.node_indices() {
431 if let Entity::Event(event) = &chronicle.graph[nx]
432 && event.time_span.end <= year
433 {
434 for sc in &event.state_changes {
435 if sc.entity == entity_id
436 && let StateChange::StatusChange(s) = &sc.change
437 {
438 changes.push((event.time_span.end, s));
439 }
440 }
441 }
442 }
443
444 changes.sort_by_key(|(time, _)| *time);
445 changes.last().map(|(_, s)| (*s).clone()).unwrap_or(Status::Active)
446}