use super::{
    ast::{Expr, Spanned},
    lexer::Token,
};
use chumsky::{prelude::*, Stream};
use logos::Logos;

/// Parse source string into a Expr
pub fn parse(src: &str) -> Result<Vec<Spanned<Expr>>, Vec<Simple<Token<'_>>>> {
    let lexer = Token::lexer(src);
    let len = lexer.source().len();
    parser().parse(Stream::from_iter(len..len + 1, lexer.spanned()))
}

fn parser<'s>() -> impl Parser<Token<'s>, Vec<Spanned<Expr<'s>>>, Error = Simple<Token<'s>>> {
    recursive(|expr| {
        let atom = select! {
            Token::Symbol(s) => Expr::Symbol(s),
            Token::Keyword(k) => Expr::Keyword(k),
            Token::String(s) => Expr::String(s),
            Token::Number(n) => Expr::Number(n),
        };

        let list = expr
            .clone()
            .repeated()
            .delimited_by(just(Token::LeftParen), just(Token::RightParen));

        let vector = just(Token::Octothorpe)
            .ignore_then(list.clone())
            .map(Expr::Vector);

        let list = list.map(Expr::List);

        let quote = just(Token::Quote)
            .ignore_then(expr.clone())
            .map(Box::new)
            .map(Expr::Quote);

        let pair = expr
            .clone()
            .then_ignore(just(Token::Dot))
            .then(expr)
            .delimited_by(just(Token::LeftParen), just(Token::RightParen))
            .map(|(l, r)| Expr::Pair((Box::new(l), Box::new(r))));

        atom.or(pair)
            .or(list)
            .or(vector)
            .or(quote)
            .map_with_span(Spanned::new)
    })
    .repeated()
    .then_ignore(end())
}

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

    #[test]
    fn function() {
        assert_eq!(
            parse(r#"(defun hello (name) (log (concat "Hello, " name "!")))"#).unwrap(),
            &[Spanned::new(
                Expr::List(vec![
                    Spanned::new(Expr::Symbol("defun"), 1..6),
                    Spanned::new(Expr::Symbol("hello"), 7..12),
                    Spanned::new(
                        Expr::List(vec![Spanned::new(Expr::Symbol("name"), 14..18),]),
                        13..19,
                    ),
                    Spanned::new(
                        Expr::List(vec![
                            Spanned::new(Expr::Symbol("log"), 21..24),
                            Spanned::new(
                                Expr::List(vec![
                                    Spanned::new(Expr::Symbol("concat"), 26..32),
                                    Spanned::new(Expr::String("Hello, "), 33..42),
                                    Spanned::new(Expr::Symbol("name"), 43..47),
                                    Spanned::new(Expr::String("!"), 48..51),
                                ]),
                                48..51
                            )
                        ]),
                        20..53
                    )
                ]),
                0..54,
            )]
        );
    }
}