use std::cell::Cell;

use crate::analysis;
use crate::ast::Identifier;
use crate::calculation_result::CalculationResult;
use crate::errors::KalkError;
use crate::{
    ast::{Expr, Stmt},
    interpreter,
    lexer::{Lexer, Token, TokenKind},
    symbol_table::SymbolTable,
};
use wasm_bindgen::prelude::*;

pub const DECL_UNIT: &str = ".u";
pub const DEFAULT_ANGLE_UNIT: &str = "rad";

/// Struct containing the current state of the parser. It stores user-defined functions and variables.
#[wasm_bindgen]
pub struct Context {
    tokens: Vec<Token>,
    pos: usize,
    symbol_table: Cell<SymbolTable>,
    angle_unit: String,
    timeout: Option<u32>,
    /// This is true whenever the parser is currently parsing a unit declaration.
    /// It is necessary to keep track of this in order to know when to find (figure out) units that haven't been defined yet.
    /// Unit names are instead treated as variables.
    parsing_unit_decl: bool,
    /// When a unit declaration is being parsed, this value will be set
    /// whenever a unit in the expression is found. Eg. unit a = 3b, it will be set to Some("b")
    unit_decl_base_unit: Option<String>,
    other_radix: Option<u8>,
    current_stmt_start_pos: usize,
}

#[wasm_bindgen]
impl Context {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        let mut context = Self {
            tokens: Vec::new(),
            pos: 0,
            symbol_table: Cell::from(SymbolTable::new()),
            angle_unit: DEFAULT_ANGLE_UNIT.into(),
            timeout: None,
            parsing_unit_decl: false,
            unit_decl_base_unit: None,
            other_radix: None,
            current_stmt_start_pos: 0,
        };

        parse(&mut context, crate::prelude::INIT).unwrap();

        context
    }

    pub fn set_angle_unit(mut self, unit: &str) -> Self {
        self.angle_unit = unit.into();

        self
    }

    /// Set the timeout in milliseconds.
    /// The calculation will stop after this amount of time has passed.
    #[cfg(not(target_arch = "wasm32"))]
    pub fn set_timeout(mut self, timeout: Option<u32>) -> Self {
        self.timeout = timeout;

        self
    }

    #[wasm_bindgen(js_name = evaluate)]
    #[cfg(not(feature = "rug"))]
    pub fn js_eval(&mut self, input: &str) -> Result<Option<CalculationResult>, JsValue> {
        let result = eval(self, input);

        match result {
            Ok(Some(value)) => Ok(Some(value)),
            Ok(None) => Ok(None),
            Err(err) => Err(err.to_string().into()),
        }
    }
}

impl Default for Context {
    fn default() -> Self {
        Self::new()
    }
}

/// Evaluate expressions/declarations and return the answer.
///
/// `None` will be returned if the last statement is a declaration.
pub fn eval(
    context: &mut Context,
    input: &str,
    #[cfg(feature = "rug")] precision: u32,
) -> Result<Option<CalculationResult>, KalkError> {
    let statements = parse(context, input)?;

    let symbol_table = context.symbol_table.get_mut();
    let mut interpreter = interpreter::Context::new(
        symbol_table,
        &context.angle_unit,
        #[cfg(feature = "rug")]
        precision,
        context.timeout.map(|timeout| timeout as u128),
    );
    let result = interpreter.interpret(statements);
    if let Ok(Some(mut num)) = result {
        num.set_radix(context.other_radix.unwrap_or(10));
        Ok(Some(num))
    } else {
        result
    }
}

/// Parse expressions/declarations and return a syntax tree.
///
/// `None` will be returned if the last statement is a declaration.
pub fn parse(context: &mut Context, input: &str) -> Result<Vec<Stmt>, KalkError> {
    let mut lexer = Lexer::new(input);
    context.tokens = lexer.lex();
    context.pos = 0;
    context.parsing_unit_decl = false;
    context.unit_decl_base_unit = None;
    context.other_radix = lexer.get_other_radix();

    let mut statements: Vec<Stmt> = Vec::new();
    while !is_at_end(context) {
        context.current_stmt_start_pos = context.pos;
        let parsed = match parse_stmt(context) {
            Ok(stmt) => stmt,
            Err(KalkError::WasStmt(stmt)) => stmt,
            Err(err) => return Err(err),
        };
        let symbol_table = context.symbol_table.get_mut();
        let analysed = analysis::analyse_stmt(symbol_table, parsed)?;
        statements.push(analysed);

        if match_token(context, TokenKind::Semicolon) {
            advance(context);
        }

        skip_newlines(context);
    }

    Ok(statements)
}

fn parse_stmt(context: &mut Context) -> Result<Stmt, KalkError> {
    if match_token(context, TokenKind::UnitKeyword) {
        parse_unit_decl_stmt(context)
    } else {
        Ok(Stmt::Expr(Box::new(parse_expr(context)?)))
    }
}

fn parse_piecewise(context: &mut Context) -> Result<Expr, KalkError> {
    advance(context);
    skip_newlines(context);

    let mut pieces = Vec::new();
    let mut reached_otherwise = false;
    // Parse `expr if condition`
    // do-while loop
    while {
        let left_expr = parse_expr(context)?;
        if match_token(context, TokenKind::IfKeyword) {
            advance(context);
            pieces.push(crate::ast::ConditionalPiece {
                expr: left_expr,
                condition: parse_expr(context)?,
            });
        } else if match_token(context, TokenKind::OtherwiseKeyword) {
            advance(context);
            // Yeah, a bit hacky, but there's no `true` keyword...
            let true_expr = Expr::Binary(
                Box::new(Expr::Literal(1f64)),
                TokenKind::Equals,
                Box::new(Expr::Literal(1f64)),
            );
            pieces.push(crate::ast::ConditionalPiece {
                expr: left_expr,
                condition: true_expr,
            });

            reached_otherwise = true;
        } else {
            return Err(KalkError::ExpectedIf);
        }

        if match_token(context, TokenKind::Semicolon) {
            advance(context);
        }
        skip_newlines(context);

        (previous(context).kind == TokenKind::Semicolon
            || previous(context).kind == TokenKind::Newline)
            && !reached_otherwise
            && !match_token(context, TokenKind::ClosedBrace)
    } {}

    advance(context);

    Ok(Expr::Piecewise(pieces))
}

fn parse_unit_decl_stmt(context: &mut Context) -> Result<Stmt, KalkError> {
    advance(context); // Unit keyword
    let identifier = advance(context).clone();
    consume(context, TokenKind::Equals)?;

    // Parse the mut definition
    context.unit_decl_base_unit = None;
    context.parsing_unit_decl = true;
    let def = parse_expr(context)?;
    context.parsing_unit_decl = false;

    let base_unit = if let Some(base_unit) = &context.unit_decl_base_unit {
        base_unit.clone()
    } else {
        return Err(KalkError::InvalidUnit);
    };

    // Automatically create a second unit decl with the expression inverted.
    // This will turn eg. unit a = 3b, into unit b = a/3
    // This is so that you only have to define `a`, and it will figure out the formula for `b` since it is used in the formula for `a`.
    let stmt_inv = Stmt::UnitDecl(
        base_unit.clone(),
        identifier.value.clone(),
        Box::new(def.invert(context.symbol_table.get_mut(), DECL_UNIT)?),
    );
    let stmt = Stmt::UnitDecl(identifier.value, base_unit, Box::new(def));

    context.symbol_table.get_mut().insert(stmt.clone());
    context.symbol_table.get_mut().insert(stmt_inv);

    Ok(stmt)
}

fn parse_expr(context: &mut Context) -> Result<Expr, KalkError> {
    parse_or(context)
}

fn parse_comprehension(context: &mut Context) -> Result<Expr, KalkError> {
    let left = parse_or(context)?;

    if match_token(context, TokenKind::Colon) {
        let op = advance(context).kind;
        skip_newlines(context);
        let right = Box::new(parse_comprehension_comma(context)?);
        return Ok(Expr::Binary(Box::new(left), op, right));
    }

    Ok(left)
}

fn parse_comprehension_comma(context: &mut Context) -> Result<Expr, KalkError> {
    let left = parse_or(context)?;

    if match_token(context, TokenKind::Comma) {
        let op = advance(context).kind;
        skip_newlines(context);
        let right = Box::new(parse_comprehension_comma(context)?);
        return Ok(Expr::Binary(Box::new(left), op, right));
    }

    Ok(left)
}

fn parse_or(context: &mut Context) -> Result<Expr, KalkError> {
    let left = parse_and(context)?;

    if match_token(context, TokenKind::Or) {
        let op = advance(context).kind;
        skip_newlines(context);
        let right = Box::new(parse_or(context)?);
        return Ok(Expr::Binary(Box::new(left), op, right));
    }

    Ok(left)
}

fn parse_and(context: &mut Context) -> Result<Expr, KalkError> {
    let left = parse_comparison(context)?;

    if match_token(context, TokenKind::And) {
        let op = advance(context).kind;
        skip_newlines(context);
        let right = Box::new(parse_and(context)?);
        return Ok(Expr::Binary(Box::new(left), op, right));
    }

    Ok(left)
}

fn parse_comparison(context: &mut Context) -> Result<Expr, KalkError> {
    let at_start_of_line = context.current_stmt_start_pos == context.pos;
    let mut left = parse_to(context)?;

    // Equality check
    while match_token(context, TokenKind::Equals)
        || match_token(context, TokenKind::NotEquals)
        || match_token(context, TokenKind::GreaterThan)
        || match_token(context, TokenKind::LessThan)
        || match_token(context, TokenKind::GreaterOrEquals)
        || match_token(context, TokenKind::LessOrEquals)
    {
        let op = advance(context).kind;

        if let (true, Some((identifier, parameters))) =
            (at_start_of_line, analysis::is_fn_decl(&left))
        {
            context.symbol_table.get_mut().set(Stmt::FnDecl(
                identifier.clone(),
                parameters.clone(),
                Box::new(Expr::Literal(1f64)),
            ));
            let right = if match_token(context, TokenKind::OpenBrace) {
                parse_piecewise(context)?
            } else {
                parse_expr(context)?
            };
            let fn_decl = Stmt::FnDecl(identifier, parameters, Box::new(right));

            // Hack to return a statement...
            return Err(KalkError::WasStmt(fn_decl));
        };

        let right = parse_comparison(context)?;

        left = match right {
            Expr::Binary(
                inner_left,
                inner_op @ (TokenKind::Equals
                | TokenKind::NotEquals
                | TokenKind::GreaterThan
                | TokenKind::LessThan
                | TokenKind::GreaterOrEquals
                | TokenKind::LessOrEquals),
                inner_right,
            ) => Expr::Binary(
                Box::new(Expr::Binary(
                    Box::new(left),
                    op,
                    Box::new(*inner_left.clone()),
                )),
                TokenKind::And,
                Box::new(Expr::Binary(
                    Box::new(*inner_left),
                    inner_op,
                    Box::new(*inner_right),
                )),
            ),
            _ => Expr::Binary(Box::new(left), op, Box::new(right)),
        }
    }

    Ok(left)
}

fn parse_to(context: &mut Context) -> Result<Expr, KalkError> {
    let left = parse_term(context)?;

    if match_token(context, TokenKind::ToKeyword) {
        advance(context);
        let right = Expr::Var(Identifier::from_full_name(&advance(context).value)); // Parse this as a variable for now.

        return Ok(Expr::Binary(
            Box::new(left),
            TokenKind::ToKeyword,
            Box::new(right),
        ));
    }

    Ok(left)
}

fn parse_term(context: &mut Context) -> Result<Expr, KalkError> {
    let mut left = parse_factor(context)?;

    while match_token(context, TokenKind::Plus) || match_token(context, TokenKind::Minus) {
        let op = peek(context).kind;
        advance(context);
        let right = parse_factor(context)?;

        left = Expr::Binary(Box::new(left), op, Box::new(right));
    }

    Ok(left)
}

fn parse_factor(context: &mut Context) -> Result<Expr, KalkError> {
    let mut left = parse_unit(context)?;

    if let Expr::Unary(TokenKind::Percent, percent_left) = left.clone() {
        let try_parse = parse_unit(context);
        if try_parse.is_ok() {
            left = Expr::Binary(percent_left, TokenKind::Percent, Box::new(try_parse?));
        }
    }

    while match_token(context, TokenKind::Star)
        || match_token(context, TokenKind::Slash)
        || match_token(context, TokenKind::Percent)
        || match_token(context, TokenKind::Identifier)
        || match_token(context, TokenKind::Literal)
        || match_token(context, TokenKind::OpenParenthesis)
        || match_token(context, TokenKind::OpenCeil)
        || match_token(context, TokenKind::OpenFloor)
        || match_token(context, TokenKind::OpenBracket)
    {
        // If the token is an identifier, literal, or open parenthesis,
        // assume it's multiplication. Eg. 3y or (3x + 2)(2 + 3)
        let op = match peek(context).kind {
            TokenKind::Identifier
            | TokenKind::Literal
            | TokenKind::OpenParenthesis
            | TokenKind::OpenCeil
            | TokenKind::OpenFloor
            | TokenKind::OpenBracket => TokenKind::Star,
            _ => advance(context).kind,
        };

        let right = parse_unit(context)?;

        left = Expr::Binary(Box::new(left), op, Box::new(right));
    }

    Ok(left)
}

fn parse_unit(context: &mut Context) -> Result<Expr, KalkError> {
    let expr = parse_exponent(context)?;

    if match_token(context, TokenKind::Identifier) {
        let peek = &peek(context).value.clone();
        if context.symbol_table.get_mut().contains_unit(peek) {
            return Ok(Expr::Unit(
                advance(context).value.to_string(),
                Box::new(expr),
            ));
        }
    }

    Ok(expr)
}

fn parse_exponent(context: &mut Context) -> Result<Expr, KalkError> {
    let left = parse_unary(context)?;

    if match_token(context, TokenKind::Power) {
        let op = advance(context).kind;
        let right = Box::new(parse_exponent(context)?);

        return Ok(Expr::Binary(Box::new(left), op, right));
    }

    Ok(left)
}

fn parse_unary(context: &mut Context) -> Result<Expr, KalkError> {
    if match_token(context, TokenKind::Minus) || match_token(context, TokenKind::Not) {
        let op = advance(context).kind;
        let expr = Box::new(parse_unary(context)?);

        return Ok(Expr::Unary(op, expr));
    }

    let expr = parse_indexer(context)?;
    if match_token(context, TokenKind::Percent) {
        Ok(Expr::Unary(advance(context).kind, Box::new(expr)))
    } else {
        Ok(expr)
    }
}

fn parse_indexer(context: &mut Context) -> Result<Expr, KalkError> {
    let left = parse_factorial(context)?;

    if match_token(context, TokenKind::OpenDoubleBracket) {
        advance(context);
        let mut indexes = vec![parse_expr(context)?];
        while match_token(context, TokenKind::Comma) {
            advance(context);
            indexes.push(parse_expr(context)?);
        }

        consume(context, TokenKind::ClosedDoubleBracket)?;

        return Ok(Expr::Indexer(Box::new(left), indexes));
    }

    Ok(left)
}

fn parse_factorial(context: &mut Context) -> Result<Expr, KalkError> {
    let expr = parse_primary(context)?;

    Ok(if match_token(context, TokenKind::Exclamation) {
        advance(context);
        Expr::Unary(TokenKind::Exclamation, Box::new(expr))
    } else {
        expr
    })
}

fn parse_primary(context: &mut Context) -> Result<Expr, KalkError> {
    let expr = match peek(context).kind {
        TokenKind::OpenParenthesis | TokenKind::OpenBracket => parse_vector(context)?,
        TokenKind::Pipe | TokenKind::OpenCeil | TokenKind::OpenFloor => parse_group_fn(context)?,
        TokenKind::Identifier => parse_identifier(context)?,
        TokenKind::Literal => Expr::Literal(string_to_num(&advance(context).value)?),
        TokenKind::True => {
            advance(context);
            Expr::Boolean(true)
        }
        TokenKind::False => {
            advance(context);
            Expr::Boolean(false)
        }
        _ => return Err(KalkError::UnexpectedToken(peek(context).kind, None)),
    };

    Ok(expr)
}

fn parse_group_fn(context: &mut Context) -> Result<Expr, KalkError> {
    let name = match &peek(context).kind {
        TokenKind::Pipe => "abs",
        TokenKind::OpenCeil => "ceil",
        TokenKind::OpenFloor => "floor",
        _ => unreachable!(),
    };

    match parse_vector(context)? {
        Expr::Vector(arguments) => Ok(Expr::FnCall(Identifier::from_full_name(name), arguments)),
        Expr::Group(argument) => Ok(Expr::FnCall(
            Identifier::from_full_name(name),
            vec![*argument],
        )),
        _ => unreachable!(),
    }
}

fn parse_vector(context: &mut Context) -> Result<Expr, KalkError> {
    let kind = advance(context).kind;

    if kind == TokenKind::OpenBracket {
        skip_newlines(context);
    }

    let first_expr = if kind == TokenKind::OpenBracket {
        parse_comprehension(context)?
    } else {
        parse_expr(context)?
    };
    let mut rows = vec![vec![first_expr]];
    let mut column_count = None;
    let mut items_in_row = 1;
    while match_token(context, TokenKind::Comma)
        || match_token(context, TokenKind::Semicolon)
        || (match_token(context, TokenKind::Newline)
            && peek_next(context).kind != TokenKind::ClosedBracket)
    {
        if kind == TokenKind::OpenBracket
            && (match_token(context, TokenKind::Newline)
                || match_token(context, TokenKind::Semicolon))
        {
            if let Some(columns) = column_count {
                if columns != items_in_row {
                    return Err(KalkError::InconsistentColumnWidths);
                }
            } else {
                column_count = Some(items_in_row);
            }

            rows.push(Vec::new());
            items_in_row = 0;
        }

        advance(context);
        rows.last_mut().unwrap().push(parse_expr(context)?);
        items_in_row += 1;
    }

    if peek(context).kind == TokenKind::Eof {
        return Err(KalkError::Expected(String::from(
            "Closing group symbol, eg. )",
        )));
    }

    if kind == TokenKind::OpenBracket {
        skip_newlines(context);
    }

    advance(context);

    if rows.len() == 1 {
        let mut values = rows.pop().unwrap();
        if values.len() == 1 {
            Ok(Expr::Group(Box::new(values.pop().unwrap())))
        } else {
            Ok(Expr::Vector(values))
        }
    } else {
        Ok(Expr::Matrix(rows))
    }
}

fn parse_identifier(context: &mut Context) -> Result<Expr, KalkError> {
    let at_start_of_line = context.current_stmt_start_pos == context.pos;
    let identifier = Identifier::from_full_name(&advance(context).value);

    let mut log_base = None;
    if identifier.full_name.starts_with("log") {
        if let Some(lowered) = identifier.get_lowered_part() {
            if let Ok(lowered_float) = lowered.parse::<f64>() {
                log_base = Some(Expr::Literal(lowered_float));
            }
        }
    }

    if context.parsing_unit_decl
        && !context
            .symbol_table
            .get_mut()
            .contains_var(&identifier.full_name)
    {
        context.unit_decl_base_unit = Some(identifier.full_name);
        Ok(Expr::Var(Identifier::from_full_name(DECL_UNIT)))
    } else if log_base.is_some()
        || context
            .symbol_table
            .get_mut()
            .contains_fn(&identifier.pure_name)
    {
        let identifier_pos = context.pos;

        // Function call
        let mut arguments = match parse_primary(context)? {
            Expr::Vector(arguments) => arguments,
            Expr::Group(argument) => vec![*argument],
            argument => vec![argument],
        };

        // If it's a re-definition, revert and parse as a declaration
        if at_start_of_line
            && match_token(context, TokenKind::Equals)
            && arguments.iter().all(|x| matches!(x, Expr::Var(_)))
        {
            for argument in &arguments {
                let mut all_vars_exist = true;
                if let Expr::Var(argument_identifier) = argument {
                    if !context
                        .symbol_table
                        .get_mut()
                        .contains_var(&argument_identifier.full_name)
                    {
                        all_vars_exist = false;
                    }
                }

                if !all_vars_exist {
                    context
                        .symbol_table
                        .get_mut()
                        .get_and_remove_fn(&identifier.full_name);
                    context.pos = identifier_pos;

                    return Ok(Expr::Var(identifier));
                }
            }
        }

        if let Some(log_base) = log_base {
            let log_arg = arguments.remove(0);
            Ok(Expr::FnCall(
                Identifier::from_full_name("log"),
                vec![log_arg, log_base],
            ))
        } else {
            Ok(Expr::FnCall(identifier, arguments))
        }
    } else {
        Ok(Expr::Var(identifier))
    }
}

fn peek(context: &Context) -> &Token {
    if context.pos >= context.tokens.len() {
        context.tokens.last().unwrap() // Eof
    } else {
        &context.tokens[context.pos]
    }
}

fn peek_next(context: &Context) -> &Token {
    &context.tokens[context.pos + 1]
}

fn previous(context: &Context) -> &Token {
    &context.tokens[context.pos - 1]
}

fn match_token(context: &Context, kind: TokenKind) -> bool {
    if is_at_end(context) {
        return false;
    }

    peek(context).kind == kind
}

fn advance(context: &mut Context) -> &Token {
    context.pos += 1;
    previous(context)
}

fn consume(context: &mut Context, kind: TokenKind) -> Result<&Token, KalkError> {
    if match_token(context, kind) {
        return Ok(advance(context));
    }

    Err(KalkError::UnexpectedToken(peek(context).kind, Some(kind)))
}

fn is_at_end(context: &Context) -> bool {
    context.pos >= context.tokens.len() || peek(context).kind == TokenKind::Eof
}

fn skip_newlines(context: &mut Context) {
    while match_token(context, TokenKind::Newline) {
        advance(context);
    }
}

fn string_to_num(value: &str) -> Result<f64, KalkError> {
    let base = get_base(value)?;
    if let Some(result) = crate::radix::parse_float_radix(&value.replace(' ', ""), base) {
        Ok(result)
    } else {
        Err(KalkError::InvalidNumberLiteral(value.into()))
    }
}

fn get_base(value: &str) -> Result<u8, KalkError> {
    let underscore_pos = if let Some(i) = value.find('_') {
        i
    } else {
        return Ok(10);
    };

    let subscript = value.chars().skip(underscore_pos + 1usize);
    if let Some(base) = crate::text_utils::parse_subscript(subscript) {
        Ok(base)
    } else {
        Err(KalkError::UnrecognizedBase)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::Identifier;
    use crate::lexer::{Token, TokenKind::*};
    use crate::test_helpers::*;
    use wasm_bindgen_test::*;

    fn parse_with_context(context: &mut Context, tokens: Vec<Token>) -> Result<Stmt, KalkError> {
        context.tokens = tokens;
        context.pos = 0;

        let parsed = parse_stmt(context)?;
        let symbol_table = context.symbol_table.get_mut();
        analysis::analyse_stmt(symbol_table, parsed)
    }

    fn parse(tokens: Vec<Token>) -> Result<Stmt, KalkError> {
        let mut context = Context::new();
        context.tokens = tokens;
        context.pos = 0;

        let parsed = match parse_stmt(&mut context) {
            Ok(stmt) => stmt,
            Err(KalkError::WasStmt(stmt)) => stmt,
            Err(err) => return Err(err),
        };
        let symbol_table = context.symbol_table.get_mut();
        analysis::analyse_stmt(symbol_table, parsed)
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_var() {
        // x
        let tokens = vec![token(Identifier, "x"), token(Eof, "")];

        assert_eq!(parse(tokens).unwrap(), Stmt::Expr(var("x")));
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_var_multiplication() {
        let mut context = Context::new();
        context.symbol_table.get_mut().insert(Stmt::VarDecl(
            Identifier::from_full_name("x"),
            literal(1f64),
        ));
        context.symbol_table.get_mut().insert(Stmt::VarDecl(
            Identifier::from_full_name("y"),
            literal(2f64),
        ));

        // xy²
        let tokens = vec![
            token(Identifier, "xy"),
            token(Power, ""),
            token(Literal, "2"),
            token(Eof, ""),
        ];

        assert_eq!(
            parse(tokens).unwrap(),
            Stmt::Expr(binary(
                var("x"),
                Star,
                binary(var("y"), Power, literal(2f64))
            ))
        );
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_binary() {
        // 1+2*(3-4/5)
        let tokens = vec![
            token(Literal, "1"),
            token(Plus, ""),
            token(Literal, "2"),
            token(Star, ""),
            token(OpenParenthesis, ""),
            token(Literal, "3"),
            token(Minus, ""),
            token(Literal, "4"),
            token(Slash, ""),
            token(Literal, "5"),
            token(ClosedParenthesis, ""),
            token(Eof, ""),
        ];

        assert_eq!(
            parse(tokens).unwrap(),
            Stmt::Expr(binary(
                literal(1f64),
                Plus,
                binary(
                    literal(2f64),
                    Star,
                    group(binary(
                        literal(3f64),
                        Minus,
                        binary(literal(4f64), Slash, literal(5f64))
                    ))
                )
            ))
        );
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_pow() {
        let tokens = vec![
            token(Literal, "1"),
            token(Star, ""),
            token(Literal, "2"),
            token(Power, ""),
            token(Literal, "3"),
            token(Power, ""),
            token(Literal, "4"),
            token(Plus, ""),
            token(Literal, "5"),
            token(Eof, ""),
        ];

        assert_eq!(
            parse(tokens).unwrap(),
            Stmt::Expr(binary(
                binary(
                    literal(1f64),
                    Star,
                    binary(
                        literal(2f64),
                        Power,
                        binary(literal(3f64), Power, literal(4f64)),
                    ),
                ),
                Plus,
                literal(5f64)
            )),
        );
    }

    #[wasm_bindgen_test]
    fn test_pow_unary() {
        let tokens = vec![
            token(Literal, "10"),
            token(Power, ""),
            token(Minus, ""),
            token(Literal, "1"),
            token(Eof, ""),
        ];

        assert_eq!(
            parse(tokens).unwrap(),
            Stmt::Expr(binary(literal(10f64), Power, unary(Minus, literal(1f64)))),
        );
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_percent() {
        let tokens = vec![
            token(Literal, "1"),
            token(Percent, ""),
            token(Literal, "1"),
            token(Plus, ""),
            token(Literal, "5"),
            token(Percent, ""),
            token(Eof, ""),
        ];

        assert_eq!(
            parse(tokens).unwrap(),
            Stmt::Expr(binary(
                binary(literal(1f64), Percent, literal(1f64)),
                Plus,
                unary(Percent, literal(5f64))
            ))
        );
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_unit() {
        let tokens = vec![token(Literal, "1"), token(Identifier, "a")];

        let mut context = Context::new();
        context
            .symbol_table
            .get_mut()
            .insert(unit_decl("a", "b", var(super::DECL_UNIT)));

        assert_eq!(
            parse_with_context(&mut context, tokens).unwrap(),
            Stmt::Expr(unit("a", literal(1f64)))
        );
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_var_decl() {
        let tokens = vec![
            token(Identifier, "x"),
            token(Equals, ""),
            token(Literal, "1"),
            token(Plus, ""),
            token(Literal, "2"),
            token(Eof, ""),
        ];

        assert_eq!(
            parse(tokens).unwrap(),
            Stmt::VarDecl(
                Identifier::from_full_name("x"),
                binary(literal(1f64), Plus, literal(2f64))
            )
        );
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_fn_decl() {
        let tokens = vec![
            token(Identifier, "f"),
            token(OpenParenthesis, ""),
            token(Identifier, "x"),
            token(ClosedParenthesis, ""),
            token(Equals, ""),
            token(Literal, "1"),
            token(Plus, ""),
            token(Identifier, "x"),
            token(Eof, ""),
        ];

        assert_eq!(
            parse(tokens).unwrap(),
            Stmt::FnDecl(
                Identifier::from_full_name("f"),
                vec![String::from("f-x")],
                binary(literal(1f64), Plus, param_var("f", "x"))
            )
        );
    }

    #[test]
    #[wasm_bindgen_test]
    fn test_fn_call() {
        let tokens = vec![
            token(Identifier, "f"),
            token(OpenParenthesis, ""),
            token(Literal, "1"),
            token(Plus, ""),
            token(Literal, "2"),
            token(ClosedParenthesis, ""),
            token(Plus, ""),
            token(Literal, "3"),
            token(Eof, ""),
        ];

        let mut context = Context::new();

        // Add the function to the symbol table first, in order to prevent errors.
        context.symbol_table.get_mut().set(Stmt::FnDecl(
            Identifier::from_full_name("f"),
            vec![String::from("x")],
            literal(1f64),
        ));

        assert_eq!(
            parse_with_context(&mut context, tokens).unwrap(),
            Stmt::Expr(binary(
                Box::new(Expr::FnCall(
                    Identifier::from_full_name("f"),
                    vec![*binary(literal(1f64), Plus, literal(2f64))]
                )),
                Plus,
                literal(3f64)
            ))
        );
    }
}
