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::vec::VecExt;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::sync::Arc;
17use support::SupportReport;
18use uuid::Uuid;
19
20pub trait Report {
21 fn id(&self) -> ReportId;
22 fn round(&self) -> RoundId;
23 fn time(&self) -> &Zoned;
24}
25
26#[derive(Clone, Debug, Default, Deserialize, Serialize)]
27pub struct ReportManager {
28 reports: HashMap<ReportId, ReportKind>,
29 players: HashMap<PlayerId, Vec<ReportId>>,
30}
31
32impl ReportManager {
33 pub(crate) fn manage<I>(&mut self, report: ReportKind, players: I)
34 where
35 I: IntoIterator<Item = PlayerId>,
36 {
37 let id = report.as_dyn().id();
38 let players = players.into_iter().unique().collect_vec();
39
40 if !players.is_empty() {
41 for player in players {
42 self
43 .players
44 .entry(player)
45 .or_default()
46 .push(id);
47 }
48
49 debug_assert!(!self.reports.contains_key(&id));
50 self.reports.insert(id, report);
51 }
52 }
53
54 pub fn report(&self, id: ReportId) -> Result<&ReportKind> {
55 self
56 .reports
57 .get(&id)
58 .ok_or(Error::ReportNotFound(id))
59 }
60
61 pub fn reports_by<F>(&self, f: F) -> impl Iterator<Item = &ReportKind>
62 where
63 F: Fn((ReportId, &ReportKind)) -> bool,
64 {
65 self
66 .reports
67 .iter()
68 .filter(move |(id, report)| f((**id, report)))
69 .map(|(_, report)| report)
70 }
71
72 pub fn reports_of(&self, player: &PlayerId) -> impl Iterator<Item = ReportId> {
73 self
74 .players
75 .get(player)
76 .map(Vec::as_slice)
77 .unwrap_or_default()
78 .iter()
79 .copied()
80 }
81
82 pub(crate) fn forward(&mut self, id: ReportId, recipient: PlayerId) -> bool {
83 self.reports.contains_key(&id)
84 && self
85 .players
86 .entry(recipient)
87 .or_default()
88 .push_unique(id)
89 .is_none()
90 }
91}
92
93#[derive(Clone, Debug, Deserialize, Serialize)]
94#[serde(tag = "kind", rename_all = "kebab-case")]
95#[remain::sorted]
96pub enum ReportKind {
97 Battle { report: Arc<BattleReport> },
98 Support { report: Arc<SupportReport> },
99}
100
101impl ReportKind {
102 pub fn as_dyn(&self) -> &dyn Report {
103 match self {
104 Self::Battle { report } => report.as_ref(),
105 Self::Support { report } => report.as_ref(),
106 }
107 }
108}
109
110#[must_use]
111#[derive(
112 Clone,
113 Copy,
114 Debug,
115 derive_more::Display,
116 PartialEq,
117 Eq,
118 PartialOrd,
119 Ord,
120 Hash,
121 Deserialize,
122 Serialize,
123)]
124pub struct ReportId(Uuid);
125
126impl ReportId {
127 #[inline]
128 pub fn new() -> Self {
129 Self(Uuid::now_v7())
130 }
131}
132
133impl Default for ReportId {
134 fn default() -> Self {
135 Self::new()
136 }
137}