1use std::collections::{HashMap, HashSet};
2
3use crate::config::{Border, Borders, HorizontalLine, Position, VerticalLine};
4
5#[derive(Debug, Default, Clone, PartialEq, Eq)]
6pub(crate) struct BordersConfig<T> {
7 global: Option<T>,
8 borders: Borders<T>,
9 cells: BordersMap<T>,
10 horizontals: HashMap<usize, HorizontalLine<T>>,
11 verticals: HashMap<usize, VerticalLine<T>>,
12 layout: BordersLayout,
13}
14
15#[derive(Debug, Clone, Default, PartialEq, Eq)]
16pub(crate) struct BordersMap<T> {
17 vertical: HashMap<Position, T>,
18 horizontal: HashMap<Position, T>,
19 intersection: HashMap<Position, T>,
20}
21
22impl<T> BordersMap<T> {
23 fn is_empty(&self) -> bool {
24 self.vertical.is_empty() && self.horizontal.is_empty() && self.intersection.is_empty()
25 }
26}
27
28#[derive(Debug, Clone, Default, PartialEq, Eq)]
29pub(crate) struct BordersLayout {
30 left: bool,
31 right: bool,
32 top: bool,
33 bottom: bool,
34 horizontals: HashSet<usize>,
35 verticals: HashSet<usize>,
36}
37
38impl<T> BordersConfig<T> {
39 pub(crate) fn is_empty(&self) -> bool {
40 self.global.is_none()
41 && self.borders.is_empty()
42 && self.cells.is_empty()
43 && self.horizontals.is_empty()
44 && self.verticals.is_empty()
45 }
46
47 pub(crate) fn insert_border(&mut self, pos: Position, border: Border<T>) {
48 if let Some(c) = border.top {
49 self.cells.horizontal.insert(pos, c);
50 self.layout.horizontals.insert(pos.0);
51 }
52
53 if let Some(c) = border.bottom {
54 self.cells.horizontal.insert((pos.0 + 1, pos.1), c);
55 self.layout.horizontals.insert(pos.0 + 1);
56 }
57
58 if let Some(c) = border.left {
59 self.cells.vertical.insert(pos, c);
60 self.layout.verticals.insert(pos.1);
61 }
62
63 if let Some(c) = border.right {
64 self.cells.vertical.insert((pos.0, pos.1 + 1), c);
65 self.layout.verticals.insert(pos.1 + 1);
66 }
67
68 if let Some(c) = border.left_top_corner {
69 self.cells.intersection.insert((pos.0, pos.1), c);
70 self.layout.horizontals.insert(pos.0);
71 self.layout.verticals.insert(pos.1);
72 }
73
74 if let Some(c) = border.right_top_corner {
75 self.cells.intersection.insert((pos.0, pos.1 + 1), c);
76 self.layout.horizontals.insert(pos.0);
77 self.layout.verticals.insert(pos.1 + 1);
78 }
79
80 if let Some(c) = border.left_bottom_corner {
81 self.cells.intersection.insert((pos.0 + 1, pos.1), c);
82 self.layout.horizontals.insert(pos.0 + 1);
83 self.layout.verticals.insert(pos.1);
84 }
85
86 if let Some(c) = border.right_bottom_corner {
87 self.cells.intersection.insert((pos.0 + 1, pos.1 + 1), c);
88 self.layout.horizontals.insert(pos.0 + 1);
89 self.layout.verticals.insert(pos.1 + 1);
90 }
91 }
92
93 pub(crate) fn remove_border(&mut self, pos: Position, shape: (usize, usize)) {
94 let (count_rows, count_cols) = shape;
95
96 self.cells.horizontal.remove(&pos);
97 self.cells.horizontal.remove(&(pos.0 + 1, pos.1));
98 self.cells.vertical.remove(&pos);
99 self.cells.vertical.remove(&(pos.0, pos.1 + 1));
100 self.cells.intersection.remove(&pos);
101 self.cells.intersection.remove(&(pos.0 + 1, pos.1));
102 self.cells.intersection.remove(&(pos.0, pos.1 + 1));
103 self.cells.intersection.remove(&(pos.0 + 1, pos.1 + 1));
104
105 // clean up the layout.
106
107 if !self.check_is_horizontal_set(pos.0, count_rows) {
108 self.layout.horizontals.remove(&pos.0);
109 }
110
111 if !self.check_is_horizontal_set(pos.0 + 1, count_rows) {
112 self.layout.horizontals.remove(&(pos.0 + 1));
113 }
114
115 if !self.check_is_vertical_set(pos.1, count_cols) {
116 self.layout.verticals.remove(&pos.1);
117 }
118
119 if !self.check_is_vertical_set(pos.1 + 1, count_cols) {
120 self.layout.verticals.remove(&(pos.1 + 1));
121 }
122 }
123
124 pub(crate) fn get_border(&self, pos: Position, shape: (usize, usize)) -> Border<&T> {
125 Border {
126 top: self.get_horizontal(pos, shape.0),
127 bottom: self.get_horizontal((pos.0 + 1, pos.1), shape.0),
128 left: self.get_vertical(pos, shape.1),
129 left_top_corner: self.get_intersection(pos, shape),
130 left_bottom_corner: self.get_intersection((pos.0 + 1, pos.1), shape),
131 right: self.get_vertical((pos.0, pos.1 + 1), shape.1),
132 right_top_corner: self.get_intersection((pos.0, pos.1 + 1), shape),
133 right_bottom_corner: self.get_intersection((pos.0 + 1, pos.1 + 1), shape),
134 }
135 }
136
137 pub(crate) fn insert_horizontal_line(&mut self, row: usize, line: HorizontalLine<T>) {
138 if line.left.is_some() {
139 self.layout.left = true;
140 }
141
142 // todo: when we delete lines these are still left set; so has_horizontal/vertical return true in some cases;
143 // it shall be fixed, but maybe we can improve the logic as it got a bit complicated.
144 if line.right.is_some() {
145 self.layout.right = true;
146 }
147
148 self.horizontals.insert(row, line);
149 self.layout.horizontals.insert(row);
150 }
151
152 pub(crate) fn get_horizontal_line(&self, row: usize) -> Option<&HorizontalLine<T>> {
153 self.horizontals.get(&row)
154 }
155
156 pub(crate) fn get_horizontal_lines(&self) -> HashMap<usize, HorizontalLine<T>>
157 where
158 T: Clone,
159 {
160 self.horizontals.clone()
161 }
162
163 pub(crate) fn remove_horizontal_line(&mut self, row: usize, count_rows: usize) {
164 self.horizontals.remove(&row);
165 self.layout.horizontals.remove(&row);
166
167 if self.has_horizontal(row, count_rows) {
168 self.layout.horizontals.insert(row);
169 }
170 }
171
172 pub(crate) fn insert_vertical_line(&mut self, column: usize, line: VerticalLine<T>) {
173 if line.top.is_some() {
174 self.layout.top = true;
175 }
176
177 if line.bottom.is_some() {
178 self.layout.bottom = true;
179 }
180
181 self.verticals.insert(column, line);
182 self.layout.verticals.insert(column);
183 }
184
185 pub(crate) fn get_vertical_line(&self, column: usize) -> Option<&VerticalLine<T>> {
186 self.verticals.get(&column)
187 }
188
189 pub(crate) fn get_vertical_lines(&self) -> HashMap<usize, VerticalLine<T>>
190 where
191 T: Clone,
192 {
193 self.verticals.clone()
194 }
195
196 pub(crate) fn remove_vertical_line(&mut self, col: usize, count_columns: usize) {
197 self.verticals.remove(&col);
198 self.layout.verticals.remove(&col);
199
200 if self.has_vertical(col, count_columns) {
201 self.layout.verticals.insert(col);
202 }
203 }
204
205 pub(crate) fn set_borders(&mut self, borders: Borders<T>) {
206 self.borders = borders;
207 }
208
209 pub(crate) fn get_borders(&self) -> &Borders<T> {
210 &self.borders
211 }
212
213 pub(crate) fn get_global(&self) -> Option<&T> {
214 self.global.as_ref()
215 }
216
217 pub(crate) fn set_global(&mut self, value: T) {
218 self.global = Some(value);
219 }
220
221 pub(crate) fn get_vertical(&self, pos: Position, count_cols: usize) -> Option<&T> {
222 self.cells
223 .vertical
224 .get(&pos)
225 .or_else(|| self.verticals.get(&pos.1).and_then(|l| l.main.as_ref()))
226 .or({
227 if pos.1 == count_cols {
228 self.borders.right.as_ref()
229 } else if pos.1 == 0 {
230 self.borders.left.as_ref()
231 } else {
232 self.borders.vertical.as_ref()
233 }
234 })
235 .or(self.global.as_ref())
236 }
237
238 pub(crate) fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<&T> {
239 self.cells
240 .horizontal
241 .get(&pos)
242 .or_else(|| self.horizontals.get(&pos.0).and_then(|l| l.main.as_ref()))
243 .or({
244 if pos.0 == 0 {
245 self.borders.top.as_ref()
246 } else if pos.0 == count_rows {
247 self.borders.bottom.as_ref()
248 } else {
249 self.borders.horizontal.as_ref()
250 }
251 })
252 .or(self.global.as_ref())
253 }
254
255 pub(crate) fn get_intersection(
256 &self,
257 pos: Position,
258 (count_rows, count_cols): (usize, usize),
259 ) -> Option<&T> {
260 let use_top = pos.0 == 0;
261 let use_bottom = pos.0 == count_rows;
262 let use_left = pos.1 == 0;
263 let use_right = pos.1 == count_cols;
264
265 let itersection = self.cells.intersection.get(&pos);
266 if itersection.is_some() {
267 return itersection;
268 }
269
270 let itersection = self.horizontals.get(&pos.0).and_then(|l| {
271 if use_left && l.left.is_some() {
272 l.left.as_ref()
273 } else if use_right && l.right.is_some() {
274 l.right.as_ref()
275 } else if !use_right && !use_left && l.intersection.is_some() {
276 l.intersection.as_ref()
277 } else {
278 None
279 }
280 });
281 if itersection.is_some() {
282 return itersection;
283 }
284
285 let itersection = self.verticals.get(&pos.1).and_then(|l| {
286 if use_top && l.top.is_some() {
287 l.top.as_ref()
288 } else if use_bottom && l.bottom.is_some() {
289 l.bottom.as_ref()
290 } else if !use_top && !use_bottom && l.intersection.is_some() {
291 l.intersection.as_ref()
292 } else {
293 None
294 }
295 });
296 if itersection.is_some() {
297 return itersection;
298 }
299
300 let itersection = {
301 if use_top && use_left {
302 self.borders.top_left.as_ref()
303 } else if use_top && use_right {
304 self.borders.top_right.as_ref()
305 } else if use_bottom && use_left {
306 self.borders.bottom_left.as_ref()
307 } else if use_bottom && use_right {
308 self.borders.bottom_right.as_ref()
309 } else if use_top {
310 self.borders.top_intersection.as_ref()
311 } else if use_bottom {
312 self.borders.bottom_intersection.as_ref()
313 } else if use_left {
314 self.borders.left_intersection.as_ref()
315 } else if use_right {
316 self.borders.right_intersection.as_ref()
317 } else {
318 self.borders.intersection.as_ref()
319 }
320 };
321 if itersection.is_some() {
322 return itersection;
323 }
324
325 self.global.as_ref()
326 }
327
328 pub(crate) fn has_horizontal(&self, row: usize, count_rows: usize) -> bool {
329 self.global.is_some()
330 || (row == 0 && self.borders.has_top())
331 || (row == count_rows && self.borders.has_bottom())
332 || (row > 0 && row < count_rows && self.borders.has_horizontal())
333 || self.is_horizontal_set(row, count_rows)
334 }
335
336 pub(crate) fn has_vertical(&self, col: usize, count_cols: usize) -> bool {
337 self.global.is_some()
338 || (col == 0 && self.borders.has_left())
339 || (col == count_cols && self.borders.has_right())
340 || (col > 0 && col < count_cols && self.borders.has_vertical())
341 || self.is_vertical_set(col, count_cols)
342 }
343
344 fn is_horizontal_set(&self, row: usize, count_rows: usize) -> bool {
345 (row == 0 && self.layout.top)
346 || (row == count_rows && self.layout.bottom)
347 || self.layout.horizontals.contains(&row)
348 }
349
350 fn is_vertical_set(&self, col: usize, count_cols: usize) -> bool {
351 (col == 0 && self.layout.left)
352 || (col == count_cols && self.layout.right)
353 || self.layout.verticals.contains(&col)
354 }
355
356 fn check_is_horizontal_set(&self, row: usize, count_rows: usize) -> bool {
357 (row == 0 && self.layout.top)
358 || (row == count_rows && self.layout.bottom)
359 || self.cells.horizontal.keys().any(|&p| p.0 == row)
360 || self.cells.intersection.keys().any(|&p| p.0 == row)
361 }
362
363 fn check_is_vertical_set(&self, col: usize, count_cols: usize) -> bool {
364 (col == 0 && self.layout.left)
365 || (col == count_cols && self.layout.right)
366 || self.cells.vertical.keys().any(|&p| p.1 == col)
367 || self.cells.intersection.keys().any(|&p| p.1 == col)
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_insert_border() {
377 let mut borders = BordersConfig::<char>::default();
378 borders.insert_border((0, 0), Border::filled('x'));
379
380 assert_eq!(borders.get_border((0, 0), (10, 10)), Border::filled(&'x'));
381 assert_eq!(borders.get_border((0, 0), (0, 0)), Border::filled(&'x'));
382
383 assert!(borders.is_horizontal_set(0, 10));
384 assert!(borders.is_horizontal_set(1, 10));
385 assert!(!borders.is_horizontal_set(2, 10));
386 assert!(borders.is_vertical_set(0, 10));
387 assert!(borders.is_vertical_set(1, 10));
388 assert!(!borders.is_vertical_set(2, 10));
389
390 assert!(borders.is_horizontal_set(0, 0));
391 assert!(borders.is_horizontal_set(1, 0));
392 assert!(!borders.is_horizontal_set(2, 0));
393 assert!(borders.is_vertical_set(0, 0));
394 assert!(borders.is_vertical_set(1, 0));
395 assert!(!borders.is_vertical_set(2, 0));
396 }
397
398 #[test]
399 fn test_insert_border_override() {
400 let mut borders = BordersConfig::<char>::default();
401 borders.insert_border((0, 0), Border::filled('x'));
402 borders.insert_border((1, 0), Border::filled('y'));
403 borders.insert_border((0, 1), Border::filled('w'));
404 borders.insert_border((1, 1), Border::filled('q'));
405
406 assert_eq!(
407 borders.get_border((0, 0), (10, 10)).copied(),
408 Border::full('x', 'y', 'x', 'w', 'x', 'w', 'y', 'q')
409 );
410 assert_eq!(
411 borders.get_border((0, 1), (10, 10)).copied(),
412 Border::full('w', 'q', 'w', 'w', 'w', 'w', 'q', 'q')
413 );
414 assert_eq!(
415 borders.get_border((1, 0), (10, 10)).copied(),
416 Border::full('y', 'y', 'y', 'q', 'y', 'q', 'y', 'q')
417 );
418 assert_eq!(
419 borders.get_border((1, 1), (10, 10)).copied(),
420 Border::filled('q')
421 );
422
423 assert!(borders.is_horizontal_set(0, 10));
424 assert!(borders.is_horizontal_set(1, 10));
425 assert!(borders.is_horizontal_set(2, 10));
426 assert!(!borders.is_horizontal_set(3, 10));
427 assert!(borders.is_vertical_set(0, 10));
428 assert!(borders.is_vertical_set(1, 10));
429 assert!(borders.is_vertical_set(2, 10));
430 assert!(!borders.is_vertical_set(3, 10));
431 }
432
433 #[test]
434 fn test_set_global() {
435 let mut borders = BordersConfig::<char>::default();
436 borders.insert_border((0, 0), Border::filled('x'));
437 borders.set_global('l');
438
439 assert_eq!(borders.get_border((0, 0), (10, 10)), Border::filled(&'x'));
440 assert_eq!(borders.get_border((2, 0), (10, 10)), Border::filled(&'l'));
441
442 assert!(borders.is_horizontal_set(0, 10));
443 assert!(borders.is_horizontal_set(1, 10));
444 assert!(!borders.is_horizontal_set(2, 10));
445 assert!(borders.is_vertical_set(0, 10));
446 assert!(borders.is_vertical_set(1, 10));
447 assert!(!borders.is_vertical_set(2, 10));
448
449 assert!(borders.is_horizontal_set(0, 0));
450 assert!(borders.is_horizontal_set(1, 0));
451 assert!(!borders.is_horizontal_set(2, 0));
452 assert!(borders.is_vertical_set(0, 0));
453 assert!(borders.is_vertical_set(1, 0));
454 assert!(!borders.is_vertical_set(2, 0));
455 }
456}
457