use crate::Token;
use crate::function_parser::lexer::Lexer;
use std::fmt;
use thiserror::Error;

#[macro_export]
macro_rules! parse {
    ($input:expr) => {{
        use $crate::{Error, Lexer, parse_br};
        let lexer = Lexer::new($input);
        match lexer {
            Ok(mut lex) => match parse_br(&mut lex, 0) {
                Ok(s) => Ok(s),
                Err(e) => Err(Error::FunctionParse(e)),
            },
            Err(e) => Err(e),
        }
    }};
}

#[derive(Debug, Clone, Error)]
pub enum ParseError {
    #[error("expected a parenthesis after {0} function")]
    MissingParen(String),
}

pub enum S {
    AtomIden(String),
    Cons(String, Vec<S>),
    AtomNum(f64),
}
impl fmt::Display for S {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            S::AtomIden(i) => write!(f, "{}", i),
            S::AtomNum(i) => write!(f, "{}", i),
            S::Cons(head, rest) => {
                write!(f, "({}", head)?;
                for s in rest {
                    write!(f, " {}", s)?
                }
                write!(f, ")")
            }
        }
    }
}

fn infix_binding_power(op: &str) -> Option<(u8, u8)> {
    let res = match op {
        "+" | "-" => (5, 6),
        "*" | "/" => (7, 8),
        "^" => (10, 11),
        _ => return None,
    };
    Some(res)
}

fn prefix_binding_power(op: &str) -> ((), u8) {
    match op {
        "+" | "-" => ((), 9),
        "sin" | "cos" | "exp" | "ln" | "sqrt" | "tan" => ((), 10),
        _ => panic!("bad op: {:?}", op),
    }
}
fn postfix_binding_power(op: &str) -> Option<(u8, ())> {
    let res = match op {
        "(" => (11, ()),
        _ => return None,
    };
    Some(res)
}

pub fn parse_br(lexer: &mut Lexer, min_bp: u8) -> std::result::Result<S, ParseError> {
    let mut lhs = match lexer.get_next_token() {
        Token::Iden(name) => S::AtomIden(name),
        Token::Number(num) => S::AtomNum(num),
        Token::Fun(name) => {
            if lexer.peek() != Token::Lparen {
                return Err(ParseError::MissingParen(name));
            };
            let ((), r_bp) = prefix_binding_power(&name);
            let rhs = parse_br(lexer, r_bp)?;
            S::Cons(name, vec![rhs])
        }
        Token::Op(op) => {
            let ((), r_bp) = prefix_binding_power(&op);
            let rhs = parse_br(lexer, r_bp)?;
            S::Cons(op, vec![rhs])
        }
        Token::Lparen => {
            let lhs = parse_br(lexer, 0)?;
            assert_eq!(lexer.get_next_token(), Token::Rparen);
            lhs
        }
        t => panic!("bad Token: {:?}", t),
    };

    loop {
        let op = match lexer.peek() {
            Token::Op(op) => op,
            Token::Eof => break,
            Token::Lparen => "(".to_string(),
            Token::Rparen => ")".to_string(),
            t => panic!("unknown token in parsing for operand: {:?}", t),
        };

        if let Some((l_bp, ())) = postfix_binding_power(&op) {
            if l_bp < min_bp {
                break;
            }
            lexer.get_next_token();
            lhs = if op == "(" {
                let rhs = parse_br(lexer, 0)?;
                assert_eq!(lexer.get_next_token(), Token::Rparen);
                S::Cons(op, vec![lhs, rhs])
            } else {
                S::Cons(op, vec![lhs])
            };
            continue;
        }

        if let Some((l_bp, r_bp)) = infix_binding_power(&op) {
            if l_bp < min_bp {
                break;
            }
            lexer.get_next_token();
            let rhs = parse_br(lexer, r_bp)?;
            lhs = S::Cons(op, vec![lhs, rhs]);
            continue;
        }
        break;
    }
    Ok(lhs)
}

#[cfg(test)]
mod tests {
    use crate::Result;

    #[test]
    fn tests() -> Result<()> {
        let s = parse!("1")?;
        assert_eq!(s.to_string(), "1");

        let s = parse!("1 + 2 * 3")?;
        assert_eq!(s.to_string(), "(+ 1 (* 2 3))");

        let s = parse!("a + b * c * d + f")?;
        assert_eq!(s.to_string(), "(+ (+ a (* (* b c) d)) f)");

        let s = parse!("--1 * 2")?;
        assert_eq!(s.to_string(), "(* (- (- 1)) 2)");

        let s = parse!("(((0)))")?;
        assert_eq!(s.to_string(), "0");

        let s = parse!("cos(0)")?;
        assert_eq!(s.to_string(), "(cos 0)");

        let s = parse!("sin(1.57)")?;
        assert_eq!(s.to_string(), "(sin 1.57)");

        let s = parse!("cos(x)")?;
        assert_eq!(s.to_string(), "(cos x)");

        let s = parse!("sin(a + b)")?;
        assert_eq!(s.to_string(), "(sin (+ a b))");

        let s = parse!("cos(sin(x))")?;
        assert_eq!(s.to_string(), "(cos (sin x))");

        let s = parse!("sin(cos(2 * theta))")?;
        assert_eq!(s.to_string(), "(sin (cos (* 2 theta)))");

        let s = parse!("sin(x + 1) * 2")?;
        assert_eq!(s.to_string(), "(* (sin (+ x 1)) 2)");

        Ok(())
    }
}
