Skip to main content

ironcalc_base/
worksheet.rs

1use crate::constants::{self, LAST_COLUMN, LAST_ROW};
2use crate::expressions::types::CellReferenceIndex;
3use crate::expressions::utils::{is_valid_column_number, is_valid_row};
4use crate::model::CellStructure;
5use crate::{expressions::token::Error, types::*};
6
7use std::collections::HashMap;
8
9#[derive(Debug, PartialEq, Eq)]
10pub struct WorksheetDimension {
11    pub min_row: i32,
12    pub max_row: i32,
13    pub min_column: i32,
14    pub max_column: i32,
15}
16
17#[derive(Clone, Copy, PartialEq, Eq)]
18pub enum NavigationDirection {
19    Left,
20    Right,
21    Up,
22    Down,
23}
24
25impl Worksheet {
26    pub fn get_name(&self) -> String {
27        self.name.clone()
28    }
29
30    pub fn get_sheet_id(&self) -> u32 {
31        self.sheet_id
32    }
33
34    pub fn set_name(&mut self, name: &str) {
35        self.name = name.to_string();
36    }
37
38    pub fn cell(&self, row: i32, column: i32) -> Option<&Cell> {
39        self.sheet_data.get(&row)?.get(&column)
40    }
41
42    pub(crate) fn cell_mut(&mut self, row: i32, column: i32) -> Option<&mut Cell> {
43        self.sheet_data.get_mut(&row)?.get_mut(&column)
44    }
45
46    pub(crate) fn update_cell(
47        &mut self,
48        row: i32,
49        column: i32,
50        new_cell: Cell,
51    ) -> Result<(), String> {
52        // validate row and column arg before updating cell of worksheet
53        if !is_valid_row(row) || !is_valid_column_number(column) {
54            return Err("Incorrect row or column".to_string());
55        }
56
57        match self.sheet_data.get_mut(&row) {
58            Some(column_data) => match column_data.get(&column) {
59                Some(_cell) => {
60                    column_data.insert(column, new_cell);
61                }
62                None => {
63                    column_data.insert(column, new_cell);
64                }
65            },
66            None => {
67                let mut column_data = HashMap::new();
68                column_data.insert(column, new_cell);
69                self.sheet_data.insert(row, column_data);
70            }
71        }
72        Ok(())
73    }
74
75    // See: get_style_for_cell
76    fn get_row_column_style(&self, row_index: i32, column_index: i32) -> i32 {
77        let rows = &self.rows;
78        for row in rows {
79            if row.r == row_index {
80                if row.custom_format {
81                    return row.s;
82                }
83                break;
84            }
85        }
86        let cols = &self.cols;
87        for column in cols.iter() {
88            let min = column.min;
89            let max = column.max;
90            if column_index >= min && column_index <= max {
91                return column.style.unwrap_or(0);
92            }
93        }
94        0
95    }
96
97    pub fn get_style(&self, row: i32, column: i32) -> i32 {
98        match self.sheet_data.get(&row) {
99            Some(column_data) => match column_data.get(&column) {
100                Some(cell) => cell.get_style(),
101                None => self.get_row_column_style(row, column),
102            },
103            None => self.get_row_column_style(row, column),
104        }
105    }
106
107    pub fn set_style(&mut self, style_index: i32) -> Result<(), String> {
108        self.cols = vec![Col {
109            min: 1,
110            max: constants::LAST_COLUMN,
111            width: constants::DEFAULT_COLUMN_WIDTH,
112            custom_width: false,
113            style: Some(style_index),
114            hidden: false,
115        }];
116        Ok(())
117    }
118
119    pub fn set_column_style(&mut self, column: i32, style_index: i32) -> Result<(), String> {
120        let width = self
121            .get_column_width(column)
122            .unwrap_or(constants::DEFAULT_COLUMN_WIDTH);
123        let hidden = self.is_column_hidden(column)?;
124        self.set_column_width_and_style(column, width, hidden, Some(style_index))
125    }
126
127    pub fn set_row_style(&mut self, row: i32, style_index: i32) -> Result<(), String> {
128        // FIXME: This is a HACK
129        let custom_format = style_index != 0;
130        for r in self.rows.iter_mut() {
131            if r.r == row {
132                r.s = style_index;
133                r.custom_format = custom_format;
134                return Ok(());
135            }
136        }
137        self.rows.push(Row {
138            height: constants::DEFAULT_ROW_HEIGHT / constants::ROW_HEIGHT_FACTOR,
139            r: row,
140            custom_format,
141            custom_height: false,
142            s: style_index,
143            hidden: false,
144        });
145        Ok(())
146    }
147
148    pub fn delete_row_style(&mut self, row: i32) -> Result<(), String> {
149        let mut index = None;
150        for (i, r) in self.rows.iter().enumerate() {
151            if r.r == row {
152                index = Some(i);
153                break;
154            }
155        }
156        if let Some(i) = index {
157            if let Some(r) = self.rows.get_mut(i) {
158                r.s = 0;
159                r.custom_format = false;
160            }
161        }
162        Ok(())
163    }
164
165    pub fn delete_column_style(&mut self, column: i32) -> Result<(), String> {
166        if !is_valid_column_number(column) {
167            return Err(format!("Column number '{column}' is not valid."));
168        }
169        let cols = &mut self.cols;
170
171        let mut index = 0;
172        let mut split = false;
173        for c in cols.iter_mut() {
174            let min = c.min;
175            let max = c.max;
176            if min <= column && column <= max {
177                //
178                split = true;
179                break;
180            }
181            if column < min {
182                // We passed, there is nothing to delete
183                break;
184            }
185            index += 1;
186        }
187        if split {
188            let min = cols[index].min;
189            let max = cols[index].max;
190            let custom_width = cols[index].custom_width;
191            let width = cols[index].width;
192            let pre = Col {
193                min,
194                max: column - 1,
195                width,
196                custom_width,
197                style: cols[index].style,
198                hidden: cols[index].hidden,
199            };
200            let col = Col {
201                min: column,
202                max: column,
203                width,
204                custom_width,
205                style: None,
206                hidden: false,
207            };
208            let post = Col {
209                min: column + 1,
210                max,
211                width,
212                custom_width,
213                style: cols[index].style,
214                hidden: cols[index].hidden,
215            };
216            cols.remove(index);
217            if column != max {
218                cols.insert(index, post);
219            }
220            if custom_width {
221                cols.insert(index, col);
222            }
223            if column != min {
224                cols.insert(index, pre);
225            }
226        }
227        Ok(())
228    }
229
230    pub fn set_cell_style(
231        &mut self,
232        row: i32,
233        column: i32,
234        style_index: i32,
235    ) -> Result<(), String> {
236        match self.cell_mut(row, column) {
237            Some(cell) => {
238                cell.set_style(style_index);
239            }
240            None => {
241                self.cell_clear_contents_with_style(row, column, style_index)?;
242            }
243        }
244        Ok(())
245
246        // TODO: cleanup check if the old cell style is still in use
247    }
248
249    pub fn set_cell_with_formula(
250        &mut self,
251        row: i32,
252        column: i32,
253        index: i32,
254        style: i32,
255    ) -> Result<(), String> {
256        let cell = Cell::new_formula(index, style);
257        self.update_cell(row, column, cell)
258    }
259
260    pub fn set_cell_with_dynamic_formula(
261        &mut self,
262        row: i32,
263        column: i32,
264        index: i32,
265        style: i32,
266        width: i32,
267        height: i32,
268    ) -> Result<(), String> {
269        let cell = Cell::ArrayFormula {
270            f: index,
271            s: style,
272            r: (width, height),
273            kind: ArrayKind::Dynamic,
274            v: FormulaValue::Unevaluated,
275        };
276        self.update_cell(row, column, cell)
277    }
278
279    pub fn set_cell_with_array_formula(
280        &mut self,
281        row: i32,
282        column: i32,
283        index: i32,
284        style: i32,
285        width: i32,
286        height: i32,
287    ) -> Result<(), String> {
288        let cell = Cell::ArrayFormula {
289            f: index,
290            s: style,
291            r: (width, height),
292            kind: ArrayKind::Cse,
293            v: FormulaValue::Unevaluated,
294        };
295        self.update_cell(row, column, cell)
296    }
297
298    pub fn set_cell_with_number(
299        &mut self,
300        row: i32,
301        column: i32,
302        value: f64,
303        style: i32,
304    ) -> Result<(), String> {
305        let cell = Cell::new_number(value, style);
306        self.update_cell(row, column, cell)
307    }
308
309    pub fn set_cell_with_string(
310        &mut self,
311        row: i32,
312        column: i32,
313        index: i32,
314        style: i32,
315    ) -> Result<(), String> {
316        let cell = Cell::new_string(index, style);
317        self.update_cell(row, column, cell)
318    }
319
320    pub fn set_cell_with_boolean(
321        &mut self,
322        row: i32,
323        column: i32,
324        value: bool,
325        style: i32,
326    ) -> Result<(), String> {
327        let cell = Cell::new_boolean(value, style);
328        self.update_cell(row, column, cell)
329    }
330
331    pub fn set_cell_with_error(
332        &mut self,
333        row: i32,
334        column: i32,
335        error: Error,
336        style: i32,
337    ) -> Result<(), String> {
338        let cell = Cell::new_error(error, style);
339        self.update_cell(row, column, cell)
340    }
341
342    pub fn cell_clear_contents(&mut self, row: i32, column: i32) -> Result<(), String> {
343        let s = self.get_style(row, column);
344        let cell = Cell::EmptyCell { s };
345        self.update_cell(row, column, cell)
346    }
347
348    pub fn cell_clear_contents_with_style(
349        &mut self,
350        row: i32,
351        column: i32,
352        style: i32,
353    ) -> Result<(), String> {
354        let cell = Cell::EmptyCell { s: style };
355        self.update_cell(row, column, cell)
356    }
357
358    pub fn set_frozen_rows(&mut self, frozen_rows: i32) -> Result<(), String> {
359        if frozen_rows < 0 {
360            return Err("Frozen rows cannot be negative".to_string());
361        }
362        if frozen_rows >= constants::LAST_ROW {
363            return Err("Too many rows".to_string());
364        }
365        self.frozen_rows = frozen_rows;
366        Ok(())
367    }
368
369    pub fn set_frozen_columns(&mut self, frozen_columns: i32) -> Result<(), String> {
370        if frozen_columns < 0 {
371            return Err("Frozen columns cannot be negative".to_string());
372        }
373        if frozen_columns >= constants::LAST_COLUMN {
374            return Err("Too many columns".to_string());
375        }
376        self.frozen_columns = frozen_columns;
377        Ok(())
378    }
379
380    /// Changes the hidden status of a row.
381    pub fn set_row_hidden(&mut self, row: i32, hidden: bool) -> Result<(), String> {
382        if !is_valid_row(row) {
383            return Err(format!("Row number '{row}' is not valid."));
384        }
385
386        let rows = &mut self.rows;
387        for r in rows.iter_mut() {
388            if r.r == row {
389                r.hidden = hidden;
390                return Ok(());
391            }
392        }
393        rows.push(Row {
394            height: constants::DEFAULT_ROW_HEIGHT / constants::ROW_HEIGHT_FACTOR,
395            r: row,
396            custom_format: false,
397            custom_height: false,
398            s: 0,
399            hidden,
400        });
401        Ok(())
402    }
403
404    /// Changes the height of a row.
405    ///   * If the row does not a have a style we add it.
406    ///   * If it has we modify the height and make sure it is applied.
407    ///
408    /// Fails if row index is outside allowed range or height is negative.
409    pub fn set_row_height(&mut self, row: i32, height: f64) -> Result<(), String> {
410        if !is_valid_row(row) {
411            return Err(format!("Row number '{row}' is not valid."));
412        }
413        if height < 0.0 {
414            return Err(format!("Can not set a negative height: {height}"));
415        }
416        let hidden = self.is_row_hidden(row)?;
417        let rows = &mut self.rows;
418        for r in rows.iter_mut() {
419            if r.r == row {
420                r.height = height / constants::ROW_HEIGHT_FACTOR;
421                r.custom_height = true;
422                return Ok(());
423            }
424        }
425        rows.push(Row {
426            height: height / constants::ROW_HEIGHT_FACTOR,
427            r: row,
428            custom_format: false,
429            custom_height: true,
430            s: 0,
431            hidden,
432        });
433        Ok(())
434    }
435
436    /// Changes the width of a column.
437    ///   * If the column does not a have a width we simply add it
438    ///   * If it has, it might be part of a range and we need to split the range.
439    ///
440    /// Fails if column index is outside allowed range or width is negative.
441    pub fn set_column_width(&mut self, column: i32, width: f64) -> Result<(), String> {
442        let style = self.get_column_style(column)?;
443        let hidden = self.is_column_hidden(column)?;
444        self.set_column_width_and_style(column, width, hidden, style)
445    }
446
447    pub fn set_column_hidden(&mut self, column: i32, hidden: bool) -> Result<(), String> {
448        let width = self
449            .get_actual_column_width(column)
450            .unwrap_or(constants::DEFAULT_COLUMN_WIDTH);
451        let style = self.get_column_style(column)?;
452        self.set_column_width_and_style(column, width, hidden, style)
453    }
454
455    pub(crate) fn set_column_width_and_style(
456        &mut self,
457        column: i32,
458        width: f64,
459        hidden: bool,
460        style: Option<i32>,
461    ) -> Result<(), String> {
462        if !is_valid_column_number(column) {
463            return Err(format!("Column number '{column}' is not valid."));
464        }
465        if width < 0.0 {
466            return Err(format!("Can not set a negative width: {width}"));
467        }
468        let cols = &mut self.cols;
469        let mut col = Col {
470            min: column,
471            max: column,
472            width: width / constants::COLUMN_WIDTH_FACTOR,
473            custom_width: width != constants::DEFAULT_COLUMN_WIDTH,
474            style,
475            hidden,
476        };
477        let mut index = 0;
478        let mut split = false;
479        for c in cols.iter_mut() {
480            let min = c.min;
481            let max = c.max;
482            if min <= column && column <= max {
483                if min == column && max == column {
484                    c.style = style;
485                    c.width = width / constants::COLUMN_WIDTH_FACTOR;
486                    c.custom_width = width != constants::DEFAULT_COLUMN_WIDTH;
487                    c.hidden = hidden;
488                    return Ok(());
489                }
490                split = true;
491                break;
492            }
493            if column < min {
494                // We passed, we should insert at index
495                break;
496            }
497            index += 1;
498        }
499        if split {
500            let min = cols[index].min;
501            let max = cols[index].max;
502            let pre = Col {
503                min,
504                max: column - 1,
505                width: cols[index].width,
506                custom_width: cols[index].custom_width,
507                style: cols[index].style,
508                hidden: cols[index].hidden,
509            };
510            let post = Col {
511                min: column + 1,
512                max,
513                width: cols[index].width,
514                custom_width: cols[index].custom_width,
515                style: cols[index].style,
516                hidden: cols[index].hidden,
517            };
518            col.style = cols[index].style;
519            cols.remove(index);
520            if column != max {
521                cols.insert(index, post);
522            }
523            cols.insert(index, col);
524            if column != min {
525                cols.insert(index, pre);
526            }
527        } else {
528            cols.insert(index, col);
529        }
530        Ok(())
531    }
532
533    /// Return the width of a column in pixels
534    pub fn get_column_width(&self, column: i32) -> Result<f64, String> {
535        if !is_valid_column_number(column) {
536            return Err(format!("Column number '{column}' is not valid."));
537        }
538
539        let cols = &self.cols;
540        for col in cols {
541            let min = col.min;
542            let max = col.max;
543            if column >= min && column <= max {
544                if col.hidden {
545                    return Ok(0.0);
546                }
547                if col.custom_width {
548                    return Ok(col.width * constants::COLUMN_WIDTH_FACTOR);
549                }
550                break;
551            }
552        }
553        Ok(constants::DEFAULT_COLUMN_WIDTH)
554    }
555
556    /// Return the actual width of a column in pixels, ignoring hidden status
557    pub fn get_actual_column_width(&self, column: i32) -> Result<f64, String> {
558        if !is_valid_column_number(column) {
559            return Err(format!("Column number '{column}' is not valid."));
560        }
561
562        let cols = &self.cols;
563        for col in cols {
564            let min = col.min;
565            let max = col.max;
566            if column >= min && column <= max {
567                if col.custom_width {
568                    return Ok(col.width * constants::COLUMN_WIDTH_FACTOR);
569                }
570                break;
571            }
572        }
573        Ok(constants::DEFAULT_COLUMN_WIDTH)
574    }
575
576    /// Returns true if the column is hidden
577    pub fn is_column_hidden(&self, column: i32) -> Result<bool, String> {
578        if !is_valid_column_number(column) {
579            return Err(format!("Column number '{column}' is not valid."));
580        }
581
582        let cols = &self.cols;
583        for col in cols {
584            let min = col.min;
585            let max = col.max;
586            if column >= min && column <= max {
587                return Ok(col.hidden);
588            }
589        }
590        Ok(false)
591    }
592
593    /// Returns if a row is hidden
594    pub fn is_row_hidden(&self, row: i32) -> Result<bool, String> {
595        if !is_valid_row(row) {
596            return Err(format!("Row number '{row}' is not valid."));
597        }
598        let rows = &self.rows;
599        for r in rows {
600            if r.r == row {
601                return Ok(r.hidden);
602            }
603        }
604        Ok(false)
605    }
606
607    /// Returns the column style index if present
608    pub fn get_column_style(&self, column: i32) -> Result<Option<i32>, String> {
609        if !is_valid_column_number(column) {
610            return Err(format!("Column number '{column}' is not valid."));
611        }
612
613        let cols = &self.cols;
614        for col in cols {
615            let min = col.min;
616            let max = col.max;
617            if column >= min && column <= max {
618                return Ok(col.style);
619            }
620        }
621        Ok(None)
622    }
623
624    // Returns non empty cells in a column
625    pub(crate) fn column_cell_references(
626        &self,
627        column: i32,
628    ) -> Result<Vec<CellReferenceIndex>, String> {
629        let mut column_cell_references: Vec<CellReferenceIndex> = Vec::new();
630        if !is_valid_column_number(column) {
631            return Err(format!("Column number '{column}' is not valid."));
632        }
633
634        for row in self.sheet_data.keys() {
635            if self.cell(*row, column).is_some() {
636                column_cell_references.push(CellReferenceIndex {
637                    sheet: self.sheet_id,
638                    row: *row,
639                    column,
640                });
641            }
642        }
643        Ok(column_cell_references)
644    }
645
646    pub(crate) fn remove_cell(&mut self, row: i32, column: i32) -> Result<(), String> {
647        if let Some(row_data) = self.sheet_data.get_mut(&row) {
648            row_data.remove(&column);
649            if row_data.is_empty() {
650                self.sheet_data.remove(&row);
651            }
652        }
653        Ok(())
654    }
655
656    /// Returns the height of a row in pixels
657    pub fn row_height(&self, row: i32) -> Result<f64, String> {
658        if !is_valid_row(row) {
659            return Err(format!("Row number '{row}' is not valid."));
660        }
661
662        let rows = &self.rows;
663        for r in rows {
664            if r.r == row {
665                if r.hidden {
666                    return Ok(0.0);
667                }
668                return Ok(r.height * constants::ROW_HEIGHT_FACTOR);
669            }
670        }
671        Ok(constants::DEFAULT_ROW_HEIGHT)
672    }
673
674    /// Calculates dimension of the sheet. This function isn't cheap to calculate.
675    pub fn dimension(&self) -> WorksheetDimension {
676        // FIXME: It's probably better to just track the size as operations happen.
677        if self.sheet_data.is_empty() {
678            return WorksheetDimension {
679                min_row: 1,
680                max_row: 1,
681                min_column: 1,
682                max_column: 1,
683            };
684        }
685
686        let mut row_range: Option<(i32, i32)> = None;
687        let mut column_range: Option<(i32, i32)> = None;
688
689        for (row_index, columns) in &self.sheet_data {
690            row_range = if let Some((current_min, current_max)) = row_range {
691                Some((current_min.min(*row_index), current_max.max(*row_index)))
692            } else {
693                Some((*row_index, *row_index))
694            };
695
696            for column_index in columns.keys() {
697                column_range = if let Some((current_min, current_max)) = column_range {
698                    Some((
699                        current_min.min(*column_index),
700                        current_max.max(*column_index),
701                    ))
702                } else {
703                    Some((*column_index, *column_index))
704                }
705            }
706        }
707
708        let dimension = if let Some((min_row, max_row)) = row_range {
709            if let Some((min_column, max_column)) = column_range {
710                Some(WorksheetDimension {
711                    min_row,
712                    min_column,
713                    max_row,
714                    max_column,
715                })
716            } else {
717                None
718            }
719        } else {
720            None
721        };
722
723        dimension.unwrap_or(WorksheetDimension {
724            min_row: 1,
725            max_row: 1,
726            min_column: 1,
727            max_column: 1,
728        })
729    }
730
731    /// Returns true if cell is completely empty.
732    /// Cell with formula that evaluates to empty string is not considered empty.
733    pub fn is_empty_cell(&self, row: i32, column: i32) -> Result<bool, String> {
734        if !is_valid_column_number(column) || !is_valid_row(row) {
735            return Err("Row or column is outside valid range.".to_string());
736        }
737
738        let is_empty = if let Some(data_row) = self.sheet_data.get(&row) {
739            if let Some(cell) = data_row.get(&column) {
740                matches!(cell, Cell::EmptyCell { .. })
741            } else {
742                true
743            }
744        } else {
745            true
746        };
747
748        Ok(is_empty)
749    }
750
751    // Returns:
752    // - If it is an anchor for a dynamic array => full range
753    // - If it is an anchor for an array formula => full range
754    // - If it is a single cell with a formula => SingleCell
755    // - If it is a single cell without formula => Error
756    // - If it is a spill cell => Error
757    // - Any other case => Error
758    pub(crate) fn get_cell_spill(&self, row: i32, column: i32) -> Result<(i32, i32), String> {
759        if !is_valid_column_number(column) || !is_valid_row(row) {
760            return Err("Row or column is outside valid range.".to_string());
761        }
762
763        let cell = match self.cell(row, column) {
764            Some(c) => c,
765            None => return Err("Cell does not exist.".to_string()),
766        };
767        match cell {
768            Cell::ArrayFormula { r, .. } => Ok((r.0, r.1)),
769            Cell::CellFormula { .. } => Ok((1, 1)),
770            _ => Err("Cell does not contain a formula.".to_string()),
771        }
772    }
773
774    /// Returns true if cell is part of an array formula. This includes both anchor and spill cells.
775    pub(crate) fn get_cell_structure(
776        &self,
777        row: i32,
778        column: i32,
779    ) -> Result<CellStructure, String> {
780        if !is_valid_column_number(column) || !is_valid_row(row) {
781            return Err("Row or column is outside valid range.".to_string());
782        }
783
784        let cell = match self.cell(row, column) {
785            Some(c) => c,
786            None => return Ok(CellStructure::SingleCell),
787        };
788        match cell {
789            Cell::ArrayFormula {
790                r,
791                kind: ArrayKind::Cse,
792                ..
793            } => Ok(CellStructure::ArrayFormula { range: *r }),
794            Cell::ArrayFormula {
795                r,
796                kind: ArrayKind::Dynamic,
797                ..
798            } => Ok(CellStructure::DynamicFormula { range: *r }),
799            Cell::SpillCell { a, .. } => {
800                let anchor_cell = match self.cell(a.0, a.1) {
801                    Some(c) => c,
802                    None => return Err("Invalid spill reference".to_string()),
803                };
804                match anchor_cell {
805                    Cell::ArrayFormula {
806                        r,
807                        kind: ArrayKind::Cse,
808                        ..
809                    } => Ok(CellStructure::SpillArray {
810                        anchor: (a.0, a.1),
811                        range: *r,
812                    }),
813                    Cell::ArrayFormula {
814                        r,
815                        kind: ArrayKind::Dynamic,
816                        ..
817                    } => Ok(CellStructure::SpillDynamic {
818                        anchor: (a.0, a.1),
819                        range: *r,
820                    }),
821                    _ => Err("Spill cell does not reference an array formula".to_string()),
822                }
823            }
824            _ => Ok(CellStructure::SingleCell),
825        }
826    }
827
828    /// It provides convenient method for user navigation in the spreadsheet by jumping to edges.
829    /// Spreadsheet engines usually allow this method of navigation by using CTRL+arrows.
830    /// Behaviour summary:
831    /// - if starting cell is empty then find first non empty cell in given direction
832    /// - if starting cell is not empty, and neighbour in given direction is empty, then find
833    ///   first non empty cell in given direction
834    /// - if starting cell is not empty, and neighbour in given direction is also not empty, then
835    ///   find last non empty cell in given direction
836    pub fn navigate_to_edge_in_direction(
837        &self,
838        row: i32,
839        column: i32,
840        direction: NavigationDirection,
841    ) -> Result<(i32, i32), String> {
842        if !is_valid_column_number(column) || !is_valid_row(row) {
843            return Err("Row or column is outside valid range.".to_string());
844        }
845
846        let start_cell = (row, column);
847        let neighbour_cell = if let Some(cell) = step_in_direction(start_cell, direction) {
848            cell
849        } else {
850            return Ok((start_cell.0, start_cell.1));
851        };
852
853        if self.is_empty_cell(start_cell.0, start_cell.1)? {
854            // Find first non-empty cell or move to the end.
855            let found_cells = walk_in_direction(start_cell, direction, |(row, column)| {
856                Ok(!self.is_empty_cell(row, column)?)
857            })?;
858            Ok(match found_cells.found_cell {
859                Some(cell) => cell,
860                None => found_cells.previous_cell,
861            })
862        } else {
863            // Neighbour cell is empty     => find FIRST that is NOT empty
864            // Neighbour cell is not empty => find LAST  that is NOT empty in sequence
865            if self.is_empty_cell(neighbour_cell.0, neighbour_cell.1)? {
866                let found_cells = walk_in_direction(start_cell, direction, |(row, column)| {
867                    Ok(!self.is_empty_cell(row, column)?)
868                })?;
869                Ok(match found_cells.found_cell {
870                    Some(cell) => cell,
871                    None => found_cells.previous_cell,
872                })
873            } else {
874                let found_cells = walk_in_direction(start_cell, direction, |(row, column)| {
875                    self.is_empty_cell(row, column)
876                })?;
877                Ok(found_cells.previous_cell)
878            }
879        }
880    }
881}
882
883struct WalkFoundCells {
884    /// If cell is found, it contains coordinates of the cell, otherwise None
885    found_cell: Option<(i32, i32)>,
886    /// Previous cell in chain relative to `found_cell`.
887    /// If `found_cell` is None then it's last considered cell.
888    previous_cell: (i32, i32),
889}
890
891/// Walks in direction until condition is met or boundary reached.
892/// Returns tuple `(current_cell, previous_cell)`. `current_cell` is either None or passes predicate
893fn walk_in_direction<F>(
894    start_cell: (i32, i32),
895    direction: NavigationDirection,
896    predicate: F,
897) -> Result<WalkFoundCells, String>
898where
899    F: Fn((i32, i32)) -> Result<bool, String>,
900{
901    let mut previous_cell = start_cell;
902    let mut current_cell = step_in_direction(start_cell, direction);
903    while let Some(cell) = current_cell {
904        if !predicate((cell.0, cell.1))? {
905            previous_cell = cell;
906            current_cell = step_in_direction(cell, direction);
907        } else {
908            break;
909        }
910    }
911    Ok(WalkFoundCells {
912        found_cell: current_cell,
913        previous_cell,
914    })
915}
916
917/// Returns coordinate of cell in given direction from given cell.
918/// Returns `None` if steps over the edge.
919fn step_in_direction(
920    (row, column): (i32, i32),
921    direction: NavigationDirection,
922) -> Option<(i32, i32)> {
923    if (row == 1 && direction == NavigationDirection::Up)
924        || (row == LAST_ROW && direction == NavigationDirection::Down)
925        || (column == 1 && direction == NavigationDirection::Left)
926        || (column == LAST_COLUMN && direction == NavigationDirection::Right)
927    {
928        return None;
929    }
930
931    Some(match direction {
932        NavigationDirection::Left => (row, column - 1),
933        NavigationDirection::Right => (row, column + 1),
934        NavigationDirection::Up => (row - 1, column),
935        NavigationDirection::Down => (row + 1, column),
936    })
937}