1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
use std::{error::Error, fmt};

use log::error;

use crate::text::{CoverTextLineIterator, CoverTextWordIterator};

/// Context with essential methods for every encoder/decoder.
pub trait Context {
    /// Gets currently loaded cover text fragment *mutably*.
    ///
    /// # Returns
    /// Result which is either `&mut String` or [ContextError].
    fn get_current_text_mut(&mut self) -> Result<&mut String, ContextError>;

    /// Gets currently loaded cover text fragment as read-only.
    ///
    /// # Returns
    /// Result which is either `&String` or [ContextError].
    fn get_current_text(&self) -> Result<&String, ContextError>;

    /// Loads next cover text fragment.
    ///
    /// # Returns
    /// Result which is either `&String` or [ContextError]. Returned string is the newly loaded fragment.
    fn load_text(&mut self) -> Result<&str, ContextError>;
}

/// Context used by methods requiring pivot.
/// Loads and returns cover text line by line - *not bound by pivot*.
pub struct PivotByRawLineContext {
    pivot: usize,
    cover_text_iter: CoverTextLineIterator,
    current_text: Option<String>,
}
/// Context used by methods requiring pivot.
/// Loads cover text line by line, uses pivot and does not preserve original whitespace.
/// It also exposes the word iterator for purpose of peeking and/or traversing.
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
    }

    // Peeks the next word without forwarding the iterator.
    //
    // # Returns
    // Returns the next word or none if the iterator gets out-of-bounds.
    pub fn peek_word(&mut self) -> Option<String> {
        self.cover_text_iter.peek()
    }
    
    // Gets the next word and proceeds the iterator.
    //
    // # Returns
    // Returns the next word or none if the iterator gets out-of-bounds.
    pub fn next_word(&mut self) -> Option<String> {
        self.cover_text_iter.next()
    }

    // Constructs line of maximum length determined by pivot.
    //
    // # Returns
    // Returns the line or none if there are no words left. It is a result, 
    // throws an error if line cannot be constructed but there are still words left.
    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);
            // Skip the peeked word
            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))
    }

    // Loads the line considering the pivot. Line length is smaller or equal the pivot value.
    // This function does not preserve original whitespace. By default words are delimited by standard ASCII space (0x20). 
    //
    // # Returns
    // Result which is either the line or [ContextError] if anything fails. 
    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))
    }

    // Loads the raw line. By raw it means preserving the whitespace
    //
    // # Returns
    // Result which is either the line or [ContextError] if anything fails. 
    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))
    }
}

/// Enum determining the exact context error.
#[derive(Debug, Clone, Copy)]
pub enum ContextErrorKind {
    NoTextLeft,
    CannotConstructLine
}

/// Error implementation for [Context]. Exact error message is determined by [ContextErrorKind].
#[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 {}