#[macro_use]
extern crate nom;

use std::fmt;
use std::fmt::{Display, Debug, Formatter};

use std::str;
use std::str::FromStr;

use nom::{IResult, digit, multispace};

pub enum Expr {
    Value(i64),
    Add(Box<Expr>, Box<Expr>),
    Sub(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
    Div(Box<Expr>, Box<Expr>),
    Paren(Box<Expr>),
}

pub enum Oper {
    Add,
    Sub,
    Mul,
    Div,
}

impl Display for Expr {
    fn fmt(&self, format: &mut Formatter) -> fmt::Result {
        use self::Expr::*;
        match *self {
            Value(val) => write!(format, "{}", val),
            Add(ref left, ref right) => write!(format, "{} + {}", left, right),
            Sub(ref left, ref right) => write!(format, "{} - {}", left, right),
            Mul(ref left, ref right) => write!(format, "{} * {}", left, right),
            Div(ref left, ref right) => write!(format, "{} / {}", left, right),
            Paren(ref expr) => write!(format, "({})", expr),
        }
    }
}

impl Debug for Expr {
    fn fmt(&self, format: &mut Formatter) -> fmt::Result {
        use self::Expr::*;
        match *self {
            Value(val) => write!(format, "{}", val),
            Add(ref left, ref right) => write!(format, "({:?} + {:?})", left, right),
            Sub(ref left, ref right) => write!(format, "({:?} - {:?})", left, right),
            Mul(ref left, ref right) => write!(format, "({:?} * {:?})", left, right),
            Div(ref left, ref right) => write!(format, "({:?} / {:?})", left, right),
            Paren(ref expr) => write!(format, "[{:?}]", expr),
        }
    }
}

named!(parens< Expr >, delimited!(
    delimited!(opt!(multispace), tag!("("), opt!(multispace)),
    map!(map!(expr, Box::new), Expr::Paren),
    delimited!(opt!(multispace), tag!(")"), opt!(multispace))
  )
);

named!(factor< Expr >, alt_complete!(
    map!(
      map_res!(
        map_res!(
          delimited!(opt!(multispace), digit, opt!(multispace)),
          str::from_utf8
        ),
      FromStr::from_str
    ),
    Expr::Value)
  | parens
  )
);

fn fold_exprs(initial: Expr, remainder: Vec<(Oper, Expr)>) -> Expr {
    remainder.into_iter().fold(initial, |acc, pair| {
        let (oper, expr) = pair;
        match oper {
            Oper::Add => Expr::Add(Box::new(acc), Box::new(expr)),
            Oper::Sub => Expr::Sub(Box::new(acc), Box::new(expr)),
            Oper::Mul => Expr::Mul(Box::new(acc), Box::new(expr)),
            Oper::Div => Expr::Div(Box::new(acc), Box::new(expr)),
        }
    })
}

named!(term< Expr >, chain!(
    initial: factor ~
    remainder: many0!(
           alt!(
             chain!(tag!("*") ~ mul: factor, || { (Oper::Mul, mul) }) |
             chain!(tag!("/") ~ div: factor, || { (Oper::Div, div) })
           )
         ),
    || fold_exprs(initial, remainder))
);

named!(expr< Expr >, chain!(
    initial: term ~
    remainder: many0!(
           alt!(
             chain!(tag!("+") ~ add: term, || { (Oper::Add, add) }) |
             chain!(tag!("-") ~ sub: term, || { (Oper::Sub, sub) })
           )
         ),
    || fold_exprs(initial, remainder))
);

#[test]
fn factor_test() {
    assert_eq!(factor(&b"  3  "[..]).map(|x| format!("{:?}", x)),
               IResult::Done(&b""[..], String::from("3")));
}

#[test]
fn term_test() {
    assert_eq!(term(&b" 3 *  5   "[..]).map(|x| format!("{:?}", x)),
               IResult::Done(&b""[..], String::from("(3 * 5)")));
}

#[test]
fn 
() {
    assert_eq!(expr(&b" 1 + 2 *  3 "[..]).map(|x| format!("{:?}", x)),
               IResult::Done(&b""[..], String::from("(1 + (2 * 3))")));
    assert_eq!(expr(&b" 1 + 2 *  3 / 4 - 5 "[..]).map(|x| format!("{:?}", x)),
               IResult::Done(&b""[..], String::from("((1 + ((2 * 3) / 4)) - 5)")));
    assert_eq!(expr(&b" 72 / 2 / 3 "[..]).map(|x| format!("{:?}", x)),
               IResult::Done(&b""[..], String::from("((72 / 2) / 3)")));
}

#[test]
fn parens_test() {
    assert_eq!(expr(&b" ( 1 + 2 ) *  3 "[..]).map(|x| format!("{:?}", x)),
               IResult::Done(&b""[..], String::from("([(1 + 2)] * 3)")));
}

expr_test