1pub mod battle;
5pub mod support;
6
7use crate::error::{Error, Result};
8use crate::player::PlayerId;
9use crate::round::RoundId;
10use battle::BattleReport;
11use itertools::Itertools;
12use jiff::Zoned;
13use nil_util::iter::IterExt;
14use nil_util::vec::VecExt;
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::sync::Arc;
18use support::SupportReport;
19use uuid::Uuid;
20
21pub trait Report {
22 fn id(&self) -> ReportId;
23 fn round(&self) -> RoundId;
24 fn time(&self) -> &Zoned;
25}
26
27#[derive(Clone, Debug, Default, Deserialize, Serialize)]
28pub struct ReportManager {
29 reports: HashMap<ReportId, ReportKind>,
30 players: HashMap<PlayerId, Vec<ReportId>>,
31}
32
33impl ReportManager {
34 pub(crate) fn manage<I>(&mut self, report: ReportKind, players: I)
35 where
36 I: IntoIterator<Item = PlayerId>,
37 {
38 let id = report.as_dyn().id();
39 let players = players.into_iter().unique().collect_vec();
40
41 if !players.is_empty() {
42 for player in players {
43 self
44 .players
45 .entry(player)
46 .or_default()
47 .push(id);
48 }
49
50 debug_assert!(!self.reports.contains_key(&id));
51 self.reports.insert(id, report);
52 }
53 }
54
55 pub fn report(&self, id: ReportId) -> Result<&ReportKind> {
56 self
57 .reports
58 .get(&id)
59 .ok_or(Error::ReportNotFound(id))
60 }
61
62 pub fn reports_by<F>(&self, f: F) -> impl Iterator<Item = &ReportKind>
63 where
64 F: Fn((ReportId, &ReportKind)) -> bool,
65 {
66 self
67 .reports
68 .iter()
69 .filter(move |(id, report)| f((**id, report)))
70 .map(|(_, report)| report)
71 }
72
73 pub fn reports_of(&self, player: &PlayerId) -> impl Iterator<Item = ReportId> {
74 self
75 .players
76 .get(player)
77 .map(Vec::as_slice)
78 .unwrap_or_default()
79 .iter()
80 .copied()
81 }
82
83 pub(crate) fn forward(&mut self, id: ReportId, recipient: PlayerId) -> bool {
85 self.reports.contains_key(&id)
86 && self
87 .players
88 .entry(recipient)
89 .or_default()
90 .push_unique(id)
91 .is_none()
92 }
93
94 pub(crate) fn remove_of(&mut self, id: ReportId, player: &PlayerId) {
96 if let Some(reports) = self.players.get_mut(player) {
97 reports.retain(|report| *report != id);
98 }
99 }
100
101 pub(crate) fn prune(&mut self) {
103 let reports = self
104 .players
105 .values()
106 .flat_map(|ids| ids.iter().copied())
107 .collect_set();
108
109 self
110 .reports
111 .retain(|id, _| reports.contains(id));
112 }
113}
114
115#[derive(Clone, Debug, Deserialize, Serialize)]
116#[serde(tag = "kind", rename_all = "kebab-case")]
117#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
118#[remain::sorted]
119pub enum ReportKind {
120 Battle { report: Arc<BattleReport> },
121 Support { report: Arc<SupportReport> },
122}
123
124impl ReportKind {
125 pub fn as_dyn(&self) -> &dyn Report {
126 match self {
127 Self::Battle { report } => report.as_ref(),
128 Self::Support { report } => report.as_ref(),
129 }
130 }
131
132 #[inline]
133 pub fn id(&self) -> ReportId {
134 self.as_dyn().id()
135 }
136}
137
138#[must_use]
139#[derive(
140 Clone,
141 Copy,
142 Debug,
143 derive_more::Display,
144 PartialEq,
145 Eq,
146 PartialOrd,
147 Ord,
148 Hash,
149 Deserialize,
150 Serialize,
151)]
152#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
153pub struct ReportId(Uuid);
154
155impl ReportId {
156 #[inline]
157 pub fn new() -> Self {
158 Self(Uuid::now_v7())
159 }
160}
161
162impl Default for ReportId {
163 fn default() -> Self {
164 Self::new()
165 }
166}