mod parser {
    use pest_derive::Parser;

    #[derive(Parser)]
    #[grammar = "../examples/calc.pest"]
    pub struct Parser;
}

use parser::Rule;
use pest::{
    iterators::Pairs,
    pratt_parser::{Assoc::*, Op, PrattParser},
    Parser,
};
use std::io::{stdin, stdout, Write};

fn parse_to_str(pairs: Pairs<Rule>, pratt: &PrattParser<Rule>) -> String {
    pratt
        .map_primary(|primary| match primary.as_rule() {
            Rule::int => primary.as_str().to_owned(),
            Rule::expr => parse_to_str(primary.into_inner(), pratt),
            _ => unreachable!(),
        })
        .map_prefix(|op, rhs| match op.as_rule() {
            Rule::neg => format!("(-{})", rhs),
            _ => unreachable!(),
        })
        .map_postfix(|lhs, op| match op.as_rule() {
            Rule::fac => format!("({}!)", lhs),
            _ => unreachable!(),
        })
        .map_infix(|lhs, op, rhs| match op.as_rule() {
            Rule::add => format!("({}+{})", lhs, rhs),
            Rule::sub => format!("({}-{})", lhs, rhs),
            Rule::mul => format!("({}*{})", lhs, rhs),
            Rule::div => format!("({}/{})", lhs, rhs),
            Rule::pow => format!("({}^{})", lhs, rhs),
            _ => unreachable!(),
        })
        .parse(pairs)
}

fn parse_to_i32(pairs: Pairs<Rule>, pratt: &PrattParser<Rule>) -> i128 {
    pratt
        .map_primary(|primary| match primary.as_rule() {
            Rule::int => primary.as_str().parse().unwrap(),
            Rule::expr => parse_to_i32(primary.into_inner(), pratt),
            _ => unreachable!(),
        })
        .map_prefix(|op, rhs| match op.as_rule() {
            Rule::neg => -rhs,
            _ => unreachable!(),
        })
        .map_postfix(|lhs, op| match op.as_rule() {
            Rule::fac => (1..lhs + 1).product(),
            _ => unreachable!(),
        })
        .map_infix(|lhs, op, rhs| match op.as_rule() {
            Rule::add => lhs + rhs,
            Rule::sub => lhs - rhs,
            Rule::mul => lhs * rhs,
            Rule::div => lhs / rhs,
            Rule::pow => (1..rhs + 1).map(|_| lhs).product(),
            _ => unreachable!(),
        })
        .parse(pairs)
}

fn main() {
    let pratt = PrattParser::new()
        .op(Op::infix(Rule::add, Left) | Op::infix(Rule::sub, Left))
        .op(Op::infix(Rule::mul, Left) | Op::infix(Rule::div, Left))
        .op(Op::infix(Rule::pow, Right))
        .op(Op::postfix(Rule::fac))
        .op(Op::prefix(Rule::neg));

    let stdin = stdin();
    let mut stdout = stdout();

    loop {
        let source = {
            print!("> ");
            let _ = stdout.flush();
            let mut input = String::new();
            let _ = stdin.read_line(&mut input);
            input.trim().to_string()
        };

        let pairs = match parser::Parser::parse(Rule::program, &source) {
            Ok(mut parse_tree) => {
                parse_tree
                    .next()
                    .unwrap()
                    .into_inner() // inner of program
                    .next()
                    .unwrap()
                    .into_inner() // inner of expr
            }
            Err(err) => {
                println!("Failed parsing input: {:}", err);
                continue;
            }
        };

        print!("{} => ", source);
        print!("{} => ", parse_to_str(pairs.clone(), &pratt));
        println!("{}", parse_to_i32(pairs.clone(), &pratt));
    }
}
