use std::collections::HashMap;

use crate::{
    expr::Expr,
    token::{Token, TokenType},
};

pub struct Parser {
    tokens: Vec<Token>,
    current: usize,
    env: Vec<char>,
}

impl Parser {
    pub fn new(tokens: Vec<Token>) -> Self {
        Self {
            tokens,
            current: 0,
            env: vec![],
        }
    }

    pub fn parse(&mut self) -> Option<(HashMap<String, Expr>, Expr)> {
        let mut definitions = HashMap::new();
        let mut main_expr = None;
        while !self.is_at_end() {
            let t = self.peek().token_type.clone();

            match t {
                TokenType::DefinitionName(name) => {
                    self.advance(); // consume definition name

                    // function expr

                    if self.peek().token_type != TokenType::Equals {
                        self.print_error("Expected '=' after definition name");

                        return None;
                    }

                    self.advance(); // consume equals

                    let definition = self.expr()?;

                    if definitions.contains_key(&name) {
                        self.print_error(&format!("Duplicate definition for ${}", name));
                        return None;
                    }

                    definitions.insert(name, definition);
                }

                // main expr
                _ => {
                    let expr = self.expr()?;

                    if main_expr.is_some() {
                        self.print_error("Duplicate primary expression");
                        return None;
                    }

                    main_expr = Some(expr);
                }
            }
        }

        match main_expr {
            Some(expr) => Some((definitions, expr)),
            None => {
                self.print_error("Missing primary expression");

                None
            }
        }
    }

    fn expr(&mut self) -> Option<Expr> {
        match &self.peek().token_type {
            TokenType::Lambda => self.abstraction(),

            TokenType::DefinitionName(name) => {
                let name = name.clone();
                self.advance();

                Some(Expr::DefinitionName(name))
            }

            TokenType::LeftParen => self.application(),

            TokenType::Variable(c) => {
                let c = *c;
                self.advance();

                Some(Expr::Var(self.find_variable(c)?))
            }

            _ => {
                self.print_error(
                    "Unexpected token. Expected one of 'λ', '/', '\\', '$', '(', or a variable",
                );
                None
            }
        }
    }

    fn abstraction(&mut self) -> Option<Expr> {
        self.advance(); // consume lambda

        let mut abs_count = 0;
        while let TokenType::Variable(v) = self.peek().token_type {
            self.env.push(v);
            abs_count += 1;
            self.advance();
        }

        if self.advance().token_type != TokenType::Period {
            self.print_error("Expected '.' after abstraction variable definitions");
            return None;
        }

        let mut expr = self.expr()?;

        for _ in 0..abs_count {
            self.env.pop().unwrap();
            expr = Expr::Lambda(Box::new(expr));
        }

        Some(expr)
    }

    fn application(&mut self) -> Option<Expr> {
        self.advance(); // consume left paren

        let left = self.expr()?;
        let right = self.expr()?;

        dbg!(&left, &right);
        dbg!(&self.peek().token_type);

        if self.peek().token_type != TokenType::RightParen {
            self.print_error("Expected ')' after application");
            return None;
        }

        self.advance(); // consume right paren

        Some(Expr::App(Box::new(left), Box::new(right)))
    }

    fn find_variable(&self, name: char) -> Option<usize> {
        let ret = self
            .env
            .iter()
            .enumerate()
            .find(|(_, x)| **x == name)
            .map(|(i, _)| i);

        match ret {
            Some(x) => Some(x),
            None => {
                self.print_error(&format!("Unknown variable '{}'.", name));

                None
            }
        }
    }

    fn peek(&self) -> &Token {
        &self.tokens[self.current]
    }

    fn peek_next(&self) -> &TokenType {
        if self.current + 1 == self.tokens.len() {
            &TokenType::NoToken
        } else {
            &self.tokens[self.current + 1].token_type
        }
    }

    fn advance(&mut self) -> &Token {
        let t = &self.tokens[self.current];
        self.current += 1;

        t
    }

    fn is_at_end(&self) -> bool {
        self.tokens.len() == self.current
    }

    fn print_error(&self, err: &str) {
        eprintln!("Error on line [{}]: {err}", self.peek().line);
    }
}