use std::io::{self, BufRead, BufReader, Read};

pub struct LineBuffer {
    reader: BufReader<Box<dyn Read>>,
    lines: Vec<String>,
    eof: bool,
    final_newline: bool,
}

impl LineBuffer {
    pub fn new<R: Read + 'static>(source: R) -> Self {
        Self {
            reader: BufReader::new(Box::new(source)),
            lines: Vec::new(),
            eof: false,
            final_newline: true,
        }
    }

    pub fn fill_to(&mut self, target_lines: usize) -> io::Result<()> {
        while !self.eof && self.lines.len() < target_lines {
            let mut s = String::new();
            let n = self.reader.read_line(&mut s)?;
            if n == 0 {
                self.eof = true;
                break;
            }
            if s.ends_with('\n') {
                s.pop();
            } else {
                self.final_newline = false;
                self.eof = true;
            }
            self.lines.push(s);
        }
        Ok(())
    }

    pub fn fill_all(&mut self) -> io::Result<()> {
        self.fill_to(usize::MAX)
    }

    pub fn lines(&self) -> &[String] {
        &self.lines
    }

    pub fn len(&self) -> usize {
        self.lines.len()
    }

    pub fn eof(&self) -> bool {
        self.eof
    }

    pub fn final_newline(&self) -> bool {
        self.final_newline
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fill_to_stops_at_target() {
        let input = b"a\nb\nc\nd\ne\n".to_vec();
        let mut buf = LineBuffer::new(io::Cursor::new(input));
        buf.fill_to(3).unwrap();
        assert_eq!(buf.len(), 3);
        assert!(!buf.eof());
        assert_eq!(buf.lines(), &["a", "b", "c"]);
    }

    #[test]
    fn fill_to_stops_at_eof() {
        let input = b"a\nb\n".to_vec();
        let mut buf = LineBuffer::new(io::Cursor::new(input));
        buf.fill_to(10).unwrap();
        assert_eq!(buf.len(), 2);
        assert!(buf.eof());
        assert!(buf.final_newline());
    }

    #[test]
    fn fill_continues_from_where_it_left_off() {
        let input = b"a\nb\nc\nd\n".to_vec();
        let mut buf = LineBuffer::new(io::Cursor::new(input));
        buf.fill_to(2).unwrap();
        assert_eq!(buf.len(), 2);
        assert!(!buf.eof());
        buf.fill_all().unwrap();
        assert_eq!(buf.len(), 4);
        assert!(buf.eof());
    }

    #[test]
    fn detects_missing_final_newline() {
        let input = b"hello".to_vec();
        let mut buf = LineBuffer::new(io::Cursor::new(input));
        buf.fill_all().unwrap();
        assert_eq!(buf.lines(), &["hello"]);
        assert!(buf.eof());
        assert!(!buf.final_newline());
    }
}
