use crate::lexer::span::Span;

pub struct CharSource<'source> {
    source: &'source str,
    pos: usize,
}

impl<'source> CharSource<'source> {
    pub fn from<T: AsRef<str> + ?Sized>(source: &'source T) -> Self {
        Self {
            source: source.as_ref(),
            pos: 0,
        }
    }

    pub fn source(&self) -> &'source str {
        self.source
    }

    pub fn is_empty(&self) -> bool {
        self.pos >= self.source.len()
    }

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

    pub fn spanning(&self, span: Span) -> &'source str {
        &self.source[span.range()]
    }

    pub fn peek_nth(&self, n: usize) -> Option<char> {
        self.source[self.pos..].chars().nth(n)
    }

    pub fn peek(&self) -> Option<char> {
        self.peek_nth(0)
    }

    pub fn peek_by<const N: usize>(&self) -> Option<[char; N]> {
        let mut result = ['\0'; N];

        for (i, c) in result.iter_mut().enumerate() {
            *c = self.peek_nth(i)?;
        }
        Some(result)
    }

    pub fn backtrack_by(&mut self, n: usize) {
        self.pos = self.pos.saturating_sub(n);
    }

    pub fn backtrack(&mut self) {
        self.backtrack_by(1);
    }

    pub fn advance(&mut self) {
        if let Some(c) = self.peek() {
            self.pos += c.len_utf8(); // advance by char width, not 1 byte
        }
    }

    pub fn advance_by(&mut self, n: usize) {
        for _ in 0..n {
            self.advance();
        }
    }

    pub fn next_nth<const N: usize>(&mut self) -> Option<[char; N]> {
        self.peek_by::<N>().inspect(|_| {
            self.advance_by(N);
        })
    }

    pub fn try_next_nth<const N: usize>(
        &mut self,
        f: impl FnOnce([char; N]) -> Option<[char; N]>,
    ) -> Option<[char; N]> {
        let result = f(self.peek_by::<N>()?);
        if result.is_some() {
            self.advance_by(N);
        }
        result
    }

    pub fn try_next<T>(&mut self, f: impl FnOnce(char, &mut Self) -> Option<T>) -> Option<T> {
        let c = self.peek()?;
        self.advance();
        let result = f(c, self);
        if result.is_none() {
            self.backtrack();
        }
        result
    }

    pub fn next_if(&mut self, f: impl FnOnce(char) -> bool) -> Option<char> {
        let c = self.peek()?;
        if f(c) {
            self.advance();
            Some(c)
        } else {
            None
        }
    }
}

impl<'source> Iterator for CharSource<'source> {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        let c = self.peek()?;
        self.advance();
        Some(c)
    }
}
