use std::sync::RwLock;
use getset::{CopyGetters, Getters, MutGetters, Setters};
use rayon::{iter::{IntoParallelIterator, ParallelIterator}, slice::ParallelSliceMut};
use crate::{codex::Codex, dossier::document::chapter::{chapter_header::ChapterHeader, paragraph::Paragraph}, load::{LoadConfiguration, LoadConfigurationOverLay, LoadError}};
#[derive(Debug, Getters, CopyGetters, MutGetters, Setters)]
pub struct LoadBlock {
#[getset(get_copy = "pub", set = "pub")]
start: usize,
#[getset(get_copy = "pub", set = "pub")]
end: usize,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
content: LoadBlockContent
}
impl LoadBlock {
pub fn new(start: usize, end: usize, content: LoadBlockContent) -> Self {
Self {
start,
end,
content,
}
}
pub fn load_from_str(content: &str, codex: &Codex, configuration: &LoadConfiguration, configuration_overlay: LoadConfigurationOverLay) -> Result<Vec<LoadBlock>, LoadError> {
Self::inner_load_from_str(content, 0, codex, 0, configuration, configuration_overlay.clone())
}
fn inner_load_from_str(current_content: &str, offset: usize, codex: &Codex, paragraph_modifier_index: usize, configuration: &LoadConfiguration, configuration_overlay: LoadConfigurationOverLay) -> Result<Vec<LoadBlock>, LoadError> {
let position_in_global_content = |position_in_current: usize| position_in_current + offset;
if let Some((modifier_identifier, (paragraph_modifier, paragraph_loading_rule))) = codex.paragraph_modifiers().get_index(paragraph_modifier_index) {
log::debug!("load using {}", modifier_identifier);
let mut current_paragraph_blocks: Vec<LoadBlock> = Vec::new();
let mut unmatched_slices: Vec<(usize, &str)> = Vec::new();
let mut last_position: usize = 0;
for m in paragraph_modifier.modifier_pattern_regex().find_iter(current_content) {
assert!(!m.is_empty());
let m_start = m.start();
let m_end = m.end();
log::debug!("match found between {} and {}", m_start, m_end);
if m_start > last_position {
unmatched_slices.push((position_in_global_content(last_position), ¤t_content[last_position..m_start]));
}
last_position = m_end;
let paragraph = paragraph_loading_rule.load(m.as_str(), codex, configuration, configuration_overlay.clone())?;
if !paragraph.is_empty() {
let block = LoadBlock::new(
position_in_global_content(m_start),
position_in_global_content(m_end),
LoadBlockContent::Paragraph(paragraph)
);
log::debug!("added block:\n{:#?}", block);
current_paragraph_blocks.push(block);
}
}
if current_content.len() > last_position {
unmatched_slices.push((position_in_global_content(last_position), ¤t_content[last_position..]));
}
if configuration.parallelization() {
let unmatched_slices_blocks: RwLock<Vec<LoadBlock>> = RwLock::new(Vec::new());
let errors: Vec<LoadError> = unmatched_slices.into_par_iter().map(|(offset, unmatched_slice)| -> Result<(), LoadError> {
log::debug!("no matches using paragraph modifier {} on:\n{}\n(offset: {})", modifier_identifier, unmatched_slice, offset);
let mut blocks = Self::inner_load_from_str(unmatched_slice, offset, codex, paragraph_modifier_index + 1, configuration, configuration_overlay.clone())?;
unmatched_slices_blocks.write().unwrap().append(&mut blocks);
Ok(())
})
.filter(|result| result.is_err())
.map(|result| result.err().unwrap())
.collect();
if errors.len() > 0 {
return Err(LoadError::BucketOfErrors(errors))
}
let mut unmatched_slices_blocks = unmatched_slices_blocks.into_inner().unwrap();
current_paragraph_blocks.append(&mut unmatched_slices_blocks);
} else {
let mut unmatched_slices_blocks: Vec<LoadBlock> = Vec::new();
for (offset, unmatched_slice) in unmatched_slices {
log::debug!("try next paragraph modifier on:\n{}\n(offset: {})", unmatched_slice, offset);
let mut blocks = Self::inner_load_from_str(unmatched_slice, offset, codex, paragraph_modifier_index + 1, configuration, configuration_overlay.clone())?;
unmatched_slices_blocks.append(&mut blocks);
}
current_paragraph_blocks.append(&mut unmatched_slices_blocks);
}
return Ok(current_paragraph_blocks)
} else {
log::debug!("next content contains headings and/or fallback paragraph:\n{}", current_content);
if codex.fallback_paragraph().is_none() {
log::warn!("there isn't fallback paragraph loading rule")
}
let mut headers_blocks = ChapterHeader::load(current_content, codex, configuration)?;
headers_blocks.par_sort_by(|a, b| a.start().cmp(&b.start()));
let mut blocks: Vec<LoadBlock> = Vec::new();
let mut add_fb_blocks = |raw_fb_paragraph: &str, start: usize, end: usize| -> Result<(), LoadError> {
if let Some((fb_id, fallback_loading_rule)) = codex.fallback_paragraph() {
log::debug!("fallback rule {} will be used to load:\n{}", fb_id, raw_fb_paragraph);
let paragraphs = fallback_loading_rule.load(raw_fb_paragraph, codex, configuration, configuration_overlay.clone())?;
let len = paragraphs.len();
assert!((end - start) > len);
for (index, paragraph) in paragraphs.into_iter().enumerate() {
let fake_start = start + ((end - start) / len * index);
let fake_end = start + ((end - start) / len * (index + 1));
let block = LoadBlock::new(
fake_start,
fake_end,
LoadBlockContent::Paragraph(paragraph)
);
log::debug!("generated fallback blocks:\n{:#?}", block);
blocks.push(block);
}
}
Ok(())
};
let mut last_position = 0;
for header_block in headers_blocks.iter_mut() {
if header_block.start() > last_position {
let start = last_position;
let global_start = position_in_global_content(last_position);
let end = header_block.start();
let global_end = position_in_global_content(header_block.start());
let s = ¤t_content[start..end];
log::debug!("found not header slice between {} (global pos: {}) and {} (global pos: {}) of current content:\n{}", start, global_start, end, global_end, s);
add_fb_blocks(
s,
global_start,
global_end
)?;
}
last_position = header_block.end();
header_block.set_start(position_in_global_content(header_block.start()));
header_block.set_end(position_in_global_content(header_block.end()));
}
log::debug!("last heading found at position (of current content): {}/{}", last_position, current_content.len());
if current_content.len() > last_position {
let s = ¤t_content[last_position..];
add_fb_blocks(
s,
position_in_global_content(last_position),
position_in_global_content(current_content.len())
)?;
}
blocks.append(&mut headers_blocks);
return Ok(blocks);
}
}
}
impl Into<LoadBlockContent> for LoadBlock {
fn into(self) -> LoadBlockContent {
self.content
}
}
impl TryInto<Box<dyn Paragraph>> for LoadBlock {
type Error = String;
fn try_into(self) -> Result<Box<dyn Paragraph>, Self::Error> {
if let LoadBlockContent::Paragraph(p) = self.content {
return Ok(p)
}
Err(String::from("this block doesn't contain a paragraph"))
}
}
#[derive(Debug)]
pub enum LoadBlockContent {
Paragraph(Box<dyn Paragraph>),
ChapterHeader(ChapterHeader)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn paragraphs_from_str() {
let content = concat!(
"paragraph1",
"\n\n",
"paragraph2a\nparagraph2b",
"\n\n",
"paragraph3",
);
let codex = Codex::of_html();
let paragraphs = LoadBlock::load_from_str(content, &codex, &LoadConfiguration::default(), LoadConfigurationOverLay::default()).unwrap();
assert_eq!(paragraphs.len(), 3)
}
}