extern crate nom;

use nom::combinator::map_res;
use nom::{
    branch::alt,
    bytes::complete::tag,
    character::complete::{digit1, multispace0},
    combinator::map,
    error::{context, ErrorKind, VerboseError, VerboseErrorKind},
    sequence::{preceded, tuple},
    Err, IResult,
};

use nom_locate::{position, LocatedSpan};

type Span<'a> = LocatedSpan<&'a str>;

#[derive(Debug, PartialEq)]
pub enum Op {
    Add,
}

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

// this is the definititon of IResult 
// type IResult<I, O, E = u32> = Result<(I, O), Err<I, E>>;

pub fn parse_i32(i: Span) -> IResult<Span, Expr> {
    map(digit1, |digit_str: Span| {
        Expr::Num(digit_str, digit_str.fragment.parse::<i32>().unwrap())
    })(i)
    // below is just an exapmle on howto generate an Error explicitly
    // Err(Err::Error((i, ErrorKind::Alpha)))
}

fn parse_expr(i: Span) -> IResult<Span, Expr> {
    preceded(
        multispace0,
        alt((
            map(
                tuple((parse_i32, preceded(multispace0, tag("+")), parse_expr)),
                |(l, _, r)| Expr::BinOp(i, Box::new(l), Op::Add, Box::new(r)),
            ),
            parse_i32,
        )),
    )(i)
}

// cargo test
#[test]
fn test_parse_i32_1() {
    let (rest, expr) = parse_expr(Span::new("1")).unwrap();

    // check that we are at the end of the input
    assert_eq!(
        rest,
        Span {
            offset: 1,
            line: 1,
            fragment: "",
            extra: (),
        },
    );

    // check that the expression is parsed correctly
    assert_eq!(
        expr,
        Expr::Num(
            Span {
                offset: 0,
                line: 1,
                fragment: "1",
                extra: (),
            },
            1
        )
    );
}


fn main() {
    let (a, b) = parse_expr(Span::new("1")).unwrap();
    println!("{:?}", parse_expr(Span::new("1")));
    println!("{:?}", parse_expr(Span::new("1+2 + 3")));
    println!("{:?}", parse_expr(Span::new("   1+ 1a")));
    println!("{:?}", parse_expr(Span::new("11111111111111111111111111")));
}