use crate::parser::ast::Token;
use crate::parser::error::{Expected, ParseError, ParseResult};
use crate::parser::token::Keyword;

pub struct Parser {
    tokens: Vec<Token>,
    position: usize,
    depth: usize,
}

const MAX_PARSER_DEPTH: usize = 32;

impl Parser {
    pub fn new(tokens: Vec<Token>) -> Self {
        Self {
            tokens,
            position: 0,
            depth: 0,
        }
    }

    pub fn enter_recursion(&mut self) -> ParseResult<()> {
        self.depth += 1;
        if self.depth > MAX_PARSER_DEPTH {
            return Err(ParseError::new(self.position)
                .expected_desc("recursion limit exceeded")
                .found(format!("depth {}", self.depth)));
        }
        Ok(())
    }

    pub fn leave_recursion(&mut self) {
        self.depth = self.depth.saturating_sub(1);
    }

    pub fn peek(&self) -> Option<&Token> {
        self.tokens.get(self.position)
    }

    pub fn peek_at(&self, offset: usize) -> Option<&Token> {
        self.tokens.get(self.position + offset)
    }

    pub fn next(&mut self) -> Option<&Token> {
        let tok = self.tokens.get(self.position)?;
        self.position += 1;
        Some(tok)
    }

    pub fn is_empty(&self) -> bool {
        self.position >= self.tokens.len()
    }

    pub fn at_keyword(&self, kw: Keyword) -> bool {
        matches!(self.peek(), Some(Token::Keyword(k)) if *k == kw)
    }

    pub fn expect_keyword(&mut self, kw: Keyword) -> ParseResult<()> {
        let pos = self.position;
        match self.next() {
            Some(Token::Keyword(k)) if *k == kw => Ok(()),
            Some(tok) => Err(ParseError::new(pos)
                .expected_keyword(kw)
                .found(token_display(tok))),
            None => Err(ParseError::new(pos)
                .expected_keyword(kw)
                .found("end of input".to_string())),
        }
    }

    pub fn expect_lparen(&mut self) -> ParseResult<()> {
        self.expect_token(&Token::LParen)
    }

    pub fn expect_rparen(&mut self) -> ParseResult<()> {
        self.expect_token(&Token::RParen)
    }

    pub fn expect_comma(&mut self) -> ParseResult<()> {
        self.expect_token(&Token::Comma)
    }

    pub fn expect_token(&mut self, expected: &Token) -> ParseResult<()> {
        let pos = self.position;
        match self.next() {
            Some(tok) if tokens_equal(tok, expected) => Ok(()),
            Some(tok) => Err(ParseError::new(pos)
                .expected_desc("token")
                .found(token_display(tok))),
            None => Err(ParseError::new(pos)
                .expected_desc("token")
                .found("end of input".to_string())),
        }
    }

    pub fn error(&self, expected: Expected) -> ParseError {
        let pos = self.position;
        let found = self.peek().map(token_display);
        let mut err = ParseError::new(pos).expected(expected);
        if let Some(f) = found {
            err = err.found(f);
        }
        err
    }

    pub fn backtrack<T>(&self, expected: Expected) -> ParseResult<T> {
        Err(self.error(expected))
    }

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

    pub fn restore(&mut self, pos: usize) {
        self.position = pos;
    }
}

fn tokens_equal(a: &Token, b: &Token) -> bool {
    a == b
}

fn token_display(tok: &Token) -> String {
    match tok {
        Token::Keyword(k) => format!("keyword {}", k),
        Token::Identifier(id) => format!("identifier '{}'", id),
        Token::Variable(v) => format!("variable '{}'", v),
        Token::Number { value: n, .. } => format!("number {}", n),
        Token::String(s) => format!("string '{}'", s),
        Token::NString(s) => format!("string N'{}'", s),
        Token::Operator(op) => format!("operator '{}'", op),
        Token::LParen => "'('".to_string(),
        Token::RParen => "')'".to_string(),
        Token::Comma => "','".to_string(),
        Token::Semicolon => "';'".to_string(),
        Token::Dot => "'.'".to_string(),
        Token::Star => "'*'".to_string(),
        Token::Tilde => "'~'".to_string(),
        Token::BinaryLiteral(hex) => format!("binary literal '{}'", hex),
        Token::Go => "GO".to_string(),
    }
}
