use std::sync::Mutex;
use formualizer_common::{ExcelError, LiteralValue};
use formualizer_parse::parser::{ReferenceType, TableReference};
use rustc_hash::{FxHashMap, FxHashSet};
use crate::engine::eval::Engine;
use crate::engine::range_view::RangeView;
use crate::reference::{CellRef, SheetId};
use crate::traits::{
EvaluationContext, FunctionProvider, NamedRangeResolver, Range, RangeResolver, ReferenceInfo,
ReferenceResolver, Resolver, SourceResolver, Table, TableResolver,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct MemberCell {
sheet_id: SheetId,
row: u32,
col: u32,
}
#[derive(Default)]
struct CollectorState {
current: Option<u32>,
edges: FxHashSet<(u32, u32)>,
}
pub struct LiveEdgeCollector {
members: Vec<MemberCell>,
index: FxHashMap<(SheetId, u32, u32), u32>,
name_index: FxHashMap<String, u32>,
total_members: usize,
state: Mutex<CollectorState>,
}
impl LiveEdgeCollector {
pub fn new(members: &[CellRef]) -> Self {
Self::new_with_names(members, &[])
}
pub fn new_with_names(cells: &[CellRef], names: &[String]) -> Self {
let members: Vec<MemberCell> = cells
.iter()
.map(|c| MemberCell {
sheet_id: c.sheet_id,
row: c.coord.row(),
col: c.coord.col(),
})
.collect();
let mut index = FxHashMap::default();
index.reserve(members.len());
for (i, m) in members.iter().enumerate() {
index.insert((m.sheet_id, m.row, m.col), i as u32);
}
let mut name_index = FxHashMap::default();
name_index.reserve(names.len());
for (j, name) in names.iter().enumerate() {
name_index.insert(name.clone(), (members.len() + j) as u32);
}
let total_members = members.len() + names.len();
Self {
members,
index,
name_index,
total_members,
state: Mutex::new(CollectorState::default()),
}
}
pub fn member_count(&self) -> usize {
self.total_members
}
pub fn set_current(&self, member_idx: u32) {
debug_assert!((member_idx as usize) < self.total_members);
self.state.lock().unwrap().current = Some(member_idx);
}
pub fn clear_current(&self) {
self.state.lock().unwrap().current = None;
}
pub fn record_scalar(&self, sheet_id: SheetId, row: u32, col: u32) {
let Some(&to) = self.index.get(&(sheet_id, row, col)) else {
return;
};
let mut st = self.state.lock().unwrap();
if let Some(from) = st.current {
st.edges.insert((from, to));
}
}
pub fn record_rect(&self, sheet_id: SheetId, sr: u32, sc: u32, er: u32, ec: u32) {
let mut st = self.state.lock().unwrap();
let Some(from) = st.current else {
return;
};
for (i, m) in self.members.iter().enumerate() {
if m.sheet_id == sheet_id && m.row >= sr && m.row <= er && m.col >= sc && m.col <= ec {
st.edges.insert((from, i as u32));
}
}
}
pub fn record_name(&self, folded_name: &str) {
let Some(&to) = self.name_index.get(folded_name) else {
return;
};
let mut st = self.state.lock().unwrap();
if let Some(from) = st.current {
st.edges.insert((from, to));
}
}
pub fn take_edges(&self) -> FxHashSet<(u32, u32)> {
std::mem::take(&mut self.state.lock().unwrap().edges)
}
}
pub struct RecordingContext<'a, R: EvaluationContext> {
engine: &'a Engine<R>,
collector: &'a LiveEdgeCollector,
}
impl<'a, R: EvaluationContext> RecordingContext<'a, R> {
pub fn new(engine: &'a Engine<R>, collector: &'a LiveEdgeCollector) -> Self {
Self { engine, collector }
}
fn record_name(&self, raw_name: &str) {
let key = self.engine.graph.name_lookup_key(raw_name);
self.collector.record_name(&key);
}
fn record_cell_1based(&self, sheet_name: &str, row: u32, col: u32) {
if row == 0 || col == 0 {
return;
}
if let Some(sid) = self.engine.sheet_id(sheet_name) {
self.collector.record_scalar(sid, row - 1, col - 1);
}
}
fn record_view(&self, view: &RangeView<'_>) {
if view.is_empty() {
return;
}
if let Some(sid) = self.engine.sheet_id(view.sheet_name()) {
self.collector.record_rect(
sid,
view.start_row() as u32,
view.start_col() as u32,
view.end_row() as u32,
view.end_col() as u32,
);
}
}
}
impl<'a, R: EvaluationContext> ReferenceResolver for RecordingContext<'a, R> {
fn resolve_cell_reference(
&self,
sheet: Option<&str>,
row: u32,
col: u32,
) -> Result<LiteralValue, ExcelError> {
if let Some(sheet_name) = sheet {
self.record_cell_1based(sheet_name, row, col);
}
self.engine.resolve_cell_reference(sheet, row, col)
}
}
impl<'a, R: EvaluationContext> RangeResolver for RecordingContext<'a, R> {
fn resolve_range_reference(
&self,
sheet: Option<&str>,
sr: Option<u32>,
sc: Option<u32>,
er: Option<u32>,
ec: Option<u32>,
) -> Result<Box<dyn Range>, ExcelError> {
if let Some(sheet_name) = sheet {
let reference = ReferenceType::Range {
sheet: Some(sheet_name.to_string()),
start_row: sr,
start_col: sc,
end_row: er,
end_col: ec,
start_row_abs: true,
start_col_abs: true,
end_row_abs: true,
end_col_abs: true,
};
if let Ok(view) = self.engine.resolve_range_view(&reference, sheet_name) {
self.record_view(&view);
}
}
self.engine.resolve_range_reference(sheet, sr, sc, er, ec)
}
}
impl<'a, R: EvaluationContext> NamedRangeResolver for RecordingContext<'a, R> {
fn resolve_named_range_reference(
&self,
name: &str,
) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
self.record_name(name);
self.engine.resolve_named_range_reference(name)
}
}
impl<'a, R: EvaluationContext> TableResolver for RecordingContext<'a, R> {
fn resolve_table_reference(&self, tref: &TableReference) -> Result<Box<dyn Table>, ExcelError> {
self.engine.resolve_table_reference(tref)
}
}
impl<'a, R: EvaluationContext> SourceResolver for RecordingContext<'a, R> {
fn source_scalar_version(&self, name: &str) -> Option<u64> {
self.engine.source_scalar_version(name)
}
fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
self.engine.resolve_source_scalar(name)
}
fn source_table_version(&self, name: &str) -> Option<u64> {
self.engine.source_table_version(name)
}
fn resolve_source_table(&self, name: &str) -> Result<Box<dyn Table>, ExcelError> {
self.engine.resolve_source_table(name)
}
}
impl<'a, R: EvaluationContext> Resolver for RecordingContext<'a, R> {}
impl<'a, R: EvaluationContext> FunctionProvider for RecordingContext<'a, R> {
fn get_function(
&self,
ns: &str,
name: &str,
) -> Option<std::sync::Arc<dyn crate::traits::Function>> {
self.engine.get_function(ns, name)
}
}
impl<'a, R: EvaluationContext> EvaluationContext for RecordingContext<'a, R> {
fn resolve_range_view<'c>(
&'c self,
reference: &ReferenceType,
current_sheet: &str,
) -> Result<RangeView<'c>, ExcelError> {
if let ReferenceType::NamedRange(name) = reference {
self.record_name(name);
}
let view = self.engine.resolve_range_view(reference, current_sheet)?;
self.record_view(&view);
Ok(view)
}
fn resolve_cell_reference_value(
&self,
sheet: Option<&str>,
row: u32,
col: u32,
current_sheet: &str,
) -> Result<LiteralValue, ExcelError> {
self.record_cell_1based(sheet.unwrap_or(current_sheet), row, col);
self.engine
.resolve_cell_reference_value(sheet, row, col, current_sheet)
}
fn thread_pool(&self) -> Option<&std::sync::Arc<rayon::ThreadPool>> {
self.engine.thread_pool()
}
fn cancellation_token(&self) -> Option<std::sync::Arc<std::sync::atomic::AtomicBool>> {
self.engine.cancellation_token()
}
fn chunk_hint(&self) -> Option<usize> {
self.engine.chunk_hint()
}
fn locale(&self) -> crate::locale::Locale {
self.engine.locale()
}
fn workbook_sheet_count(&self) -> Option<usize> {
self.engine.workbook_sheet_count()
}
fn sheet_index_by_name(&self, sheet: &str) -> Option<usize> {
self.engine.sheet_index_by_name(sheet)
}
fn current_sheet_index(&self, current_sheet: &str) -> Option<usize> {
self.engine.current_sheet_index(current_sheet)
}
fn inspect_reference(
&self,
reference: &ReferenceType,
current_sheet: &str,
) -> Result<Option<ReferenceInfo>, ExcelError> {
self.engine.inspect_reference(reference, current_sheet)
}
fn formula_text_at_cell(&self, cell: CellRef) -> Result<Option<String>, ExcelError> {
self.engine.formula_text_at_cell(cell)
}
fn clock(&self) -> &dyn crate::timezone::ClockProvider {
self.engine.clock()
}
fn timezone(&self) -> &crate::timezone::TimeZoneSpec {
self.engine.timezone()
}
fn volatile_level(&self) -> crate::traits::VolatileLevel {
self.engine.volatile_level()
}
fn workbook_seed(&self) -> u64 {
self.engine.workbook_seed()
}
fn recalc_epoch(&self) -> u64 {
self.engine.recalc_epoch()
}
fn used_rows_for_columns(
&self,
sheet: &str,
start_col: u32,
end_col: u32,
) -> Option<(u32, u32)> {
self.engine.used_rows_for_columns(sheet, start_col, end_col)
}
fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
self.engine.used_cols_for_rows(sheet, start_row, end_row)
}
fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
self.engine.sheet_bounds(sheet)
}
fn data_snapshot_id(&self) -> u64 {
self.engine.data_snapshot_id()
}
fn backend_caps(&self) -> crate::traits::BackendCaps {
self.engine.backend_caps()
}
fn date_system(&self) -> crate::engine::DateSystem {
self.engine.date_system()
}
fn build_lookup_index(
&self,
view: &RangeView<'_>,
axis: crate::engine::lookup_index_cache::LookupAxis,
) -> Option<std::sync::Arc<crate::engine::lookup_index_cache::LookupIndex>> {
self.engine.build_lookup_index(view, axis)
}
fn build_criteria_mask(
&self,
view: &RangeView<'_>,
col_in_view: usize,
pred: &crate::args::CriteriaPredicate,
) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
self.engine.build_criteria_mask(view, col_in_view, pred)
}
fn build_row_visibility_mask(
&self,
view: &RangeView<'_>,
mode: crate::engine::row_visibility::VisibilityMaskMode,
) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
self.engine.build_row_visibility_mask(view, mode)
}
}