use std::{
    iter::Peekable,
    rc::Rc,
    str::{Chars, SplitTerminator},
    vec::IntoIter,
};

use rug::Float;

use crate::{
    environment::Environment,
    node::{
        NodeEnum,
        add::Add,
        assign::Assign,
        call::Call,
        comparison::{Greater, GreaterEquals, Less, LessEquals},
        constant::Constant,
        divide::Divide,
        equals::Equals,
        exponent::Exponent,
        function::{Function, FunctionType},
        if_else::{Bool, ElseBranchEnum, IfElse},
        r#loop::{Break, Loop},
        multiply::Multiply,
        range::Range,
        set::Set,
        string_node::StringNode,
        subtract::Subtract,
        symbol::Symbol,
    },
};

#[derive(Debug)]
pub struct Token(usize, TokenType);

#[derive(Debug, PartialEq, Clone)]
pub enum TokenType {
    // Space,
    Number(String),
    Identifier(String),
    String(String),

    DoubleDot,

    Plus,
    Minus,
    Star,
    Slash,
    Hat,

    Equals,
    Greater,
    Less,
    GreaterEquals,
    LessEquals,

    ColonEquals,
    LeftArrow,
    RightArrow,

    RParen,
    LParen,

    RSquare,
    LSquare,
    Comma,

    If,
    Then,
    Else,
    End,

    Loop,
    Block,
    Break,

    True,
    False,

    Terminator,
}

impl TokenType {
    pub fn len(&self) -> usize {
        match self {
            TokenType::Number(n) => n.to_string().len(),
            TokenType::Identifier(s) | TokenType::String(s) => s.len(),

            TokenType::DoubleDot => 2,

            TokenType::Plus => 1,
            TokenType::Minus => 1,
            TokenType::Star => 1,
            TokenType::Slash => 1,
            TokenType::Hat => 1,

            TokenType::Equals => 1,
            TokenType::Greater => 1,
            TokenType::Less => 1,
            TokenType::GreaterEquals => 2,
            TokenType::LessEquals => 2,

            TokenType::ColonEquals => 2,
            TokenType::LeftArrow => 2,
            TokenType::RightArrow => 2,

            TokenType::RParen => 1,
            TokenType::LParen => 1,

            TokenType::RSquare => 1,
            TokenType::LSquare => 1,
            TokenType::Comma => 1,

            TokenType::If => 2,
            TokenType::Then => 4,
            TokenType::Else => 4,
            TokenType::End => 3,

            TokenType::Loop => 4,
            TokenType::Block => 5,
            TokenType::Break => 5,

            TokenType::True => 4,
            TokenType::False => 5,

            Self::Terminator => 1,
        }
    }
}

pub struct Lexer<'a> {
    source: Peekable<Chars<'a>>,
}

#[derive(Debug)]
pub enum LexerError {
    UnexpectedChar(usize, String),
}

impl<'a> Lexer<'a> {
    pub fn new(source: &'a String) -> Self {
        Self {
            source: source.chars().peekable(),
        }
    }

    pub fn lex(&'a mut self) -> Result<Vec<Token>, LexerError> {
        let mut i = 0;
        let mut tokens = vec![];

        while let Some(c) = self.source.next() {
            match c {
                // Collapse spaces into a single Space token
                ' ' => {
                    while self.source.peek() == Some(&' ') {
                        self.source.next();
                        i += 1;
                    }
                    // tokens.push(Token(i, TokenType::Space));
                }
                // Comments with #
                '#' => {
                    while self.source.next() != Some('\n') {
                        i += 1;
                    }
                }
                // Double period ..
                '.' if self.source.peek() == Some(&'.') => {
                    self.source.next();
                    i += 1;
                    tokens.push(Token(i, TokenType::DoubleDot));
                }
                // Numbers
                '0'..='9' | '.' => {
                    let mut digits = String::from(c);
                    let mut has_decimal = c == '.';
                    let mut push = true;
                    loop {
                        let d = self.source.peek();
                        match d {
                            Some('0'..='9') => {
                                digits.push(self.source.next().unwrap());
                                i += 1;
                            }
                            Some('.') => {
                                let current = self.source.next();

                                if self.source.peek() == Some(&'.') {
                                    // useless clone, TODO: Report this
                                    tokens.push(Token(i, TokenType::Number(digits.clone())));
                                    push = false;

                                    self.source.next();
                                    i += 2;
                                    tokens.push(Token(i, TokenType::DoubleDot));
                                    break;
                                }

                                if has_decimal {
                                    return Err(LexerError::UnexpectedChar(
                                        i + 1,
                                        "Invalid digit with multiple decimal points".into(),
                                    ));
                                }

                                digits.push(current.unwrap());
                                i += 1;
                                has_decimal = true;
                            }
                            _ => {
                                break;
                            }
                        }
                    }

                    if push {
                        tokens.push(Token(i, TokenType::Number(digits)));
                    }
                }

                '"' => {
                    let mut buffer = "".to_owned();
                    loop {
                        let next = self.source.peek();

                        match next {
                            Some('"') => {
                                tokens.push(Token(i, TokenType::String(buffer.clone())));
                                self.source.next();
                                break;
                            }
                            Some(_) => {
                                buffer.push(self.source.next().unwrap());
                            }
                            None => {
                                return Err(LexerError::UnexpectedChar(
                                    i,
                                    "Unexpected End of file".to_owned(),
                                ));
                            }
                        }
                    }
                }

                // LeftArrow (->)
                '-' if self.source.peek() == Some(&'>') => {
                    self.source.next();
                    i += 1;
                    tokens.push(Token(i, TokenType::LeftArrow));
                }
                '<' if self.source.peek() == Some(&'-') => {
                    self.source.next();
                    i += 1;
                    tokens.push(Token(i, TokenType::RightArrow));
                }

                '<' if self.source.peek() == Some(&'=') => {
                    self.source.next();
                    i += 1;
                    tokens.push(Token(i, TokenType::LessEquals));
                }
                '>' if self.source.peek() == Some(&'=') => {
                    self.source.next();
                    i += 1;
                    tokens.push(Token(i, TokenType::GreaterEquals));
                }
                '<' => tokens.push(Token(i, TokenType::Less)),
                '>' => tokens.push(Token(i, TokenType::Greater)),

                '+' => tokens.push(Token(i, TokenType::Plus)),
                '-' => tokens.push(Token(i, TokenType::Minus)),
                '*' => tokens.push(Token(i, TokenType::Star)),
                '/' => tokens.push(Token(i, TokenType::Slash)),
                '=' => tokens.push(Token(i, TokenType::Equals)),
                ',' => tokens.push(Token(i, TokenType::Comma)),
                '^' => tokens.push(Token(i, TokenType::Hat)),

                ':' if self.source.peek() == Some(&'=') => {
                    self.source.next();
                    i += 1;
                    tokens.push(Token(i, TokenType::ColonEquals));
                }

                ';' => tokens.push(Token(i, TokenType::Terminator)),

                '(' => tokens.push(Token(i, TokenType::LParen)),
                ')' => tokens.push(Token(i, TokenType::RParen)),

                '[' => tokens.push(Token(i, TokenType::LSquare)),
                ']' => tokens.push(Token(i, TokenType::RSquare)),

                _ if c.is_alphabetic() || c == '_' => {
                    tokens.push(self.lex_identifier(&mut i, c)?);
                }

                '\n' => {
                    continue;
                }

                _ => {
                    return Err(LexerError::UnexpectedChar(
                        i,
                        format!("Unexpected char {c} ({c:?})"),
                    ));
                }
            }
            i += 1;
        }

        Ok(tokens)
    }

    fn lex_identifier<'b>(&'b mut self, i: &mut usize, c: char) -> Result<Token, LexerError> {
        let mut identifier = c.to_string();

        while let Some(c) = self.source.peek() {
            if c.is_alphanumeric() || c == &'_' || c == &'\'' {
                identifier.push(*c);
                self.source.next();
                *i += 1;
            } else {
                break;
            }
        }

        Ok(Token(
            *i,
            match identifier.to_lowercase().as_str() {
                "if" => TokenType::If,
                "then" => TokenType::Then,
                "else" => TokenType::Else,
                "end" => TokenType::End,

                "loop" => TokenType::Loop,
                "block" => TokenType::Block,
                "break" => TokenType::Break,

                "true" => TokenType::True,
                "false" => TokenType::False,

                _ => TokenType::Identifier(identifier),
            },
        ))
    }
}

#[derive(Debug)]
pub enum ParserError {
    UnexpectedEndOfTokens(String),
    UnexpectedToken(usize, usize, String),
    Unimplemented(usize, usize, String),
    UnexpectedNode(usize, String),
    NumberParse(usize, String),
}

/// Recursive descent parser
pub struct Parser<'a> {
    tokens: Peekable<IntoIter<Token>>,
    environment: &'a mut Environment,
    previous: Option<Token>,
}

// type Tokens<'a> = Peekable<Iter<'a, Token>>;

impl<'a> Parser<'a> {
    pub fn new(tokens: Vec<Token>, env: &'a mut Environment) -> Self {
        // #[cfg(debug_assertions)]
        // println!("\r{tokens:?}");
        Self {
            tokens: tokens.into_iter().peekable(),
            environment: env,
            previous: None,
        }
    }

    // Parse tokens recursively and descendentantly
    pub fn parse(&mut self) -> Result<Vec<Rc<NodeEnum>>, ParserError> {
        let mut expressions = vec![];

        if self.tokens.len() == 0 {
            return Ok(expressions);
        }

        loop {
            expressions.push(self.expression()?);
            self.match_type(TokenType::Terminator);

            if self.is_at_end() {
                break;
            }
        }

        Ok(expressions)
    }

    #[inline]
    fn consume<'b>(&'b mut self) -> &'b Option<Token> {
        self.previous = self.tokens.next();
        &self.previous
    }

    #[inline]
    fn is_at_end(&mut self) -> bool {
        if self.tokens.peek().is_none() {
            return true;
        }

        false
    }

    /// Checks if the next token is `t`, if it is then consume it and return true. Otherwise does
    /// nothing and returns false.
    #[inline]
    fn match_type<'b>(&'b mut self, t: TokenType) -> bool {
        if let Some(Token(_, token_type)) = self.tokens.peek() {
            if *token_type == t {
                self.consume();
                return true;
            }
        }
        false
    }

    #[inline]
    fn match_or_err(&mut self, t: TokenType) -> Result<bool, ParserError> {
        let (i, tt) = if let Some(Token(i, tt)) = self.tokens.peek() {
            (*i, tt.clone())
        } else {
            return Err(ParserError::UnexpectedEndOfTokens(format!(
                "Expected {t:?} but found nothing instead"
            )));
        };

        if self.match_type(t.clone()) {
            Ok(true)
        } else {
            Err(ParserError::UnexpectedToken(
                i,
                tt.len(),
                format!("Expected {t:?} but found {tt:?} instead"),
            ))
        }
    }

    fn expression(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        self.assignment()
    }

    fn assignment(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        let expr = self.equality()?;

        if self.match_type(TokenType::ColonEquals) {
            return Ok(Rc::new(Assign::new(expr, self.equality()?, true).into()));
        } else if self.match_type(TokenType::RightArrow) {
            return Ok(Rc::new(Assign::new(expr, self.equality()?, false).into()));
        }

        Ok(expr)
    }

    fn equality(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        // TODO: Implement equality
        let expr = self.comparison()?;
        if self.match_type(TokenType::Equals) {
            let mut expressions = vec![];
            loop {
                expressions.push(self.comparison()?);
                if !self.match_type(TokenType::Equals) {
                    break;
                }
            }
            return Ok(Rc::new(Equals::new(expr, expressions).into()));
        }
        Ok(expr)
    }

    fn comparison(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        // TODO: Implement comparison
        let expr = self.term()?;

        let t = if let Some(Token(_i, t)) = self.tokens.peek() {
            t.clone()
        } else {
            return Ok(expr);
        };

        if match t {
            TokenType::Greater => true,
            TokenType::Less => true,
            TokenType::GreaterEquals => true,
            TokenType::LessEquals => true,
            _ => false,
        } {
            self.consume();
            let mut expressions = vec![];
            loop {
                expressions.push(self.term()?);
                if !self.match_type(t.clone()) {
                    break;
                }
            }

            return match t {
                TokenType::Greater => Ok(Greater::new(expr, expressions)),
                TokenType::Less => Ok(Less::new(expr, expressions)),
                TokenType::GreaterEquals => Ok(GreaterEquals::new(expr, expressions)),
                TokenType::LessEquals => Ok(LessEquals::new(expr, expressions)),
                _ => panic!(),
            };
        }

        Ok(expr)
    }

    fn term(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        let mut expr = self.factor()?;
        loop {
            if self.match_type(TokenType::Plus) {
                let right = self.factor()?;
                expr = Add::new_rc(expr, right);
                continue;
            } else if self.match_type(TokenType::Minus) {
                let right = self.factor()?;
                expr = Subtract::new_rc(expr, right);
                continue;
            }
            break;
        }

        Ok(expr)
    }

    fn factor(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        let mut expr = self.unary()?;

        loop {
            if self.match_type(TokenType::Star) {
                let right = self.unary()?;
                expr = Multiply::new_rc(expr, right);
                continue;
            } else if self.match_type(TokenType::Slash) {
                let right = self.unary()?;
                expr = Divide::new_rc(expr, right);
                continue;
            }
            break;
        }

        // // Implicit multiplication
        // if !self.is_at_end() {
        //     match self.tokens.peek().unwrap() {
        //         Token(_, TokenType::Identifier(_)) | Token(_, TokenType::Number(_)) => {
        //             return Ok(Multiply::new_rc(expr, self.equality()?));
        //         }
        //         Token(_, TokenType::LParen) => {
        //             if let NodeEnum::Symbol(_) = expr.as_ref() {
        //                 return Ok(expr);
        //             }
        //
        //             return Ok(Multiply::new_rc(expr, self.equality()?));
        //         }
        //         _ => {}
        //     }
        // }

        Ok(expr)
    }

    fn unary(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        self.exponent()
    }

    fn exponent(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        let expr = self.call();

        if self.match_type(TokenType::Hat) {
            let right = self.unary()?;
            return Ok(Exponent::new(expr?, right));
        }

        expr
    }

    fn call(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        // Left hand side
        let mut expr = self.function()?;

        // Calls are right-associative, so we evaluate right-to-left
        loop {
            let (i, t) = if let Some(Token(i, x)) = self.tokens.peek() {
                (*i, x.clone())
            } else {
                return Ok(expr);
            };

            // If the next token is a parenthesis then we construct a call
            if t == TokenType::LParen {
                self.consume();

                // Calls can have 0 arguments, so check and return early
                if self.match_type(TokenType::RParen) {
                    expr = Call::new(expr, vec![]);
                } else {
                    // Parse expressions until a patching Right-Parenthesis is found
                    let mut parameters = vec![self.equality()?];

                    while self.match_type(TokenType::Comma) {
                        parameters.push(self.equality()?);
                    }

                    if !self.match_type(TokenType::RParen) {
                        return Err(ParserError::UnexpectedToken(
                            i,
                            t.len(),
                            "Unclosed right parenthesis".to_owned(),
                        ));
                    }

                    // If the next token is a ColonEquals (assignment) then
                    // the user wants function assignment sugar
                    //
                    // Ie f(x) := x*5 => f := x -> x*5
                    if self.match_type(TokenType::ColonEquals) {
                        if let NodeEnum::Symbol(_) = expr.as_ref() {
                        } else {
                            let Token(i, token) = self.previous.as_ref().unwrap();
                            return Err(ParserError::UnexpectedToken(
                                *i,
                                token.len(),
                                format!(
                                    "Expected an Identifier here but found a {}",
                                    expr.type_str()
                                ),
                            ));
                        };

                        // Parse body
                        let body = self.equality()?;

                        // Convert vector of expressions to vector of symbols
                        let mut arguments = Vec::with_capacity(parameters.len());
                        for param in parameters.into_iter() {
                            if let NodeEnum::Symbol(symbol) =
                                Rc::<NodeEnum>::try_unwrap(param).unwrap()
                            {
                                arguments.push(symbol);
                            } else {
                                return Err(ParserError::UnexpectedToken(
                                    i,
                                    t.len(),
                                    format!("One or more argument is not a Symbol",),
                                ));
                            }
                        }

                        // Early exit with new desugared expression
                        return Ok(Rc::new(
                            Assign::new(
                                expr,
                                Function::new(FunctionType::UserFunction(body, arguments)),
                                true,
                            )
                            .into(),
                        ));
                    } else {
                        expr = Call::new(expr, parameters);
                    }
                }
            } else {
                break;
            }
        }

        Ok(expr)
    }

    fn function(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        let error_loc = if let Some(Token(i, _)) = self.tokens.peek() {
            *i
        } else {
            0
        };

        let expr = self.if_else()?;

        if self.match_type(TokenType::LeftArrow) {
            let right = self.equality()?;
            match expr.clone().as_ref() {
                NodeEnum::Symbol(symbol) => {
                    return Ok(Function::new(FunctionType::UserFunction(
                        right,
                        vec![symbol.clone()],
                    )));
                }
                NodeEnum::Set(set) => {
                    let mut symbols = vec![];
                    for (i, value) in set.get_values().into_iter().enumerate() {
                        match value.as_ref() {
                            NodeEnum::Symbol(symbol) => symbols.push(symbol.clone()),

                            _ => {
                                return Err(ParserError::UnexpectedNode(
                                    error_loc,
                                    format!(
                                        "Expected set of Identifiers, but argument #{i} is a {value:?}"
                                    ),
                                ));
                            }
                        }
                    }

                    return Ok(Function::new(FunctionType::UserFunction(right, symbols)));
                }
                _ => {
                    return Err(ParserError::UnexpectedNode(
                        error_loc,
                        format!("Expected Set, got {:?}", expr),
                    ));
                }
            }
        }

        Ok(expr)
    }

    fn if_else(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        if self.match_type(TokenType::If) {
            let condition = self.equality()?;

            let _ = self.match_or_err(TokenType::Then)?;

            let mut expressions = vec![];

            while !(self.match_type(TokenType::End) || self.match_type(TokenType::Else)) {
                if self.is_at_end() {
                    return Err(ParserError::UnexpectedEndOfTokens(
                        "Expected an else or end here".to_owned(),
                    ));
                }
                expressions.push(self.expression()?);
            }

            // Safe to unwrap since the while loop would terminate if previous was none (it didnt
            // find an End or Else before running out of tokens)
            let else_branch = match self.previous.as_ref().unwrap() {
                Token(_, TokenType::End) => ElseBranchEnum::None,
                Token(_, TokenType::Else) => {
                    if let Some(Token(_, TokenType::If)) = self.tokens.peek() {
                        ElseBranchEnum::ElseIf(self.if_else()?)
                    } else {
                        let mut expressions = vec![];
                        while !self.match_type(TokenType::End) {
                            if self.is_at_end() {
                                return Err(ParserError::UnexpectedEndOfTokens(
                                    "Expected an end here".to_owned(),
                                ));
                            }
                            expressions.push(self.expression()?);
                            let _ = self.match_type(TokenType::Terminator);
                        }

                        ElseBranchEnum::Block(expressions)
                    }
                }

                _ => panic!("Not possible"),
            };

            return Ok(IfElse::new(condition, expressions, else_branch));
        }

        self.r#loop()
    }

    fn r#loop(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        if self.match_type(TokenType::Loop) {
            let mut body = vec![];
            while !self.match_type(TokenType::End) {
                body.push(self.expression()?);
                let _ = self.match_type(TokenType::Terminator);
            }
            return Ok(Loop::new(body));
        }

        self.set()
    }

    fn set(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        if self.match_type(TokenType::LSquare) {
            // Empty set
            if self.match_type(TokenType::RSquare) {
                return Ok(Set::new(vec![]));
            }

            let mut values = vec![self.equality()?];
            while {
                if let Some(Token(_, TokenType::RSquare)) = self.tokens.peek() {
                    self.consume();
                    false
                } else {
                    true
                }
            } {
                let (i, token) = if let Some(Token(i, x)) = self.tokens.peek() {
                    (i, x)
                } else {
                    return Err(ParserError::UnexpectedEndOfTokens(
                        "Expected comma here".into(),
                    ));
                };

                if *token == TokenType::Comma {
                    self.consume();
                } else {
                    return Err(ParserError::UnexpectedToken(
                        *i,
                        token.len(),
                        format!("Expected comma here, but got {token:?}"),
                    ));
                }

                values.push(self.equality()?);
            }

            return Ok(Set::new(values));
        }
        self.range()
    }

    fn range(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        let exp = self.primary()?;

        if self.match_type(TokenType::DoubleDot) {
            let to = self.primary()?;

            return Ok(Range::new(exp, to));
        }

        Ok(exp)
    }

    fn primary(&mut self) -> Result<Rc<NodeEnum>, ParserError> {
        let (i, token) = if let Some(Token(i, token)) = self.tokens.next() {
            (i, token)
        } else {
            return Err(ParserError::UnexpectedEndOfTokens(
                "Expected a Primary here".into(),
            ));
        };

        let expr = match token {
            TokenType::Number(value) => {
                let value = if let Ok(incomplete) = Float::parse(&value) {
                    Float::with_val_64(self.environment.get_float_precision(), incomplete)
                } else {
                    return Err(ParserError::NumberParse(
                        i,
                        format!("Failed to convert `{value}` to a number"),
                    ));
                };
                Ok(Rc::new(Constant::new(value).into()))
            }
            TokenType::Identifier(string) => Ok(Rc::new(
                Symbol::new_from_str(string, self.environment).into(),
            )),
            TokenType::True => Ok(Rc::new(Bool::True.into())),
            TokenType::False => Ok(Rc::new(Bool::False.into())),

            TokenType::Break => Ok(Rc::new(Break.into())),

            TokenType::String(s) => Ok(StringNode::new(s)),

            TokenType::LParen => {
                let expr = self.expression()?;

                if !self.match_type(TokenType::RParen) {
                    if let Some(Token(i, t)) = self.tokens.peek() {
                        return Err(ParserError::UnexpectedToken(
                            *i,
                            t.len(),
                            format!("Expected right parenthesis here, but got {t:?}"),
                        ));
                    } else {
                        return Err(ParserError::UnexpectedToken(
                            i,
                            1,
                            "Unclosed right parenthesis".to_owned(),
                        ));
                    }
                }

                Ok(expr)
            }
            _ => Err(ParserError::UnexpectedToken(
                i,
                token.len(),
                format!("Unexpected token {token:?}"),
            )),
        };

        expr
    }
}
