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 {
42 chronicle: self,
43 nx,
44 }),
45 _ => None,
46 }
47 }
48
49 pub fn event(&self, id: &str) -> Option<EventQuery<'_>> {
53 let &nx = self.index.get(id)?;
54 match &self.graph[nx] {
55 Entity::Event(_) => Some(EventQuery {
56 chronicle: self,
57 nx,
58 }),
59 _ => None,
60 }
61 }
62
63 pub fn place(&self, id: &str) -> Option<PlaceQuery<'_>> {
67 let &nx = self.index.get(id)?;
68 match &self.graph[nx] {
69 Entity::Place(_) => Some(PlaceQuery {
70 chronicle: self,
71 nx,
72 }),
73 _ => None,
74 }
75 }
76
77 pub fn concept(&self, id: &str) -> Option<ConceptQuery<'_>> {
81 let &nx = self.index.get(id)?;
82 match &self.graph[nx] {
83 Entity::Concept(_) => Some(ConceptQuery {
84 chronicle: self,
85 nx,
86 }),
87 _ => None,
88 }
89 }
90
91 pub fn mentions(&self, entity_id: &str) -> Vec<&Account> {
93 let Some(&target_nx) = self.index.get(entity_id) else {
94 return vec![];
95 };
96 let mut seen = HashSet::new();
97 self.neighbors_by_edge(target_nx, Direction::Incoming, |r| {
98 matches!(r, Relationship::Mentions)
99 })
100 .iter()
101 .filter(|&&nx| seen.insert(nx))
102 .filter_map(|&nx| match &self.graph[nx] {
103 Entity::Account(a) => Some(a),
104 _ => None,
105 })
106 .collect()
107 }
108
109 pub fn accounts_of(&self, event_id: &str) -> Vec<&Account> {
111 let Some(&target_nx) = self.index.get(event_id) else {
112 return vec![];
113 };
114 let mut seen = HashSet::new();
115 self.neighbors_by_edge(target_nx, Direction::Incoming, |r| {
116 matches!(r, Relationship::AccountOf)
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 pub fn accounts_by(&self, source_id: &str) -> Vec<&Account> {
129 let Some(&source_nx) = self.index.get(source_id) else {
130 return vec![];
131 };
132 let mut seen = HashSet::new();
133 self.neighbors_by_edge(source_nx, Direction::Incoming, |r| {
134 matches!(r, Relationship::AuthoredBy)
135 })
136 .iter()
137 .filter(|&&nx| seen.insert(nx))
138 .filter_map(|&nx| match &self.graph[nx] {
139 Entity::Account(a) => Some(a),
140 _ => None,
141 })
142 .collect()
143 }
144}
145
146pub struct ActorQuery<'a> {
153 chronicle: &'a Chronicle,
154 nx: NodeIndex,
155}
156
157impl<'a> ActorQuery<'a> {
158 pub fn data(&self) -> &'a Actor {
160 match &self.chronicle.graph[self.nx] {
161 Entity::Actor(a) => a,
162 _ => unreachable!(),
163 }
164 }
165
166 pub fn events(&self) -> Vec<&'a Event> {
168 self.chronicle
169 .neighbors_by_edge(self.nx, Direction::Incoming, |r| {
170 matches!(r, Relationship::ParticipatedIn { .. })
171 })
172 .iter()
173 .filter_map(|&nx| match &self.chronicle.graph[nx] {
174 Entity::Event(e) => Some(e),
175 _ => None,
176 })
177 .collect()
178 }
179
180 pub fn events_during(&self, range: RangeInclusive<i32>) -> Vec<&'a Event> {
182 self.events()
183 .into_iter()
184 .filter(|e| e.time_span.start <= *range.end() && e.time_span.end >= *range.start())
185 .collect()
186 }
187
188 pub fn events_at(&self, place_id: &str) -> Vec<&'a Event> {
190 self.events()
191 .into_iter()
192 .filter(|e| e.location.as_deref() == Some(place_id))
193 .collect()
194 }
195
196 pub fn interactions(&self) -> InteractionResult<'a> {
198 let mut actors = Vec::new();
199 for event in self.events() {
200 for p in &event.participants {
201 if p.actor != self.data().id
202 && let Some(q) = self.chronicle.actor(&p.actor)
203 && !actors.iter().any(|a: &&Actor| a.id == q.data().id)
204 {
205 actors.push(q.data());
206 }
207 }
208 }
209 InteractionResult { actors }
210 }
211
212 pub fn status_at(&self, year: i32) -> Status {
214 status_at_impl(self.chronicle, &self.data().id, year)
215 }
216}
217
218pub struct InteractionResult<'a> {
223 actors: Vec<&'a Actor>,
224}
225
226impl<'a> InteractionResult<'a> {
227 pub fn all(&self) -> &[&'a Actor] {
229 &self.actors
230 }
231
232 pub fn people(&self) -> Vec<&'a Actor> {
234 self.actors
235 .iter()
236 .filter(|a| a.actor_type == ActorType::Character)
237 .copied()
238 .collect()
239 }
240
241 pub fn factions(&self) -> Vec<&'a Actor> {
243 self.actors
244 .iter()
245 .filter(|a| a.actor_type == ActorType::Faction)
246 .copied()
247 .collect()
248 }
249}
250
251pub struct EventQuery<'a> {
258 chronicle: &'a Chronicle,
259 nx: NodeIndex,
260}
261
262impl<'a> EventQuery<'a> {
263 pub fn data(&self) -> &'a Event {
265 match &self.chronicle.graph[self.nx] {
266 Entity::Event(e) => e,
267 _ => unreachable!(),
268 }
269 }
270
271 pub fn participants(&self) -> Vec<&'a Participant> {
273 self.data().participants.iter().collect()
274 }
275
276 pub fn participants_by_role(&self, role: Role) -> Vec<&'a Participant> {
278 self.data()
279 .participants
280 .iter()
281 .filter(|p| p.role == role)
282 .collect()
283 }
284
285 pub fn location(&self) -> Option<&'a Place> {
287 let loc_id = self.data().location.as_ref()?;
288 let &nx = self.chronicle.index.get(loc_id.as_str())?;
289 match &self.chronicle.graph[nx] {
290 Entity::Place(p) => Some(p),
291 _ => None,
292 }
293 }
294
295 pub fn caused_by(&self) -> Vec<&'a Event> {
297 self.data()
298 .caused_by
299 .iter()
300 .filter_map(|id| {
301 let &nx = self.chronicle.index.get(id.as_str())?;
302 match &self.chronicle.graph[nx] {
303 Entity::Event(e) => Some(e),
304 _ => None,
305 }
306 })
307 .collect()
308 }
309
310 pub fn causal_chain(&self) -> Vec<&'a Event> {
312 let mut chain = Vec::new();
313 let mut queue = std::collections::VecDeque::new();
314 let mut visited = HashSet::new();
315
316 for cause in &self.data().caused_by {
317 if let Some(&nx) = self.chronicle.index.get(cause.as_str()) {
318 queue.push_back(nx);
319 }
320 }
321
322 while let Some(nx) = queue.pop_front() {
323 if !visited.insert(nx) {
324 continue;
325 }
326 if let Entity::Event(e) = &self.chronicle.graph[nx] {
327 chain.push(e);
328 for cause_id in &e.caused_by {
329 if let Some(&cause_nx) = self.chronicle.index.get(cause_id.as_str()) {
330 queue.push_back(cause_nx);
331 }
332 }
333 }
334 }
335 chain
336 }
337
338 pub fn consequences(&self) -> Vec<&'a Event> {
340 self.chronicle
341 .neighbors_by_edge(self.nx, Direction::Incoming, |r| {
342 matches!(r, Relationship::CausedBy)
343 })
344 .iter()
345 .filter_map(|&nx| match &self.chronicle.graph[nx] {
346 Entity::Event(e) => Some(e),
347 _ => None,
348 })
349 .collect()
350 }
351
352 pub fn state_changes(&self) -> &[EventStateChange] {
354 &self.data().state_changes
355 }
356}
357
358pub struct PlaceQuery<'a> {
365 chronicle: &'a Chronicle,
366 nx: NodeIndex,
367}
368
369impl<'a> PlaceQuery<'a> {
370 pub fn data(&self) -> &'a Place {
372 match &self.chronicle.graph[self.nx] {
373 Entity::Place(p) => p,
374 _ => unreachable!(),
375 }
376 }
377
378 pub fn events(&self) -> Vec<&'a Event> {
380 self.chronicle
381 .neighbors_by_edge(self.nx, Direction::Incoming, |r| {
382 matches!(r, Relationship::OccurredAt)
383 })
384 .iter()
385 .filter_map(|&nx| match &self.chronicle.graph[nx] {
386 Entity::Event(e) => Some(e),
387 _ => None,
388 })
389 .collect()
390 }
391
392 pub fn events_during(&self, range: RangeInclusive<i32>) -> Vec<&'a Event> {
394 self.events()
395 .into_iter()
396 .filter(|e| e.time_span.start <= *range.end() && e.time_span.end >= *range.start())
397 .collect()
398 }
399
400 pub fn actors_present_at(&self, year: i32) -> Vec<&'a Actor> {
402 let mut actors = Vec::new();
403 for event in self.events() {
404 if event.time_span.start <= year && event.time_span.end >= year {
405 for p in &event.participants {
406 if let Some(q) = self.chronicle.actor(&p.actor)
407 && !actors.iter().any(|a: &&Actor| a.id == q.data().id)
408 {
409 actors.push(q.data());
410 }
411 }
412 }
413 }
414 actors
415 }
416
417 pub fn status_at(&self, year: i32) -> Status {
419 status_at_impl(self.chronicle, &self.data().id, year)
420 }
421}
422
423pub struct ConceptQuery<'a> {
429 chronicle: &'a Chronicle,
430 nx: NodeIndex,
431}
432
433impl<'a> ConceptQuery<'a> {
434 pub fn data(&self) -> &'a Concept {
436 match &self.chronicle.graph[self.nx] {
437 Entity::Concept(c) => c,
438 _ => unreachable!(),
439 }
440 }
441
442 pub fn origin_event(&self) -> Option<&'a Event> {
444 let origin_id = self.data().origin_event.as_ref()?;
445 let &nx = self.chronicle.index.get(origin_id.as_str())?;
446 match &self.chronicle.graph[nx] {
447 Entity::Event(e) => Some(e),
448 _ => None,
449 }
450 }
451}
452
453fn status_at_impl(chronicle: &Chronicle, entity_id: &str, year: i32) -> Status {
458 let mut changes: Vec<(i32, &Status)> = Vec::new();
459
460 for nx in chronicle.graph.node_indices() {
461 if let Entity::Event(event) = &chronicle.graph[nx]
462 && event.time_span.end <= year
463 {
464 for sc in &event.state_changes {
465 if sc.entity == entity_id
466 && let StateChange::StatusChange(s) = &sc.change
467 {
468 changes.push((event.time_span.end, s));
469 }
470 }
471 }
472 }
473
474 changes.sort_by_key(|(time, _)| *time);
475 changes
476 .last()
477 .map(|(_, s)| (*s).clone())
478 .unwrap_or(Status::Active)
479}