use std::io::Error;
use tailcall::*;

/// Factorial artificial wrapped in a Result
fn factorial(input: u64) -> Result<u64, Error> {
    #[tailcall]
    fn factorial_inner(
        accumulator: Result<u64, Error>,
        input: Result<u64, Error>,
    ) -> Result<u64, Error> {
        let inp = match input {
            Ok(input) => input,
            Err(error) => return Err(error),
        };
        let acc = match accumulator {
            Ok(accumulator) => accumulator,
            Err(error) => return Err(error),
        };
        if inp > 0 {
            tailcall::call! { factorial_inner(Ok(acc * inp), Ok(inp - 1)) }
        } else {
            Ok(acc)
        }
    }

    factorial_inner(Ok(1), Ok(input))
}

#[tailcall]
#[allow(dead_code)]
fn add_iter<'a, I>(int_iter: I, accum: i32) -> Result<i32, ()>
where
    I: Iterator<Item = &'a i32>,
{
    let mut int_iter = int_iter;

    match int_iter.next() {
        Some(i) => tailcall::call! { add_iter(int_iter, accum + i) },
        None => Ok(accum),
    }
}

#[test]
fn factorial_result_runs() {
    assert_eq!(factorial(0).unwrap(), 1);
    assert_eq!(factorial(1).unwrap(), 1);
    assert_eq!(factorial(2).unwrap(), 2);
    assert_eq!(factorial(3).unwrap(), 6);
    assert_eq!(factorial(4).unwrap(), 24);
}
