use std::{fmt::Debug, sync::Arc};
use crate::{
error::{Error, ErrorValue, Result},
excel::{borrowed::Borrowed, mapper::Mapper, metadata::SheetMetadata, row::Row},
file::{exd, exh},
utility::{HashMapCache, HashMapCacheExt, OptionCache, OptionCacheExt},
Ironworks,
};
use super::row_options::RowOptions;
const LANGUAGE_NONE: u8 = 0;
pub struct Sheet<'i, S> {
sheet_metadata: S,
default_language: u8,
ironworks: Borrowed<'i, Ironworks>,
mapper: &'i dyn Mapper,
header: OptionCache<exh::ExcelHeader>,
pages: HashMapCache<(u32, u8), exd::ExcelData>,
}
impl<S: Debug> Debug for Sheet<'_, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Sheet")
.field("sheet_metadata", &self.sheet_metadata)
.field("default_language", &self.default_language)
.finish()
}
}
impl<'i, S: SheetMetadata> Sheet<'i, S> {
pub(crate) fn new(
sheet_metadata: S,
default_language: u8,
ironworks: Borrowed<'i, Ironworks>,
mapper: &'i dyn Mapper,
) -> Self {
Self {
sheet_metadata,
default_language,
ironworks,
mapper,
header: Default::default(),
pages: Default::default(),
}
}
pub fn kind(&self) -> Result<exh::SheetKind> {
let kind = self.header()?.kind();
Ok(kind)
}
pub fn columns(&self) -> Result<Vec<exh::ColumnDefinition>> {
let columns = self.header()?.columns().clone();
Ok(columns)
}
pub fn with(&'i self) -> RowOptions<'i, S> {
RowOptions::new(self)
}
pub fn row(&self, row_id: u32) -> Result<S::Row> {
self.row_with_options(row_id, &Default::default())
}
pub fn subrow(&self, row_id: u32, subrow_id: u16) -> Result<S::Row> {
self.subrow_with_options(row_id, subrow_id, &Default::default())
}
pub(super) fn row_with_options(
&self,
row_id: u32,
options: &RowOptions<'i, S>,
) -> Result<S::Row> {
self.subrow_with_options(row_id, 0, options)
}
pub(super) fn subrow_with_options(
&self,
row_id: u32,
subrow_id: u16,
options: &RowOptions<'i, S>,
) -> Result<S::Row> {
let header = self.header()?;
let row_error_value = || ErrorValue::Row {
row: row_id,
subrow: subrow_id,
sheet: self.sheet_metadata.name().into(),
};
let row_not_found = || Error::NotFound(row_error_value());
if header.kind() != exh::SheetKind::Subrows && subrow_id > 0 {
return Err(row_not_found());
}
let requested_language = options.language.unwrap_or(self.default_language);
let language = *header
.languages()
.get(&requested_language)
.or_else(|| header.languages().get(&LANGUAGE_NONE))
.ok_or_else(|| {
Error::NotFound(ErrorValue::Other(format!("language {requested_language}")))
})?;
let start_id = header
.pages()
.iter()
.find(|page| page.start_id() <= row_id && page.start_id() + page.row_count() > row_id)
.ok_or_else(row_not_found)?
.start_id();
let page = self.pages.try_get_or_insert((start_id, language), || {
let path = self
.mapper
.exd(&self.sheet_metadata.name(), start_id, language);
self.ironworks.file(&path)
})?;
let data = match header.kind() {
exh::SheetKind::Subrows => page.subrow_data(row_id, subrow_id),
_ => page.row_data(row_id),
}?;
let row = Row::new(row_id, subrow_id, header, data.to_vec());
self.sheet_metadata
.populate_row(row)
.map_err(|error| Error::Invalid(row_error_value(), error.to_string()))
}
fn header(&self) -> Result<Arc<exh::ExcelHeader>> {
self.header.try_get_or_insert(|| {
let path = self.mapper.exh(&self.sheet_metadata.name());
self.ironworks.file(&path)
})
}
}