Documentation
use std::{
    fmt::Display,
    fs::File,
    io::{BufReader, Bytes, Read, Result},
    path::PathBuf,
};

#[derive(Debug, Clone, PartialEq)]
pub struct Location {
    pub input: Option<String>,
    pub line_number: usize,
    pub char_number: usize,
}

pub struct Reader<R: Read> {
    bytes: Bytes<BufReader<R>>,
    current_byte: Option<u8>,
    location: Location,
    eof: bool,
}

pub fn from_file(file_name: &PathBuf) -> Result<Reader<File>> {
    let file = File::open(file_name)?;
    Ok(Reader::new(
        file,
        file_name.to_str().map(ToString::to_string),
    ))
}

pub fn from_std_in<R: Read>(stdin: R) -> Reader<R> {
    Reader::new(stdin, None)
}

pub fn from_string(source: &String) -> Reader<&[u8]> {
    let reader = source.as_bytes();
    let mut name = source.clone();
    name.truncate(32);
    Reader::new(reader, Some(name))
}

impl<R: Read> Reader<R> {
    fn new(reader: R, name: Option<String>) -> Self {
        let location = Location {
            input: name,
            line_number: 1,
            char_number: 1,
        };
        Reader {
            bytes: BufReader::new(reader).bytes(),
            current_byte: Option::None,
            location,
            eof: false,
        }
    }

    #[inline]
    pub fn next(&mut self) -> Result<Option<u8>> {
        if self.eof {
            return Ok(None);
        }
        match self.bytes.next() {
            None => {
                self.eof = true;
                self.current_byte = None;
                Ok(None)
            }
            Some(ch) => {
                let ch = ch?;
                if ch == b'\n' {
                    self.location.line_number += 1;
                    self.location.char_number = 1;
                } else {
                    self.location.char_number += 1;
                }
                self.current_byte = Some(ch);
                Ok(self.current_byte)
            }
        }
    }

    #[inline]
    pub fn peek(&mut self) -> Result<Option<u8>> {
        match self.current_byte {
            Some(ch) => Ok(Some(ch)),
            None => self.next(),
        }
    }

    #[inline]
    pub fn eat_whitespace(&mut self) -> Result<()> {
        loop {
            match self.peek()? {
                Some(b' ' | b'\n' | b'\t' | b'\r') => {
                    self.next()?;
                }
                _ => {
                    return Ok(());
                }
            }
        }
    }

    #[inline]
    pub fn read_digits(&mut self, digits: &mut Vec<u8>) -> Result<()> {
        loop {
            let letter = self.peek()?;
            match letter {
                Some(b'0'..=b'9') => {
                    digits.push(letter.unwrap());
                    self.next()?;
                }
                _ => {
                    return Ok(());
                }
            }
        }
    }

    pub fn where_am_i(&self) -> Location {
        self.location.clone()
    }
}

impl Display for Location {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self.input {
            Some(name) => write!(f, "{}:{}:{}", name, self.line_number, self.char_number),
            None => write!(f, "{}:{}", self.line_number, self.char_number),
        }
    }
}

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

    #[test]
    fn test_from_string() -> Result<()> {
        let str = "abc".to_string();
        let mut reader = from_string(&str);

        assert_eq!(reader.next()?, Some(b'a'));

        Ok(())
    }

    #[test]
    fn test_next() -> Result<()> {
        let str = "abc".to_string();
        let mut reader = from_string(&str);
        reader.next()?;
        reader.next()?;

        assert_eq!(reader.next()?, Some(b'c'));

        Ok(())
    }

    #[test]
    fn test_next_eof() -> Result<()> {
        let str = "ab".to_string();
        let mut reader = from_string(&str);
        reader.next()?;
        reader.next()?;

        assert_eq!(reader.next()?, None);

        Ok(())
    }

    #[test]
    fn test_next_after_eof() -> Result<()> {
        let str = "ab".to_string();
        let mut reader = from_string(&str);
        reader.next()?;
        reader.next()?;
        reader.next()?;
        reader.next()?;

        assert_eq!(reader.next()?, None);

        Ok(())
    }

    #[test]
    fn test_peak_starts() -> Result<()> {
        let str = "abc".to_string();
        let mut reader = from_string(&str);

        assert_eq!(reader.peek()?, Some(b'a'));

        Ok(())
    }

    #[test]
    fn test_peak_middle() -> Result<()> {
        let str = "abc".to_string();
        let mut reader = from_string(&str);
        reader.next()?;
        reader.next()?;

        assert_eq!(reader.peek()?, Some(b'b'));

        Ok(())
    }

    #[test]
    fn test_eat_whitespaces() -> Result<()> {
        let str = "a     \t\nbc".to_string();
        let mut reader = from_string(&str);
        reader.next()?;
        reader.next()?;
        reader.eat_whitespace()?;

        assert_eq!(reader.peek()?, Some(b'b'));

        Ok(())
    }

    #[test]
    fn test_location_is_correct() -> Result<()> {
        let str = "a\nb\ncde".to_string();
        let mut reader = from_string(&str);
        reader.next()?;
        reader.next()?;
        reader.next()?;
        reader.next()?;

        assert_eq!(
            reader.where_am_i(),
            Location {
                input: Some("a\nb\ncde".to_string()),
                line_number: 3,
                char_number: 1,
            }
        );

        Ok(())
    }
}