use std::{error::Error, fmt};
use log::error;
use crate::text::{CoverTextLineIterator, CoverTextWordIterator};
pub trait Context {
fn get_current_text_mut(&mut self) -> Result<&mut String, ContextError>;
fn get_current_text(&self) -> Result<&String, ContextError>;
fn load_text(&mut self) -> Result<&str, ContextError>;
}
pub struct PivotByRawLineContext {
pivot: usize,
cover_text_iter: CoverTextLineIterator,
current_text: Option<String>,
}
pub struct PivotByLineContext {
pivot: usize,
cover_text_iter: CoverTextWordIterator,
current_text: Option<String>,
}
impl PivotByRawLineContext {
pub fn new(cover_text: &str, pivot: usize) -> Self {
PivotByRawLineContext {
pivot,
cover_text_iter: CoverTextLineIterator::new(cover_text),
current_text: None,
}
}
pub fn get_pivot(&self) -> usize {
self.pivot
}
}
impl PivotByLineContext {
const WORD_DELIMITER: char = ' ';
pub fn new(cover_text: &str, pivot: usize) -> Self {
PivotByLineContext {
pivot,
cover_text_iter: CoverTextWordIterator::new(cover_text),
current_text: None,
}
}
pub fn get_pivot(&self) -> usize {
self.pivot
}
pub fn peek_word(&mut self) -> Option<String> {
self.cover_text_iter.peek()
}
pub fn next_word(&mut self) -> Option<String> {
self.cover_text_iter.next()
}
fn construct_line_by_pivot(&mut self) -> Result<Option<String>, ContextError> {
let maybe_word = self.cover_text_iter.peek();
if maybe_word.is_none() {
return Ok(None);
}
let mut word = maybe_word.unwrap();
if word.len() > self.pivot {
error!("Stuck at word of length {}.", word.len());
return Err(ContextError { kind: ContextErrorKind::CannotConstructLine });
}
let mut line = String::new();
while line.len() + word.len() <= self.pivot {
line.push_str(&word);
line.push(Self::WORD_DELIMITER);
self.cover_text_iter.next();
if let Some(next_word) = self.cover_text_iter.peek() {
word = next_word;
} else {
return Ok(Some(line));
}
}
Ok(Some(line.trim_end().to_string()))
}
}
impl Context for PivotByLineContext {
fn get_current_text_mut(&mut self) -> Result<&mut String, ContextError> {
self.current_text
.as_mut()
.ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
}
fn get_current_text(&self) -> Result<&String, ContextError> {
self.current_text
.as_ref()
.ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
}
fn load_text(&mut self) -> Result<&str, ContextError> {
self.current_text = self.construct_line_by_pivot()?;
self.current_text
.as_deref()
.ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
}
}
impl Context for PivotByRawLineContext {
fn get_current_text_mut(&mut self) -> Result<&mut String, ContextError> {
self.current_text
.as_mut()
.ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
}
fn get_current_text(&self) -> Result<&String, ContextError> {
self.current_text
.as_ref()
.ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
}
fn load_text(&mut self) -> Result<&str, ContextError> {
self.current_text = self.cover_text_iter.next();
self.current_text
.as_deref()
.ok_or_else(|| ContextError::new(ContextErrorKind::NoTextLeft))
}
}
#[derive(Debug, Clone, Copy)]
pub enum ContextErrorKind {
NoTextLeft,
CannotConstructLine
}
#[derive(Debug)]
pub struct ContextError {
kind: ContextErrorKind,
}
impl ContextError {
fn new(kind: ContextErrorKind) -> Self {
ContextError { kind }
}
pub fn kind(&self) -> ContextErrorKind {
self.kind
}
}
#[cfg(not(tarpaulin_include))]
impl fmt::Display for ContextError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind {
ContextErrorKind::NoTextLeft => write!(f, "No cover text left.",),
ContextErrorKind::CannotConstructLine => write!(f, "Pivot is too small. Couldn't load the line no longer than the pivot.",),
}
}
}
impl Error for ContextError {}