1use crate::engine::{Mutation, Mutations, Queries, Query};
2use crate::id::Id;
3use crate::prop::Prop;
4use crate::recur::Recur;
5use crate::tag::{Sign, Tag};
6use chrono::prelude::*;
7use serde::{Deserialize, Serialize, Serializer};
8use std::cmp::Ordering;
9use std::collections::{HashMap, HashSet};
10use time::Duration;
11
12#[derive(Serialize, Deserialize, Debug)]
35pub struct Task {
36 created_at: DateTime<Utc>,
37 description: Option<String>,
38 done: Option<DateTime<Utc>>,
39 due: Option<DateTime<Utc>>,
40 id: Id,
41 recur: Option<Recur>,
42 snooze: Option<DateTime<Utc>>,
43 #[serde(serialize_with = "ordered_set")]
44 tags: HashSet<String>,
45 updated_at: DateTime<Utc>,
46 wait: Option<DateTime<Utc>>,
47}
48
49fn ordered_set<S>(value: &HashSet<String>, serializer: S) -> Result<S::Ok, S::Error>
50where
51 S: Serializer,
52{
53 let mut vec = value.iter().collect::<Vec<&String>>();
54
55 vec.sort();
56
57 vec.serialize(serializer)
58}
59
60impl Task {
61 pub fn generate() -> Self {
62 Self {
63 created_at: Utc::now(),
64 description: None,
65 done: None,
66 due: None,
67 id: Id::generate(),
68 recur: None,
69 snooze: None,
70 tags: HashSet::new(),
71 updated_at: Utc::now(),
72 wait: None,
73 }
74 }
75
76 pub fn to_renderable_hash_map(&self) -> HashMap<&str, String> {
77 let mut hm = HashMap::<&str, String>::new();
78
79 let Id(id) = &self.id;
80 hm.insert("id", id.to_string());
81
82 if let Some(description) = &self.description {
83 hm.insert("description", description.to_string());
84 }
85
86 hm.insert(
87 "tags",
88 self.tags
89 .iter()
90 .map(|t| format!("+{}", t))
91 .collect::<Vec<String>>()
92 .join(" "),
93 );
94
95 if let Some(due) = &self.due {
96 hm.insert("due", due.format("%Y-%m-%d %H:%M").to_string());
97 }
98
99 if let Some(recur) = &self.recur {
100 hm.insert("recur", format!("{}", recur));
101 }
102
103 hm
104 }
105
106 pub fn get_id(&self) -> &Id {
107 &(self.id)
108 }
109
110 pub fn satisfies_queries(&self, queries: &Queries) -> bool {
111 if queries.len() == 0 {
112 return false;
113 }
114
115 let mut default = false;
116
117 for query in queries {
118 match query {
119 Query::Id(id) => {
120 if id == &self.id {
121 return true;
122 } else {
123 continue;
124 }
125 }
126
127 Query::Tag(Tag {
128 sign: Sign::Plus,
129 name,
130 }) => {
131 if self.tags.contains(name) {
132 return true;
133 } else {
134 continue;
135 }
136 }
137
138 Query::Tag(Tag {
139 sign: Sign::Minus,
140 name,
141 }) => {
142 if self.tags.contains(name) {
143 return false;
144 } else {
145 default = true;
146 continue;
147 }
148 }
149 }
150 }
151
152 return default;
153 }
154
155 pub fn apply_mutations(&mut self, mutations: &Mutations) -> &Self {
156 for m in mutations {
157 self.apply_mutation(m);
158 }
159
160 self
161 }
162
163 pub fn apply_mutation(&mut self, mutation: &Mutation) -> &Self {
164 match mutation {
165 Mutation::SetTag(Tag {
166 sign: Sign::Plus,
167 name,
168 }) => {
169 self.tags.insert(name.to_string());
170 }
171 Mutation::SetTag(Tag {
172 sign: Sign::Minus,
173 name,
174 }) => {
175 self.tags.remove(name);
176 }
177 Mutation::SetProp(Prop::Description(description)) => {
178 self.description = Some(description.to_string());
179 }
180 Mutation::SetProp(Prop::Done(done)) => {
181 if let Some(recur) = &self.recur {
182 let dt: Duration = Duration::from(recur);
183
184 if let Some(due) = self.due {
185 self.due = Some(due + dt);
186 }
187 if let Some(wait) = self.wait {
188 self.wait = Some(wait + dt);
189 }
190 } else {
191 self.done = Some(done.clone());
192 }
193 }
194 Mutation::SetProp(Prop::Due(due)) => {
195 self.due = due.clone();
196 }
197 Mutation::SetProp(Prop::Snooze(snooze)) => {
198 self.snooze = snooze.clone();
199 }
200 Mutation::SetProp(Prop::Wait(wait)) => {
201 self.wait = wait.clone();
202 }
203 Mutation::SetProp(Prop::Recur(recur)) => self.recur = recur.clone(),
204 }
205
206 self.updated_at = Utc::now();
207
208 self
209 }
210
211 pub fn get_score(&self) -> u64 {
212 let mut score: u64 = 0;
216
217 if let Some(_) = self.done {
218 return 0;
219 }
220
221 if let Some(wait) = self.wait {
222 if wait > Utc::now() {
223 return 0;
224 }
225 }
226
227 if let Some(snooze) = self.snooze {
228 if snooze > Utc::now() {
229 return 0;
230 }
231 }
232
233 score = score + (Utc::now() - self.updated_at).num_seconds() as u64;
234
235 if let Some(due) = self.due {
236 score = score
237 + if self.tags.contains("timely") && due < Utc::now() {
238 2 * (2147483647 - (due.timestamp() as u64))
239 } else {
240 (2147483647 - (due.timestamp() as u64))
241 };
242 }
243
244 score = score
245 + if self.tags.contains("urgent") {
246 score
247 } else {
248 0
249 };
250
251 score
252 }
253
254 pub fn is_overdue(&self) -> bool {
255 if let Some(due) = self.due {
256 return due < Utc::now();
257 } else {
258 return false;
259 }
260 }
261
262 pub fn is_soon_due(&self) -> bool {
263 if let Some(due) = self.due {
264 return due < (Utc::now() + Duration::days(3));
265 } else {
266 return false;
267 }
268 }
269}
270
271impl PartialOrd for Task {
272 fn partial_cmp(&self, other: &Task) -> Option<Ordering> {
273 Some(self.get_score().cmp(&other.get_score()).reverse())
274 }
275}
276
277impl Ord for Task {
278 fn cmp(&self, other: &Task) -> Ordering {
279 self.get_score().cmp(&other.get_score()).reverse()
280 }
281}
282
283impl Eq for Task {}
284impl PartialEq for Task {
285 fn eq(&self, other: &Task) -> bool {
286 self.get_score() == other.get_score()
287 }
288}