extern crate nom;

use nom::{
    branch::alt,
    bytes::complete::tag,
    character::complete::{digit1, multispace0},
    combinator::map,
    sequence::{preceded, tuple},
    IResult,
};

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Op {
    Add,
    Sub,
    Mul,
    Div,
    Pow,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
    Num(i32),
    BinOp(Box<Expr>, Op, Box<Expr>),
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Token {
    Num(i32),
    Op(Op),
}

pub fn parse_i32(i: &str) -> IResult<&str, Token> {
    map(digit1, |digit_str: &str| {
        Token::Num(digit_str.parse::<i32>().unwrap())
    })(i)
}

fn parse_op(i: &str) -> IResult<&str, Op> {
    alt((
        map(tag("+"), |_| Op::Add),
        map(tag("-"), |_| Op::Sub),
        map(tag("*"), |_| Op::Mul),
        map(tag("/"), |_| Op::Div),
        map(tag("^"), |_| Op::Pow),
    ))(i)
}

fn parse_expr(i: &str) -> IResult<&str, Vec<Token>> {
    preceded(
        multispace0,
        alt((
            map(
                tuple((parse_i32, preceded(multispace0, parse_op), parse_expr)),
                |(l, op, r)| {
                    let mut v = Vec::new();
                    v.push(l);
                    v.push(Token::Op(op));
                    v.extend(r);
                    v
                },
            ),
            map(parse_i32, |i| {
                let mut v = Vec::new();
                v.push(i);
                v
            }),
        )),
    )(i)
}

fn math_expr(e: &Expr) -> String {
    match e {
        Expr::Num(i) => format!("{}", i),
        Expr::BinOp(l, op, r) => {
            format!("({:?}, {}, {})", op, math_expr(l), math_expr(r))
        }
    }
}

fn math_eval(e: &Expr) -> i32 {
    match e {
        Expr::Num(i) => *i,
        Expr::BinOp(l, op, r) => {
            let lv = math_eval(l);
            let rv = math_eval(r);
            match op {
                Op::Add => lv + rv,
                Op::Sub => lv - rv,
                Op::Mul => lv * rv,
                Op::Div => lv / rv,
                Op::Pow => lv.pow(rv as u32),
            }
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
enum Ass {
    Left,
    Right,
}

fn climb_op(op: &Op) -> (u8, Ass) {
    match op {
        Op::Add => (1, Ass::Left),
        Op::Sub => (1, Ass::Left),
        Op::Mul => (2, Ass::Left),
        Op::Div => (2, Ass::Left),
        Op::Pow => (3, Ass::Right),
    }
}

// boilerplate
// compute_expr(min_prec):
//   result = compute_atom()

//   while cur token is a binary operator with precedence >= min_prec:
//     prec, assoc = precedence and associativity of current token
//     if assoc is left:
//       next_min_prec = prec + 1
//     else:
//       next_min_prec = prec
//     rhs = compute_expr(next_min_prec)
//     result = compute operator(result, rhs)

//   return result

fn token_to_expr(t: Token) -> Expr {
    match t {
        Token::Num(i) => Expr::Num(i),
        _ => panic!(),
    }
}

fn climb(mut v: Vec<Token>, min_prec: u8) -> (Expr, Vec<Token>) {
    println!("in climb {:?}, {}", v, min_prec);
    let t = v.pop().unwrap();
    let mut result = token_to_expr(t);
    loop {
        match v.pop() {
            Some(Token::Num(_)) => {
                println!("break num");
                break;
            }
            Some(Token::Op(op)) => {
                println!("result {:?}, op {:?}, v:{:?}", result, op, v);
                let (prec, assoc) = climb_op(&op);
                if prec < min_prec {
                    println!("break prec");
                    break;
                } else {
                    println!("push");
                    let next_min_prec =
                        if assoc == Ass::Left { 1 + prec } else { prec };
                    let (rhs, v_rest) = climb(v.clone(), next_min_prec);
                    v = v_rest;
                    println!("return from call, rhs {:?}, v {:?}", rhs, v);
                    println!("current result {:?}", result);
                    result = Expr::BinOp(Box::new(result), op, Box::new(rhs));
                    println!("new result {:?}", result);
                }
            }

            _ => {
                println!("reaced end");
                break;
            } // reached end
        }
    }
    (result, v)
}

fn test_eq(s: &str, v: i32) {
    let mut p = parse_expr(s).unwrap().1;
    println!("{:?}", p);
    p.reverse();
    let e = climb(p, 0);
    println!("{:?}", e);

    println!("e = {}, v = {}", math_eval(&e.0), v);
}

fn main() {
    test_eq("1 + 2", 1 + 2);
    test_eq("1 + 2 * 3", 1 + 2 * 3);
    test_eq("3 * 4 + 5", 3 * 4 + 5);

    //     // climb_test("2*5+10+10", 2*5+10+10);
    //     // climb_test("2*5+10*11-1", 2*5+10*11-1);
    //     // climb_test("2*5+10*11-2+12", 2*5+10*11-1+12);
    //     // climb_test("1+2*3-4+5", 1 + 2 * 3 - 4 + 5);
    //     climb_test("1", 1);
    //     climb_test("1+2", 1 + 2);
}

// // #[test]
// // fn climb1() {
// //     test_eq("1-2+3", 1 - 2 + 3);
// // }

// // #[test]
// // fn climb2() {
// //     test_eq("1*2+3", 1 * 2 + 3);
// // }

// // #[test]
// // fn climb3() {
// //     test_eq("1*2+3*4-5", 1 * 2 + 3 * 4 - 5);
// // }

// // #[test]
// // fn climb4() {
// //     test_eq("2^5", 2i32.pow(5));
// // }

// // #[test]
// // fn climb5() {
// //     test_eq("2*3+4+5", 2 * 3 + 4 + 5);
// // }

// // #[test]
// // fn climb6() {
// //     test_eq("2*3-4*5-2", 2 * 3 - 4 * 5 - 2);
// // }

// // #[test]
// // fn climb_err() {
// //     test_eq("2 + 2 ^ 5 -3", 2 + 2i32.pow(5 - 3));
// // }

// fn climb_test(s: &str, v: i32) {
//     let p = parse_expr(s).unwrap().1;
//     println!("{:?}", &p);
//     println!("math {}\n", math_expr(&p));
//     let r = climb(p, 0);
//     println!("r {:?}", &r);
//     println!("math r {}", math_expr(&r));
//     println!("eval r {} = {} ", math_eval(&r), v);
// }