use imbl::HashMap;
use logisheets_base::id_fetcher::IdFetcherTrait;
use logisheets_base::index_fetcher::IndexFetcherTrait;
use logisheets_base::{BlockCellId, BlockId, BlockRange, NormalCellId, NormalRange, SheetId};
use crate::navigator::Navigator;
pub trait LinkNav {
fn norm_idx(&self, sheet: SheetId, c: &NormalCellId) -> Option<(usize, usize)>;
fn block_idx(&self, sheet: SheetId, c: &BlockCellId) -> Option<(usize, usize)>;
fn block_cell(
&self,
sheet: SheetId,
block_id: BlockId,
row: usize,
col: usize,
) -> Option<BlockCellId>;
}
pub struct NavLink<'a>(pub &'a Navigator);
impl LinkNav for NavLink<'_> {
fn norm_idx(&self, sheet: SheetId, c: &NormalCellId) -> Option<(usize, usize)> {
self.0.fetch_normal_cell_idx(&sheet, c).ok()
}
fn block_idx(&self, sheet: SheetId, c: &BlockCellId) -> Option<(usize, usize)> {
self.0.fetch_block_cell_idx(&sheet, c).ok()
}
fn block_cell(
&self,
sheet: SheetId,
block_id: BlockId,
row: usize,
col: usize,
) -> Option<BlockCellId> {
self.0.fetch_block_cell_id(&sheet, &block_id, row, col).ok()
}
}
pub struct CtxLink<'a, C>(pub &'a C);
impl<C: IndexFetcherTrait + IdFetcherTrait> LinkNav for CtxLink<'_, C> {
fn norm_idx(&self, sheet: SheetId, c: &NormalCellId) -> Option<(usize, usize)> {
self.0.fetch_normal_cell_index(&sheet, c).ok()
}
fn block_idx(&self, sheet: SheetId, c: &BlockCellId) -> Option<(usize, usize)> {
self.0.fetch_block_cell_index(&sheet, c).ok()
}
fn block_cell(
&self,
sheet: SheetId,
block_id: BlockId,
row: usize,
col: usize,
) -> Option<BlockCellId> {
self.0.fetch_block_cell_id(&sheet, &block_id, row, col).ok()
}
}
fn normal_corners(r: &NormalRange) -> Option<(NormalCellId, NormalCellId)> {
match r {
NormalRange::Single(c) => Some((*c, *c)),
NormalRange::AddrRange(a, b) => Some((*a, *b)),
NormalRange::RowRange(..) | NormalRange::ColRange(..) => None,
}
}
fn block_corners(r: &BlockRange) -> (BlockCellId, BlockCellId) {
match r {
BlockRange::Single(c) => (*c, *c),
BlockRange::AddrRange(a, b) => (*a, *b),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkRef {
Column { sheet: SheetId, block: BlockRange },
Invalid,
Passthrough,
}
pub fn classify<N: LinkNav>(
nav: &N,
src_sheet: SheetId,
tgt_sheet: SheetId,
source: &NormalRange,
target: &BlockRange,
query: &NormalRange,
) -> Option<LinkRef> {
let (q0, q1) = normal_corners(query)?;
let (s0, s1) = normal_corners(source)?;
let (qr0, qc0) = nav.norm_idx(src_sheet, &q0)?;
let (qr1, qc1) = nav.norm_idx(src_sheet, &q1)?;
let (sr0, sc0) = nav.norm_idx(src_sheet, &s0)?;
let (sr1, sc1) = nav.norm_idx(src_sheet, &s1)?;
let (qr0, qr1) = (qr0.min(qr1), qr0.max(qr1));
let (qc0, qc1) = (qc0.min(qc1), qc0.max(qc1));
let (sr0, sr1) = (sr0.min(sr1), sr0.max(sr1));
let (sc0, sc1) = (sc0.min(sc1), sc0.max(sc1));
if qr1 < sr0 || qr0 > sr1 || qc1 < sc0 || qc0 > sc1 {
return None;
}
let valid = qc0 == qc1 && qc0 >= sc0 && qc0 <= sc1 && qr0 == sr0 && qr1 == sr1;
if !valid {
return Some(LinkRef::Invalid);
}
let (b0, b1) = block_corners(target);
let block_id = b0.block_id;
let (br0, _) = nav.block_idx(tgt_sheet, &b0)?;
let (br1, _) = nav.block_idx(tgt_sheet, &b1)?;
let block_inner_bottom = br0.max(br1) - br0.min(br1);
let inner_col = qc0 - sc0;
let top = nav.block_cell(tgt_sheet, block_id, 0, inner_col)?;
let bottom = nav.block_cell(tgt_sheet, block_id, block_inner_bottom, inner_col)?;
Some(LinkRef::Column {
sheet: tgt_sheet,
block: BlockRange::AddrRange(top, bottom),
})
}
pub fn resolve_normal<N: LinkNav>(
nav: &N,
src_sheet: SheetId,
links: &HashMap<NormalRange, (SheetId, BlockRange)>,
query: &NormalRange,
) -> LinkRef {
links
.iter()
.find_map(|(source, (tgt_sheet, target))| {
classify(nav, src_sheet, *tgt_sheet, source, target, query)
})
.unwrap_or(LinkRef::Passthrough)
}