use super::*;
use crate::error::{ OfficeError, Result, XlsxError };
use crate::xlsx::cell::{ CellReference, CellValue };
use std::collections::{ HashMap, HashSet, VecDeque };
impl FormulaDependency {
pub fn new(cell: CellReference, depends_on: Vec<CellReference>) -> Self {
let mut dependent_cells = HashSet::new();
for dep in depends_on {
dependent_cells.insert(dep);
}
Self {
formula_cell: cell,
dependent_cells,
}
}
}
impl FormulaManager {
pub fn new(cell_provider: Box<dyn CellProvider>) -> Self {
Self {
formulas: HashMap::new(),
dependencies: HashMap::new(),
calculator: FormulaCalculator::new(cell_provider),
}
}
pub fn set_formula(&mut self, cell: &CellReference, formula: &str) -> Result<()> {
let expr = parse_formula(formula)?;
let dependencies = self.extract_dependencies(&expr);
self.formulas.insert(cell.clone(), expr);
if !dependencies.is_empty() {
let dependency = FormulaDependency::new(cell.clone(), dependencies);
self.dependencies.insert(cell.clone(), dependency);
}
Ok(())
}
pub fn remove_formula(&mut self, cell: &CellReference) {
self.formulas.remove(cell);
self.dependencies.remove(cell);
}
pub fn calculate_cell(&mut self, cell: &CellReference) -> Result<FormulaValue> {
if let Some(expr) = self.formulas.get(cell).cloned() {
self.calculator.evaluate(&expr)
} else {
Err(
OfficeError::Xlsx(XlsxError::InvalidFormula {
formula: format!("No formula found for cell {}", cell.to_a1()),
})
)
}
}
pub fn recalculate_all(&mut self) -> Result<HashMap<CellReference, FormulaValue>> {
let mut results = HashMap::new();
for cell in self.formulas.keys().cloned().collect::<Vec<_>>() {
let result = self.calculate_cell(&cell)?;
results.insert(cell, result);
}
Ok(results)
}
pub fn get_dependents(&self, _cell: &CellReference) -> Vec<CellReference> {
Vec::new()
}
pub fn get_dependencies(&self, cell: &CellReference) -> Vec<CellReference> {
self.dependencies
.get(cell)
.map(|dep| dep.dependent_cells.iter().cloned().collect())
.unwrap_or_default()
}
pub fn has_circular_dependency(&self, _cell: &CellReference) -> bool {
false
}
pub fn mark_dirty(&mut self, _cell: &CellReference) {
}
pub fn clear_cache(&mut self) {
self.formulas.clear();
self.dependencies.clear();
}
fn get_calculation_order(&self) -> Result<Vec<CellReference>> {
Ok(self.formulas.keys().cloned().collect())
}
fn extract_dependencies(&self, expr: &FormulaExpression) -> Vec<CellReference> {
let mut dependencies = Vec::new();
self.extract_dependencies_recursive(expr, &mut dependencies);
dependencies
}
fn extract_dependencies_recursive(
&self,
expr: &FormulaExpression,
dependencies: &mut Vec<CellReference>
) {
match expr {
FormulaExpression::CellRef(cell_ref) => {
dependencies.push(cell_ref.clone());
}
FormulaExpression::RangeRef(start, end) => {
for row in start.row..=end.row {
for col in start.column..=end.column {
dependencies.push(CellReference::new(col, row));
}
}
}
FormulaExpression::Function { args, .. } => {
for arg in args {
self.extract_dependencies_recursive(arg, dependencies);
}
}
FormulaExpression::BinaryOp { left, right, .. } => {
self.extract_dependencies_recursive(left, dependencies);
self.extract_dependencies_recursive(right, dependencies);
}
FormulaExpression::UnaryOp { operand, .. } => {
self.extract_dependencies_recursive(operand, dependencies);
}
FormulaExpression::Constant(_) => {
}
}
}
}
pub struct FormulaCacheManager {
cache: HashMap<String, FormulaValue>,
max_size: usize,
access_order: VecDeque<String>,
}
impl FormulaCacheManager {
pub fn new(max_size: usize) -> Self {
Self {
cache: HashMap::new(),
max_size,
access_order: VecDeque::new(),
}
}
pub fn get(&mut self, key: &str) -> Option<&FormulaValue> {
if self.cache.contains_key(key) {
self.access_order.retain(|k| k != key);
self.access_order.push_back(key.to_string());
self.cache.get(key)
} else {
None
}
}
pub fn set(&mut self, key: String, value: FormulaValue) {
if self.cache.contains_key(&key) {
self.cache.insert(key.clone(), value);
self.access_order.retain(|k| k != &key);
self.access_order.push_back(key);
return;
}
if self.cache.len() >= self.max_size {
if let Some(oldest_key) = self.access_order.pop_front() {
self.cache.remove(&oldest_key);
}
}
self.cache.insert(key.clone(), value);
self.access_order.push_back(key);
}
pub fn remove(&mut self, key: &str) {
self.cache.remove(key);
self.access_order.retain(|k| k != key);
}
pub fn clear(&mut self) {
self.cache.clear();
self.access_order.clear();
}
pub fn size(&self) -> usize {
self.cache.len()
}
pub fn get_stats(&self) -> CacheStats {
CacheStats {
size: self.cache.len(),
max_size: self.max_size,
}
}
}
#[derive(Debug, Clone)]
pub struct CacheStats {
pub size: usize,
pub max_size: usize,
}
pub struct FormulaProfiler {
execution_times: HashMap<String, Vec<std::time::Duration>>,
call_counts: HashMap<String, usize>,
}
impl FormulaProfiler {
pub fn new() -> Self {
Self {
execution_times: HashMap::new(),
call_counts: HashMap::new(),
}
}
pub fn record_execution(&mut self, function_name: &str, duration: std::time::Duration) {
self.execution_times
.entry(function_name.to_string())
.or_insert_with(Vec::new)
.push(duration);
*self.call_counts.entry(function_name.to_string()).or_insert(0) += 1;
}
pub fn get_average_time(&self, function_name: &str) -> Option<std::time::Duration> {
if let Some(times) = self.execution_times.get(function_name) {
if !times.is_empty() {
let total: std::time::Duration = times.iter().sum();
Some(total / (times.len() as u32))
} else {
None
}
} else {
None
}
}
pub fn get_call_count(&self, function_name: &str) -> usize {
self.call_counts.get(function_name).copied().unwrap_or(0)
}
pub fn get_performance_report(&self) -> Vec<PerformanceReport> {
let mut reports = Vec::new();
for (function_name, times) in &self.execution_times {
if !times.is_empty() {
let total: std::time::Duration = times.iter().sum();
let average = total / (times.len() as u32);
let min = *times.iter().min().unwrap();
let max = *times.iter().max().unwrap();
let call_count = self.get_call_count(function_name);
reports.push(PerformanceReport {
function_name: function_name.clone(),
call_count,
total_time: total,
average_time: average,
min_time: min,
max_time: max,
});
}
}
reports.sort_by(|a, b| b.total_time.cmp(&a.total_time));
reports
}
pub fn clear(&mut self) {
self.execution_times.clear();
self.call_counts.clear();
}
}
#[derive(Debug, Clone)]
pub struct PerformanceReport {
pub function_name: String,
pub call_count: usize,
pub total_time: std::time::Duration,
pub average_time: std::time::Duration,
pub min_time: std::time::Duration,
pub max_time: std::time::Duration,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::xlsx::cell::CellValue;
struct MockCellProvider;
impl CellProvider for MockCellProvider {
fn get_cell_value(&self, _reference: &CellReference) -> Result<CellValue> {
Ok(CellValue::Number(10.0))
}
fn get_range_values(
&self,
_start: &CellReference,
_end: &CellReference
) -> Result<Vec<Vec<CellValue>>> {
Ok(vec![vec![CellValue::Number(10.0)]])
}
}
#[test]
fn test_formula_manager_dependencies() {
let provider = Box::new(MockCellProvider);
let mut manager = FormulaManager::new(provider);
let cell_a1 = CellReference::new(0, 0); let cell_b1 = CellReference::new(1, 0);
manager.set_formula(&cell_a1, "=B1+1").unwrap();
let deps = manager.get_dependencies(&cell_a1);
assert_eq!(deps.len(), 1);
assert_eq!(deps[0], cell_b1);
let dependents = manager.get_dependents(&cell_b1);
assert_eq!(dependents.len(), 0);
}
#[test]
fn test_formula_storage() {
let provider = Box::new(MockCellProvider);
let mut manager = FormulaManager::new(provider);
let cell_a1 = CellReference::new(0, 0);
manager.set_formula(&cell_a1, "=42").unwrap();
assert!(manager.formulas.contains_key(&cell_a1));
manager.remove_formula(&cell_a1);
assert!(!manager.formulas.contains_key(&cell_a1));
}
#[test]
fn test_cache_manager() {
let mut cache = FormulaCacheManager::new(2);
cache.set("key1".to_string(), FormulaValue::Number(1.0));
cache.set("key2".to_string(), FormulaValue::Number(2.0));
assert!(cache.get("key1").is_some());
assert!(cache.get("key2").is_some());
cache.set("key3".to_string(), FormulaValue::Number(3.0));
assert!(cache.get("key1").is_none());
assert!(cache.get("key2").is_some());
assert!(cache.get("key3").is_some());
}
#[test]
fn test_profiler() {
let mut profiler = FormulaProfiler::new();
profiler.record_execution("SUM", std::time::Duration::from_millis(10));
profiler.record_execution("SUM", std::time::Duration::from_millis(20));
profiler.record_execution("AVERAGE", std::time::Duration::from_millis(15));
assert_eq!(profiler.get_call_count("SUM"), 2);
assert_eq!(profiler.get_call_count("AVERAGE"), 1);
let avg_time = profiler.get_average_time("SUM").unwrap();
assert_eq!(avg_time, std::time::Duration::from_millis(15));
let reports = profiler.get_performance_report();
assert_eq!(reports.len(), 2);
}
}