use crate::{
    ast::{err::ParseError, types::{BinaryOp, Expr, ExprValue, IncDecOp, UnaryOp}},
    diagnostic::span::Span,
    lexer::types::{Token, TokenValue},
};

use super::{ParseProcessError, Parser};

impl Parser {
    fn parse_primary(&mut self) -> Result<Expr, ParseProcessError> {
        let token = self.next().clone();
        match token.value {
            TokenValue::Ident(name) => {
                if matches!(self.peek().value, TokenValue::LParen) {
                    self.advance(1);
                    let mut args = vec![];
                    loop {
                        match &self.peek().value {
                            TokenValue::RParen => {
                                let end_span = self.peek().span;
                                self.advance(1);
                                return Ok(Expr {
                                    value: ExprValue::FuncCall(name, args),
                                    span: Span::from_two(token.span, end_span),
                                });
                            }
                            TokenValue::Eof => {
                                self.diagnostics.add_from_frontend_error(
                                    ParseError::ExpectedBefore(TokenValue::Eof, "`)`"),
                                    token.span,
                                );
                                return Err(ParseProcessError::ErrorInMatch);
                            }
                            _ => {}
                        }
                        args.push(self.parse_expr()?);
                        match &self.peek().value {
                            TokenValue::Comma => {
                                self.advance(1);
                            }
                            TokenValue::RParen => {
                                let end_span = self.peek().span;
                                self.advance(1);
                                return Ok(Expr {
                                    value: ExprValue::FuncCall(name, args),
                                    span: Span::from_two(token.span, end_span),
                                });
                            }
                            _ => {
                                let token = self.next();
                                self.diagnostics.add_from_frontend_error(
                                    ParseError::ExpectedBefore(token.value, "`,` or `)`"),
                                    token.span,
                                );
                                return Err(ParseProcessError::ErrorInMatch);
                            }
                        }
                    }
                }
                Ok(Expr {
                    value: ExprValue::Var(name),
                    span: token.span,
                })
            },
            TokenValue::IntLit(value) => Ok(Expr {
                value: ExprValue::IntLit(value),
                span: token.span,
            }),
            TokenValue::LParen => {
                let expr = self.parse_expr()?;
                match &self.peek().value {
                    TokenValue::RParen => {
                        let end_span = self.peek().span;
                        self.advance(1);
                        Ok(Expr {
                            span: Span::from_two(token.span, end_span),
                            ..expr
                        })
                    }
                    _ => {
                        let token = self.next();
                        self.diagnostics.add_from_frontend_error(
                            ParseError::ExpectedBefore(token.value, "`)`"),
                            token.span,
                        );
                        Err(ParseProcessError::ErrorInMatch)
                    }
                }
            }
            _ => {
                self.diagnostics.add_from_frontend_error(
                    ParseError::ExpectedBefore(token.value, "expression"),
                    token.span,
                );
                Err(ParseProcessError::ErrorInMatch)
            }
        }
    }
    fn parse_postfix(&mut self) -> Result<Expr, ParseProcessError> {
        let mut expr = self.parse_primary()?;
        loop {
            if self.peek().value == TokenValue::LBracket {
                let start_span = expr.span;
                self.advance(1);
                let index = self.parse_expr()?;
                let end_span = match &self.peek().value {
                    TokenValue::RBracket => {
                        let span = self.peek().span;
                        self.advance(1);
                        span
                    }
                    _ => {
                        let token = self.next();
                        self.diagnostics.add_from_frontend_error(
                            ParseError::ExpectedBefore(token.value, "`]`"),
                            token.span,
                        );
                        return Err(ParseProcessError::ErrorInMatch);
                    }
                };
                expr = Expr {
                    value: ExprValue::ArrayAccess {
                        array: Box::new(expr),
                        index: Box::new(index),
                    },
                    span: Span::from_two(start_span, end_span),
                };
                continue;
            }
            if let TokenValue::PlusPlus | TokenValue::MinusMinus = self.peek().value {
                let op = if self.peek().value == TokenValue::PlusPlus {
                    IncDecOp::Inc
                } else {                   
                    IncDecOp::Dec
                };
                let end_span = self.peek().span;
                self.advance(1);
                expr = Expr {
                    span: Span::from_two(expr.span, end_span),
                    value: ExprValue::IncDec {
                        op,
                        operand: Box::new(expr),
                        is_prefix: false,
                    },
                };
                continue;
            }
            break;
        }
        Ok(expr)
    }
    fn parse_unary(&mut self) -> Result<Expr, ParseProcessError> {
        let token = self.peek().clone();
        match token.value {
            TokenValue::Plus => {
                self.advance(1);
                let expr = self.parse_unary()?;
                let span = Span::from_two(token.span, expr.span);
                Ok(Expr {
                    value: ExprValue::UnaryOp {
                        op: UnaryOp::Add,
                        operand: Box::new(expr),
                    },
                    span,
                })
            }
            TokenValue::Minus => {
                self.advance(1);
                let rhs = self.parse_unary()?;
                let span = Span::from_two(token.span, rhs.span);
                Ok(Expr {
                    value: ExprValue::UnaryOp {
                        op: UnaryOp::Sub,
                        operand: Box::new(rhs),
                    },
                    span,
                })
            }
            TokenValue::Not => {
                self.advance(1);
                let rhs = self.parse_unary()?;
                let span = Span::from_two(token.span, rhs.span);
                Ok(Expr {
                    value: ExprValue::UnaryOp {
                        op: UnaryOp::Not,
                        operand: Box::new(rhs),
                    },
                    span,
                })
            }
            TokenValue::PlusPlus | TokenValue::MinusMinus => {
                let op = if token.value == TokenValue::PlusPlus {
                    IncDecOp::Inc
                } else {
                    IncDecOp::Dec
                };
                self.advance(1);
                let operand = self.parse_unary()?;
                let span = Span::from_two(token.span, operand.span);
                Ok(Expr {
                    value: ExprValue::IncDec {
                        op,
                        operand: Box::new(operand),
                        is_prefix: true,
                    },
                    span,
                })
            }
            _ => self.parse_postfix(),
        }
    }
    fn parse_multiplicative(&mut self) -> Result<Expr, ParseProcessError> {
        let mut left = self.parse_unary()?;
        loop {
            let op = match self.peek().value {
                TokenValue::Star => BinaryOp::Mul,
                TokenValue::Slash => BinaryOp::Div,
                TokenValue::Percent => BinaryOp::Mod,
                _ => break,
            };
            self.advance(1);
            let right = self.parse_unary()?;
            let span = Span::from_two(left.span, right.span);
            left = Expr {
                value: ExprValue::BinaryOp {
                    lhs: Box::new(left),
                    op,
                    rhs: Box::new(right),
                },
                span,
            };
        }
        Ok(left)
    }
    fn parse_additive(&mut self) -> Result<Expr, ParseProcessError> {
        let mut left = self.parse_multiplicative()?;
        loop {
            let op = match self.peek().value {
                TokenValue::Plus => BinaryOp::Add,
                TokenValue::Minus => BinaryOp::Sub,
                _ => break,
            };
            self.advance(1);
            let right = self.parse_multiplicative()?;
            let span = Span::from_two(left.span, right.span);
            left = Expr {
                value: ExprValue::BinaryOp {
                    lhs: Box::new(left),
                    op,
                    rhs: Box::new(right),
                },
                span,
            };
        }
        Ok(left)
    }
    fn parse_relational(&mut self) -> Result<Expr, ParseProcessError> {
        let mut left = self.parse_additive()?;
        loop {
            let op = match self.peek().value {
                TokenValue::Less => BinaryOp::Less,
                TokenValue::Greater => BinaryOp::Greater,
                TokenValue::LessEqual => BinaryOp::LessEqual,
                TokenValue::GreaterEqual => BinaryOp::GreaterEqual,
                _ => break,
            };
            self.advance(1);
            let right = self.parse_additive()?;
            let span = Span::from_two(left.span, right.span);
            left = Expr {
                value: ExprValue::BinaryOp {
                    lhs: Box::new(left),
                    op,
                    rhs: Box::new(right),
                },
                span,
            };
        }
        Ok(left)
    }
    fn parse_equality(&mut self) -> Result<Expr, ParseProcessError> {
        let mut left = self.parse_relational()?;
        loop {
            let op = match self.peek().value {
                TokenValue::DoubleEqual => BinaryOp::Equal,
                TokenValue::NotEqual => BinaryOp::NotEqual,
                _ => break,
            };
            self.advance(1);
            let right = self.parse_relational()?;
            let span = Span::from_two(left.span, right.span);
            left = Expr {
                value: ExprValue::BinaryOp {
                    lhs: Box::new(left),
                    op,
                    rhs: Box::new(right),
                },
                span,
            };
        }
        Ok(left)
    }
    fn parse_logical_and(&mut self) -> Result<Expr, ParseProcessError> {
        let mut left = self.parse_equality()?;
        loop {
            let op = match self.peek().value {
                TokenValue::And => BinaryOp::And,
                _ => break,
            };
            self.advance(1);
            let right = self.parse_equality()?;
            let span = Span::from_two(left.span, right.span);
            left = Expr {
                value: ExprValue::BinaryOp {
                    lhs: Box::new(left),
                    op,
                    rhs: Box::new(right),
                },
                span,
            };
        }
        Ok(left)
    }
    fn parse_logical_or(&mut self) -> Result<Expr, ParseProcessError> {
        let mut left = self.parse_logical_and()?;
        loop {
            let op = match self.peek().value {
                TokenValue::Or => BinaryOp::Or,
                _ => break,
            };
            self.advance(1);
            let right = self.parse_logical_and()?;
            let span = Span::from_two(left.span, right.span);
            left = Expr {
                value: ExprValue::BinaryOp {
                    lhs: Box::new(left),
                    op,
                    rhs: Box::new(right),
                },
                span,
            };
        }
        Ok(left)
    }
    fn parse_assign(&mut self) -> Result<Expr, ParseProcessError> {
        let lvalue = self.parse_logical_or()?;
        let compound_op = match self.peek().value {
            TokenValue::Equal => None,
            TokenValue::PlusEqual => Some(BinaryOp::Add),
            TokenValue::MinusEqual => Some(BinaryOp::Sub),
            TokenValue::StarEqual => Some(BinaryOp::Mul),
            TokenValue::SlashEqual => Some(BinaryOp::Div),
            TokenValue::PercentEqual => Some(BinaryOp::Mod),
            _ => return Ok(lvalue),
        };
        self.advance(1);
        let rvalue = self.parse_assign()?;
        let span = Span::from_two(lvalue.span, rvalue.span);
        if let Some(op) = compound_op {
            return Ok(Expr {
                value: ExprValue::CompoundAssign {
                    lvalue: Box::new(lvalue),
                    op,
                    rvalue: Box::new(rvalue),
                },
                span,
            });
        }
        Ok(Expr {
            value: ExprValue::Assign {
                lvalue: Box::new(lvalue),
                rvalue: Box::new(rvalue),
            },
            span,
        })
    }
    pub(super) fn parse_expr(&mut self) -> Result<Expr, ParseProcessError> {
        self.parse_assign()
    }
}
