use std::collections::{HashSet, VecDeque};
use std::io::prelude::*;
use crate::tally::VoteReadError::*;
use crate::tally::VoteToken;
use crate::tally::VoteToken::*;
pub struct BltReader<'a, B: BufRead> {
token_queue: VecDeque<VoteToken>,
candidates: Option<u32>,
line: usize,
footer: bool,
broken: bool,
footer_line: usize,
stream: &'a mut B,
}
impl<'a, B: BufRead> BltReader<'a, B> {
pub fn new(stream: &mut B) -> BltReader<B> {
BltReader {
stream,
token_queue: VecDeque::new(),
line: 0,
broken: false,
candidates: None,
footer: false,
footer_line: 0,
}
}
fn read_line1(&mut self, buf: &str) {
let mut iter = buf.split(char::is_whitespace).filter(|x| !x.is_empty());
for i in &[0, 1] {
self.token_queue.push_back(if let Some(eval) = iter.next() {
match (*i, eval.parse::<u32>()) {
(0, Ok(val)) => {
self.candidates = Some(val);
DeclareCandidates(val)
}
(1, Ok(val)) => DeclareSeats(val),
_ => ReadFailure(CannotParse(self.line, eval.to_string())),
}
} else {
ReadFailure(WrongSyntax(self.line))
});
}
}
fn read_line2(&mut self, buf: &str) {
let cmax: i64 = self.candidates.unwrap().into();
if buf.trim().starts_with('-') {
let line = self.line;
let iter = buf
.split(char::is_whitespace)
.filter(|x| !x.is_empty())
.map(|x| {
if let Ok(val) = x.parse::<i64>() {
if val < 0 && val >= -cmax {
WithdrawCandidate((-val - 1) as u32)
} else {
ReadFailure(CannotParse(line, x.to_string()))
}
} else {
ReadFailure(CannotParse(line, x.to_string()))
}
});
for e in iter {
self.token_queue.push_back(e);
}
} else {
self.read_body(buf);
}
}
fn read_body(&mut self, buf: &str) {
let line = self.line;
let cmax: u32 = self.candidates.unwrap();
let mut seen: HashSet<u32> = HashSet::with_capacity(cmax as usize);
let mut pref = 0i32;
let mut iter = buf
.split(char::is_whitespace)
.filter(|x| !x.is_empty())
.enumerate()
.map(|(i, x)| {
if i == 0 {
if let Ok(val) = x.parse::<u64>() {
if val == 0 {
EndBallots
} else {
BallotRepeat(val)
}
} else {
ReadFailure(CannotParse(line, x.to_string()))
}
} else {
let (eq, val) = if let Some(eqval) = x.strip_prefix('=') {
if let Ok(val) = eqval.parse::<u32>() {
(true, val)
} else {
return ReadFailure(CannotParse(line, x.to_string()));
}
} else if let Ok(val) = x.parse::<u32>() {
(false, val)
} else {
return ReadFailure(CannotParse(line, x.to_string()));
};
if val == 0 {
EndBallot
} else if val > cmax || seen.contains(&val) {
ReadFailure(InvalidValue(line, x.to_string()))
} else {
seen.insert(val);
if !eq {
pref += 1;
}
Vote(pref, val - 1)
}
}
})
.peekable();
if let Some(EndBallots) = iter.peek() {
if iter.count() == 1 {
self.footer = true;
self.token_queue.push_back(EndBallots);
}
} else {
while let Some(e) = iter.next() {
if let EndBallot = e {
if iter.peek().is_none() {
self.token_queue.push_back(e);
} else {
}
} else {
self.token_queue.push_back(e);
}
}
}
}
fn read_footer(&mut self, buf: &str) {
let line = self.line;
let trimmed = buf.trim();
if !trimmed.is_empty() {
self.footer_line += 1;
self.token_queue
.push_back(if !trimmed.starts_with('"') || !trimmed.ends_with('"') {
ReadFailure(InvalidToken(line, trimmed.to_string()))
} else if let Some(candidates) = self.candidates {
let name = trimmed[1..(trimmed.len() - 1)].to_string();
let id = self.footer_line as u32 - 1;
use std::cmp::Ordering;
match id.cmp(&candidates) {
Ordering::Less => CandidateName(id, name),
Ordering::Equal => ElectionTitle(name),
_ => ReadFailure(WrongSyntax(line)),
}
} else {
panic!("Candidates not emitted before footer; this shall never happen!")
});
}
}
}
impl<'a, B: BufRead> Iterator for BltReader<'a, B> {
type Item = VoteToken;
fn next(&mut self) -> Option<VoteToken> {
if self.broken {
None
} else if let Some(x) = self.token_queue.pop_front() {
if let ReadFailure(_) = x {
self.broken = true;
}
Some(x)
} else {
let mut buf = String::new();
self.line += 1;
match self.stream.read_line(&mut buf) {
Ok(read) => {
if read == 0 {
None
} else {
if self.line == 1 {
self.read_line1(&buf)
} else if self.line == 2 {
self.read_line2(&buf)
} else if !self.footer {
self.read_body(&buf)
} else {
self.read_footer(&buf)
}
self.next()
}
}
Err(err) => {
self.broken = true;
Some(ReadFailure(IOError(err)))
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn parse_sample_blt() {
const EASY_BLT: &'static [u8] =
b" 3 1\n -3 \n 7 1 =2 =3 0\n \n 17 0 2 0 0 0 3 1 0\n 0 17 8 0\n27 3 2 1 0\n 0 \n \"A\"\n \n\"B\" \n\"C\"\n \"Title\" \n";
let mut cursor = Cursor::new(EASY_BLT);
let blt_reader = BltReader::new(&mut cursor);
for e in blt_reader.enumerate() {
match e {
(0, DeclareCandidates(3))
| (1, DeclareSeats(1))
| (2, WithdrawCandidate(2))
| (3, BallotRepeat(7))
| (4, Vote(1, 0))
| (5, Vote(1, 1))
| (6, Vote(1, 2))
| (7, EndBallot)
| (8, BallotRepeat(17))
| (9, Vote(1, 1))
| (10, Vote(2, 2))
| (11, Vote(3, 0))
| (12, EndBallot)
| (13, BallotRepeat(27))
| (14, Vote(1, 2))
| (15, Vote(2, 1))
| (16, Vote(3, 0))
| (17, EndBallot)
| (18, EndBallots) => (),
(19, CandidateName(0, x)) => assert_eq!(x, "A"),
(20, CandidateName(1, x)) => assert_eq!(x, "B"),
(21, CandidateName(2, x)) => assert_eq!(x, "C"),
(22, ElectionTitle(x)) => assert_eq!(x, "Title"),
_ => unreachable!(),
}
}
}
#[test]
fn blt_cannot_parse() {
const BAD: &'static [u8] = b" eee 1\n";
let mut cursor = Cursor::new(BAD);
let mut blt_reader = BltReader::new(&mut cursor);
match blt_reader.next() {
Some(ReadFailure(CannotParse(1, x))) => assert_eq!(x, "eee"),
_ => unreachable!(),
}
}
#[test]
fn blt_header() {
const BAD: &'static [u8] = b" 1 \n";
let mut cursor = Cursor::new(BAD);
let blt_reader = BltReader::new(&mut cursor);
match blt_reader.skip(1).next() {
Some(ReadFailure(WrongSyntax(l))) => assert_eq!(l, 1),
_ => unreachable!(),
}
}
#[test]
fn blt_bad_cand() {
const BAD: &'static [u8] = b"3 1\n77 1 2 3 0\n99 2 4 1 0\n";
let mut cursor = Cursor::new(BAD);
let blt_reader = BltReader::new(&mut cursor);
match blt_reader
.filter(|x| if let ReadFailure(_) = x { true } else { false })
.next()
{
Some(ReadFailure(InvalidValue(l, s))) => {
assert_eq!(l, 3);
assert_eq!(s, "4")
}
_ => unreachable!(),
}
}
}