use crate::{Coordinates, Input};
use displaydoc::Display;
use std::io::{BufReader, Error as IoError, Read};
use thiserror::Error;
#[derive(Debug, Error, Display)]
pub enum Error {
UnexpectedChar(char),
IoError(#[from] IoError),
}
#[must_use]
#[derive(Debug)]
pub struct Plaintext<I: Input> {
lines: I::Lines,
current_line: Option<I::Bytes>,
position: Coordinates,
}
impl<I: Input> Plaintext<I> {
pub fn new(input: I) -> Result<Self, Error> {
let mut lines = input.lines();
let mut current_line = None;
for item in &mut lines {
let line = I::line(item)?;
if !line.as_ref().starts_with('!') {
current_line = Some(I::bytes(line));
break;
}
}
Ok(Self {
lines,
current_line,
position: (0, 0),
})
}
}
impl<I, L> Plaintext<I>
where
I: Input<Lines = L>,
L: Input,
{
pub fn remains(self) -> Result<Plaintext<L>, Error> {
Plaintext::new(self.lines)
}
}
impl<R: Read> Plaintext<BufReader<R>> {
pub fn new_from_file(file: R) -> Result<Self, Error> {
Self::new(BufReader::new(file))
}
}
impl<I: Input> Clone for Plaintext<I>
where
I::Lines: Clone,
I::Bytes: Clone,
{
fn clone(&self) -> Self {
Self {
lines: self.lines.clone(),
current_line: self.current_line.clone(),
position: self.position,
}
}
}
impl<I: Input> Iterator for Plaintext<I> {
type Item = Result<Coordinates, Error>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(c) = self.current_line.as_mut().and_then(Iterator::next) {
match c {
b'O' | b'*' => {
let cell = self.position;
self.position.0 += 1;
return Some(Ok(cell));
}
b'.' => self.position.0 += 1,
_ if c.is_ascii_whitespace() => continue,
_ => return Some(Err(Error::UnexpectedChar(char::from(c)))),
}
} else if let Some(item) = self.lines.next() {
match I::line(item) {
Ok(line) => {
if line.as_ref().starts_with('!') {
continue;
} else {
self.position.0 = 0;
self.position.1 += 1;
self.current_line = Some(I::bytes(line));
}
}
Err(e) => {
return Some(Err(Error::IoError(e)));
}
}
} else {
return None;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plaintext_glider() -> Result<(), Error> {
const GLIDER: &str = r"!Name: Glider
!
.O.
..O
OOO";
let glider = Plaintext::new(GLIDER)?;
let _ = glider.clone();
let cells = glider.collect::<Result<Vec<_>, _>>()?;
assert_eq!(cells, vec![(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)]);
Ok(())
}
}