#![allow(unused)]

use crate::lexer::{Lexer, Token};
use crate::Component;
use failure::Fail;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashSet};
use std::hash::BuildHasherDefault;
use std::ops::Deref;
use ulid::Ulid;

mod errors;
pub(crate) mod structures;

use errors::*;
use structures::*;

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Ast {
    ReaderMacroStart,
    ReaderMacroEnd,
    StringVar(Var),
    IntVar(usize),
    Atom(Atom),
    AtomVar(Atom),
    LeftImplication,
    End,
    RightImplication,
    Newline,
}

pub struct Parser {
    tokens: Vec<Token>,
    ast: Vec<Ast>,
    rules: Vec<Rule>,
}

impl Parser {
    pub fn new() -> Self {
        Self {
            tokens: vec![],
            ast: Vec::new(),
            rules: Vec::new(),
        }
    }

    pub fn from_lexer(lexer: &Lexer) -> Self {
        let mut parser = Self::new();
        for (_, token) in lexer.state().iter() {
            parser.tokens.push(token.clone());
        }
        parser
    }

    pub fn parse(&mut self) -> Result<(), errors::Error> {
        self.ast = self.parse_span(self.tokens.clone())?;
        Ok(())
    }

    fn parse_rules(&mut self) -> Result<(), errors::Error> {
        let mut pos = 0;
        let mut last_statement: Vec<Ast> = Vec::new();
        while let Some(node) = self.ast.get(pos).cloned() {
            match node {
                Ast::Newline => {
                    match self.ast.get(pos - 1) {
                        None | Some(Ast::ReaderMacroEnd) => continue,
                        _ => (),
                    }
                    // if rules don't start with an atom, don't parse this
                    match last_statement.first() {
                        Some(Ast::Atom(_)) => (),
                        _ => continue,
                    }
                    // if there is no implication in this potential rule, we're not parsing a rule and we can skip it
                    if last_statement
                        .iter()
                        .find(|&t| *t == Ast::LeftImplication || *t == Ast::RightImplication)
                        .is_none()
                    {
                        last_statement.clear();
                        continue;
                    }
                    self.rules.push(parse_rule(&last_statement)?);
                    last_statement.clear();
                }
                Ast::ReaderMacroEnd | Ast::ReaderMacroStart => unimplemented!(),
                Ast::End => break,
                n => last_statement.push(n),
            }
            pos += 1;
        }
        Ok(())
    }

    fn parse_span(&mut self, span: Vec<Token>) -> Result<Vec<Ast>, errors::Error> {
        let mut pos = 0;
        let mut ast = Vec::new();
        while let Some(token) = span.get(pos).cloned() {
            let mut node = match token {
                Token::EOF => {
                    pos += 1;
                    vec![Ast::End]
                }
                Token::Identifier(ident) => {
                    pos += 1;
                    vec![Ast::Atom(Atom::new(ident))]
                }
                Token::LeftParen => {
                    let length = distance(span[pos..].iter(), |&tok| *tok != Token::RightParen);
                    let mut vars = Vec::new();
                    for token in span[pos + 1..pos + length].into_iter() {
                        match token {
                            Token::Comma => (),
                            Token::Integer(int) => {
                                vars.push(Ast::IntVar(*int));
                            }
                            Token::Identifier(ident) => {
                                vars.push(Ast::AtomVar(Atom::new(
                                    ident.try_subtendril(0, ident.len() as u32).unwrap(),
                                )));
                            }
                            Token::Str(s) => {
                                vars.push(Ast::StringVar(Var::new(
                                    s.try_subtendril(0, s.len() as u32).unwrap(),
                                )));
                            }
                            _ => return Err(errors::Error::invalid_parameter_list()),
                        }
                    }
                    pos += length;
                    vars
                }
                Token::Newline | Token::SemiColon => {
                    pos += 1;
                    vec![Ast::Newline]
                }
                Token::RightParen | Token::Comma | Token::Whitespace => {
                    pos += 1;
                    continue;
                }
                Token::LeftArrow => {
                    pos += 1;
                    vec![Ast::LeftImplication]
                }
                Token::RightArrow => {
                    pos += 1;
                    vec![Ast::RightImplication]
                }
                Token::Str(_) => return Err(errors::Error::top_level_string()),
                Token::Integer(_) => return Err(errors::Error::top_level_int()),
                Token::ReaderMacroStart => {
                    pos += 1;
                    vec![Ast::ReaderMacroStart]
                }
                Token::ReaderMacroEnd => {
                    pos += 1;
                    vec![Ast::ReaderMacroEnd]
                }
            };
            ast.append(&mut node);
        }
        Ok(ast)
    }
}

fn parse_rule(nodes: &[Ast]) -> Result<Rule, errors::Error> {
    let nodes = &nodes[..nodes.len()];
    let mut pos = 0;
    let mut rule = Rule::new();
    let mut first_predicate = Predicate::new();
    let mut second_predicate = Predicate::new();
    match nodes.first().cloned() {
        Some(Ast::Atom(atom)) => {
            first_predicate.id = atom;
        }
        _ => return Err(errors::Error::atom_not_present_for_rule()),
    }
    let implication_loc = distance(nodes[1..].iter(), |&t| {
        *t != Ast::RightImplication && *t != Ast::LeftImplication
    });
    let first_variables = nodes[1..=implication_loc].to_vec();
    first_predicate.params = first_variables;
    let implication: Ast = match nodes.get(implication_loc + 1).cloned() {
        implication @ Some(Ast::LeftImplication) | implication @ Some(Ast::RightImplication) => {
            implication.unwrap()
        }
        None | _ => return Err(errors::Error::implication_not_found()),
    };
    match nodes.get(implication_loc + 2).cloned() {
        Some(Ast::Atom(atom)) => {
            second_predicate.id = atom;
        }
        _ => return Err(errors::Error::atom_not_present_for_rule()),
    }
    let second_variables = nodes[implication_loc + 3..].to_vec();
    second_predicate.params = second_variables;
    match implication {
        Ast::LeftImplication => {
            rule.head = second_predicate;
            rule.body = first_predicate;
        }
        Ast::RightImplication => {
            rule.head = first_predicate;
            rule.body = second_predicate;
        }
        _ => unreachable!(),
    }
    Ok(rule)
}

fn distance<T: Sized>(iter: impl Iterator<Item = T>, functor: impl FnMut(&T) -> bool) -> usize {
    iter.take_while(functor).fold(0, |acc, _| acc + 1)
}

#[cfg(test)]
mod tests {
    use super::*;
    fn predicate_for_enum<T: Clone>(vec: Vec<T>) -> Vec<std::mem::Discriminant<T>> {
        vec.into_iter()
            .map(|item| std::mem::discriminant(&item))
            .collect()
    }
    #[test]
    fn test_parsing_span() {
        use std::mem::discriminant as match_enum;
        let input = "#[next]\nfoo(bar) => baz(bar)";
        let mut lexer = Lexer::new(input);
        match lexer.parse_tokens() {
            Ok(()) => (),
            Err(_) => println!("{:?}", lexer.state()),
        };
        let mut parser = Parser::from_lexer(&lexer);
        parser.parse();
        let mut results = Vec::new();
        for node in parser.ast {
            results.push(node);
        }
        let predicate = predicate_for_enum(results);
        assert_eq!(
            predicate,
            vec![
                match_enum(&Ast::ReaderMacroStart),
                match_enum(&Ast::Atom(Atom::default())),
                match_enum(&Ast::ReaderMacroEnd),
                match_enum(&Ast::Newline),
                match_enum(&Ast::Atom(Atom::default())),
                match_enum(&Ast::AtomVar(Atom::default())),
                match_enum(&Ast::RightImplication),
                match_enum(&Ast::Atom(Atom::default())),
                match_enum(&Ast::AtomVar(Atom::default())),
                match_enum(&Ast::End),
            ]
        );
    }

    #[test]
    fn test_parsing_rules() {
        let input = "foo(bar) => baz(bar)\nbar(qux) <= quux(quuux)\n";
        let mut lexer = Lexer::new(input);
        match lexer.parse_tokens() {
            Ok(()) => (),
            Err(_) => println!("{:?}", lexer.state()),
        }
        let mut parser = Parser::from_lexer(&lexer);
        parser.parse();
        match parser.parse_rules() {
            Ok(()) => (),
            Err(err) => println!("ERR: {:?}", err),
        }
        assert_eq!(*parser.rules[0].head.id, "foo".into());
        assert_eq!(parser.rules[0].head.params.is_empty(), false);
        assert_eq!(*parser.rules[0].body.id, "baz".into());
        assert_eq!(parser.rules[0].body.params.is_empty(), false);
        assert_eq!(*parser.rules[1].head.id, "quux".into());
        assert_eq!(parser.rules[1].head.params.is_empty(), false);
        assert_eq!(*parser.rules[1].body.id, "bar".into());
        assert_eq!(parser.rules[1].body.params.is_empty(), false);
    }
}
