use crate::error::error_header;
use crate::error::source_quote::error_style;
use crate::lexer::{Token, Tokens};

#[derive(Clone)]
pub struct NoParserData {}
impl NoParserData {
    pub fn new() -> Self {
        Self {}
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RuleSymbol<T: Clone + Copy + Eq + PartialEq, N: Clone + Copy + Eq + PartialEq> {
    T(T),
    NT(N),
}

#[derive(Clone, Debug)]
pub enum AstParam<T: Clone, N: Clone> {
    Token(T),
    Ast(N),
}

#[derive(Clone, Debug)]
pub enum AstFunc<T: Clone, N: Clone, D: Clone> {
    Fold(fn(&[AstParam<T, N>], &mut D) -> AstParam<T, N>, usize),
    Exec(fn(&[AstParam<T, N>], &mut D), usize),
}

impl<T: Clone, N: Clone, D: Clone> AstFunc<T, N, D> {
    fn n(&self) -> usize {
        match self {
            Self::Fold(_, n) => *n,
            Self::Exec(_, n) => *n,
        }
    }
}

#[derive(Clone, Debug)]
pub struct ParseTableEntry<
    T: NoData<TN> + Clone + 'static,
    TN: Clone + Copy + Eq + PartialEq + 'static,
    N: NoData<NN> + Clone + 'static,
    NN: Clone + Copy + Eq + PartialEq + 'static,
    D: Clone + 'static,
> {
    pub nt: NN,
    pub t: TN,
    pub expand: &'static [RuleSymbol<TN, NN>],
    pub ast_func: &'static [AstFunc<T, N, D>],
}

pub trait NoData<T> {
    fn no_data(&self) -> T;
}

pub trait GetEOF<T> {
    fn get_eof() -> T;
}

fn ast_func_root<T: Clone, N: Clone, D: Clone>(
    args: &[AstParam<T, N>],
    _: &mut D,
) -> AstParam<T, N> {
    args[0].clone()
}

fn get_production_table_entry<
    T: NoData<TN> + Clone + GetEOF<TN>,
    TN: Clone + Copy + Eq + PartialEq,
    N: NoData<NN> + Clone,
    NN: Clone + Copy + Eq + PartialEq,
    D: Clone,
>(
    table: &[ParseTableEntry<Token<T>, TN, N, NN, D>],
    nt: NN,
    t: TN,
) -> Option<&ParseTableEntry<Token<T>, TN, N, NN, D>> {
    for entry in table {
        if entry.t == t && entry.nt == nt {
            return Some(entry);
        }
    }
    None
}

pub fn parse<
    'r,
    T: NoData<TN> + Clone + GetEOF<TN> + std::fmt::Debug,
    TN: Clone + Copy + Eq + PartialEq + std::fmt::Debug,
    N: NoData<NN> + Clone + std::fmt::Debug,
    NN: Clone + Copy + Eq + PartialEq + std::fmt::Debug,
    D: Clone,
>(
    parse_table: &[ParseTableEntry<Token<T>, TN, N, NN, D>],
    first_nt: NN,
    mut tokens: Tokens<'r, T>,
    mut data: D,
) -> Option<AstParam<Token<T>, N>> {
    let mut symbols_to_derive = vec![RuleSymbol::NT(first_nt), RuleSymbol::T(T::get_eof())];
    let mut cur_token_opt = tokens.next();

    let mut node_stack: Vec<AstParam<Token<T>, N>> = vec![];
    let mut node_stack_sizes: Vec<usize> = vec![0];
    let mut fn_stack: Vec<AstFunc<Token<T>, N, D>> = vec![AstFunc::Fold(ast_func_root, 2)];

    while !symbols_to_derive.is_empty() {
        let Some(ref cur_token) = cur_token_opt else { break; };
        let s = &symbols_to_derive[0];
        match s {
            RuleSymbol::NT(nt) => {
                if let Some(parsing_table_entry) =
                    get_production_table_entry(parse_table, *nt, cur_token.ty.no_data())
                {
                    node_stack_sizes.push(0);
                    let mut rule_symbols = parsing_table_entry.expand.to_vec();
                    fn_stack.extend(parsing_table_entry.ast_func.to_vec());
                    rule_symbols.extend(symbols_to_derive.into_iter().skip(1));
                    symbols_to_derive = rule_symbols;
                } else {
                    error_header("parser error");
                    tokens
                        .source
                        .quote(cur_token.loc.line..cur_token.loc.line + 1)
                        .highlight(&cur_token.loc)
                        .underline('~', error_style())
                        .color(error_style())
                        .comment(
                            format!("no entry for {:?}, {:?}", cur_token.ty.no_data(), nt),
                            error_style(),
                        )
                        .dump();
                    return None;
                }
            }
            RuleSymbol::T(t) => {
                if *t == cur_token.ty.no_data() {
                    node_stack.push(AstParam::Token(cur_token.clone()));
                    *node_stack_sizes.last_mut().unwrap() += 1;
                    cur_token_opt = tokens.next();
                    symbols_to_derive = symbols_to_derive[1..].to_vec();
                } else {
                    error_header("parser error");
                    tokens
                        .source
                        .quote_line(cur_token.loc.line)
                        .highlight(&cur_token.loc)
                        .underline('~', error_style())
                        .color(error_style())
                        .comment(
                            format!("wrong token {:?}, expected {:?}", cur_token.ty.no_data(), t),
                            error_style(),
                        )
                        .dump();
                    return None;
                }
            }
        }
        while let Some(n_last) = fn_stack.last().map(|l| l.n()) {
            if *node_stack_sizes.last().unwrap() >= n_last {
                let last = fn_stack.pop().unwrap();
                match last {
                    AstFunc::Fold(f, n) => {
                        node_stack_sizes.pop();
                        let new_node = f(&node_stack[node_stack.len() - n..], &mut data);
                        node_stack = node_stack[..(node_stack.len() - n)].to_vec();
                        node_stack.push(new_node);
                        if let Some(l) = node_stack_sizes.last_mut() {
                            *l += 1;
                        }
                    }
                    AstFunc::Exec(f, n) => {
                        f(&node_stack[node_stack.len() - n..], &mut data);
                    }
                }
            } else {
                break;
            }
        }
    }
    node_stack.into_iter().next()
}
