use crate::parse::{Parse, ParseError, Parser};
use crate::token::{Ident, Literal, Token};

#[derive(Debug)]
pub struct Module {
    pub items: Vec<Item>,
}

impl Parse for Module {
    fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
        let mut items = Vec::new();
        while let Some(item) = <Option<Item>>::try_parse(parser)? {
            items.push(item);
        }
        Ok(Module { items })
    }
}

#[derive(Debug)]
pub enum Item {
    Fn(FnItem),
}

impl Parse for Item {
    fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
        match parser.peek() {
            Some(Token::Fn) => FnItem::parse(parser).map(Item::Fn),
            Some(other) => Err(ParseError::UnexpectedToken(other.clone())),
            None => Err(ParseError::UnexpectedEof),
        }
    }
}

#[derive(Debug)]
pub struct FnItem {
    pub name: Ident,
    pub params: Params,
    pub return_type: Type,
    pub body: Block,
}

impl Parse for FnItem {
    fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
        parser.next_if_eq(&Token::Fn)?;
        let name = Ident::parse(parser)?;

        parser.next_if_eq(&Token::LeftParen)?;
        let params = Params::parse(parser)?;
        parser.next_if_eq(&Token::RightParen)?;

        let return_type = if parser.next_if_eq(&Token::Colon).is_ok() {
            Type::parse(parser)?
        } else {
            Type::Void
        };

        let body = Block::parse(parser)?;

        Ok(Self {
            name,
            params,
            return_type,
            body,
        })
    }
}

#[derive(Debug)]
pub struct Params {
    pub params: Vec<Param>,
}

impl Parse for Params {
    fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
        let mut params = Vec::new();
        loop {
            if let Some(param) = <Option<Param>>::parse(parser)? {
                params.push(param);
            } else {
                break;
            }
            if parser.next_if_eq(&Token::Comma).is_err() {
                break;
            }
        }
        Ok(Self { params })
    }
}

#[derive(Debug)]
pub struct Param {
    pub name: Ident,
    pub type_: Type,
}

impl Parse for Param {
    fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
        let name = Ident::parse(parser)?;
        parser.next_if_eq(&Token::Colon)?;
        let type_ = Type::parse(parser)?;
        Ok(Self { name, type_ })
    }
}

#[derive(Debug)]
pub enum Type {
    Void,
    I32,
}

impl Parse for Type {
    fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
        let ident = Ident::parse(parser)?;
        match &ident.value[..] {
            b"i32" => Ok(Self::I32),
            _ => Err(ParseError::UnexpectedToken(Token::Ident(ident.clone()))),
        }
    }
}

#[derive(Debug)]
pub struct Block {
    pub statements: Vec<Statement>,
    pub return_expr: Option<Expr>,
}

impl Parse for Block {
    fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
        parser.next_if_eq(&Token::LeftBrace)?;

        let mut statements = Vec::new();
        while let Some(statement) = <Option<Statement>>::parse(parser)? {
            statements.push(statement);
        }

        let return_expr = <Option<Expr>>::parse(parser)?;

        parser.next_if_eq(&Token::RightBrace)?;

        Ok(Self {
            statements,
            return_expr,
        })
    }
}

#[derive(Debug)]
pub enum Statement {}

impl Parse for Statement {
    fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
        Err(ParseError::UnexpectedToken(Token::parse(parser)?))
    }
}

#[derive(Debug)]
pub enum Expr {
    Term(Box<Term>),
    Literal(Literal),
    Ident(Ident),
    BinaryOps(BinaryOps),
}

impl Parse for Expr {
    fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
        let head = Term::parse(parser)?;
        let mut tail = Vec::new();

        while let Some(op) = <Option<BinaryOp>>::parse(parser)? {
            let term = Term::parse(parser)?;
            tail.push((op, term));
        }
        if tail.is_empty() {
            Ok(head.into())
        } else {
            Ok(Expr::BinaryOps(BinaryOps {
                head: Box::new(head),
                tail,
            }))
        }
    }
}

#[derive(Debug)]
pub struct Term {
    pub base: Expr,
    pub postfix: Vec<PostfixOp>,
}

impl From<Term> for Expr {
    fn from(term: Term) -> Expr {
        if term.postfix.is_empty() {
            term.base
        } else {
            Expr::Term(Box::new(term))
        }
    }
}

impl Parse for Term {
    fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
        let base = match parser.peek() {
            Some(Token::Literal(..)) => Literal::parse(parser).map(Expr::Literal)?,
            Some(Token::Ident(..)) => Ident::parse(parser).map(Expr::Ident)?,
            Some(other) => return Err(ParseError::UnexpectedToken(other.clone())),
            None => return Err(ParseError::UnexpectedEof),
        };
        let mut postfix = Vec::new();
        loop {
            match parser.peek() {
                Some(Token::LeftParen) => {
                    parser.next_if_eq(&Token::LeftParen)?;
                    let mut args = Vec::new();
                    loop {
                        if let Ok(arg) = Expr::parse(parser) {
                            args.push(arg);
                        } else {
                            break;
                        }
                        if parser.next_if_eq(&Token::Comma).is_err() {
                            break;
                        }
                    }
                    parser.next_if_eq(&Token::RightParen)?;
                    postfix.push(PostfixOp::Call(args));
                }
                _ => break,
            }
        }
        Ok(Term { base, postfix })
    }
}

#[derive(Debug)]
pub struct BinaryOps {
    pub head: Box<Term>,
    pub tail: Vec<(BinaryOp, Term)>,
}

#[derive(Debug)]
pub enum BinaryOp {
    Add,
    Sub,
    Mul,
    Div,
    Rem,
    And,
    Or,
    Xor,
}

#[derive(Debug)]
pub enum PostfixOp {
    Call(Vec<Expr>),
}

impl Parse for BinaryOp {
    fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
        let token = Token::parse(parser)?;
        match token {
            Token::Plus => Ok(BinaryOp::Add),
            Token::Minus => Ok(BinaryOp::Sub),
            Token::Asterisk => Ok(BinaryOp::Mul),
            Token::Slash => Ok(BinaryOp::Div),
            Token::Percent => Ok(BinaryOp::Rem),
            Token::And => Ok(BinaryOp::And),
            Token::Pipe => Ok(BinaryOp::Or),
            Token::Caret => Ok(BinaryOp::Xor),
            _ => Err(ParseError::UnexpectedToken(token.clone())),
        }
    }
}
