1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use crate::{
    expressions::{
        parser::{
            move_formula::ref_is_in_area,
            stringify::{to_string, to_string_displaced, DisplaceData},
            walk::forward_references,
        },
        types::{Area, CellReferenceIndex, CellReferenceRC},
    },
    model::Model,
};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged, deny_unknown_fields)]
pub enum CellValue {
    Value(String),
    None,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct SetCellValue {
    cell: CellReferenceIndex,
    new_value: CellValue,
    old_value: CellValue,
}

impl Model {
    pub(crate) fn shift_cell_formula(
        &mut self,
        sheet: u32,
        row: i32,
        column: i32,
        displace_data: &DisplaceData,
    ) {
        if let Some(f) = self
            .workbook
            .worksheet(sheet)
            .expect("Worksheet must exist")
            .cell(row, column)
            .expect("Cell must exist")
            .get_formula()
        {
            let node = &self.parsed_formulas[sheet as usize][f as usize].clone();
            let cell_reference = CellReferenceRC {
                sheet: self.workbook.worksheets[sheet as usize].get_name(),
                row,
                column,
            };
            // FIXME: This is not a very performant way if the formula has changed :S.
            let formula = to_string(node, &cell_reference);
            let formula_displaced = to_string_displaced(node, &cell_reference, displace_data);
            if formula != formula_displaced {
                self.update_cell_with_formula(sheet, row, column, format!("={formula_displaced}"))
                    .expect("Failed to shift cell formula");
            }
        }
    }

    pub fn forward_references(
        &mut self,
        source_area: &Area,
        target: &CellReferenceIndex,
    ) -> Result<Vec<SetCellValue>, String> {
        let mut diff_list: Vec<SetCellValue> = Vec::new();
        let target_area = &Area {
            sheet: target.sheet,
            row: target.row,
            column: target.column,
            width: source_area.width,
            height: source_area.height,
        };
        // Walk over every formula
        let cells = self.get_all_cells();
        for cell in cells {
            if let Some(f) = self
                .workbook
                .worksheet(cell.index)
                .expect("Worksheet must exist")
                .cell(cell.row, cell.column)
                .expect("Cell must exist")
                .get_formula()
            {
                let sheet = cell.index;
                let row = cell.row;
                let column = cell.column;

                // If cell is in the source or target area, skip
                if ref_is_in_area(sheet, row, column, source_area)
                    || ref_is_in_area(sheet, row, column, target_area)
                {
                    continue;
                }

                // Get the formula
                // Get a copy of the AST
                let node = &mut self.parsed_formulas[sheet as usize][f as usize].clone();
                let cell_reference = CellReferenceRC {
                    sheet: self.workbook.worksheets[sheet as usize].get_name(),
                    column: cell.column,
                    row: cell.row,
                };
                let context = CellReferenceIndex { sheet, column, row };
                let formula = to_string(node, &cell_reference);
                let target_sheet_name = &self.workbook.worksheets[target.sheet as usize].name;
                forward_references(
                    node,
                    &context,
                    source_area,
                    target.sheet,
                    target_sheet_name,
                    target.row,
                    target.column,
                );

                // If the string representation of the formula has changed update the cell
                let updated_formula = to_string(node, &cell_reference);
                if formula != updated_formula {
                    self.update_cell_with_formula(
                        sheet,
                        row,
                        column,
                        format!("={updated_formula}"),
                    )?;
                    // Update the diff list
                    diff_list.push(SetCellValue {
                        cell: CellReferenceIndex { sheet, column, row },
                        new_value: CellValue::Value(format!("={}", updated_formula)),
                        old_value: CellValue::Value(format!("={}", formula)),
                    });
                }
            }
        }
        Ok(diff_list)
    }
}