use std::error::Error;
use std::io::Read;

const BUFFER_SIZE: usize = 1024;
const EOL: u8 = 0x0a;

pub struct LineReader {
    buffer: [u8; BUFFER_SIZE],
    pos: usize,
}

impl LineReader {
    pub fn new() -> Self {
        LineReader { buffer: [0; BUFFER_SIZE], pos: 0 }
    }

    fn get_eol(&self) -> Option<usize> {
        for (pos, &item) in self.buffer[0..self.pos].iter().enumerate() {
            if item == EOL {
                return Some(pos);
            }
        }
        None
    }

    pub fn read_line(&mut self, reader: &mut impl Read) -> Result<String, Box<dyn Error>> {
        loop {
            if self.get_eol().is_some() {
                break;
            }
            if self.pos >= BUFFER_SIZE {
                return Err("buffer exceeded".into());
            }
            let len = reader.read(&mut self.buffer[self.pos..])?;
            self.pos += len;
        }

        if let Some(eol) = self.get_eol() {
            let line = String::from_utf8_lossy(&self.buffer[0..eol]).into_owned();
            let line = format!("{line}\n");
            let pos = eol + 1;
            self.buffer.copy_within(pos.., 0);
            self.pos -= pos;
            return Ok(line.to_string());
        }
        Err("no line available".into())
    }
}