use problemo::{common::*, *};

/// Error-accumulating functions have special signatures:
///   By our convention we put the receiver as the *last* argument;
///   its type is generic
/// And also special internal implementations:
///   Specifically we have to make sure to give() all errors to the receiver
///   in order to give it an opportunity to swallow them or to fail fast;
///   We provide a few friendly APIs to make this easier
fn read_files<ProblemReceiverT>(
    paths: &[&str],
    problems: &mut ProblemReceiverT,
) -> Result<Vec<String>, Problem>
where
    ProblemReceiverT: ProblemReceiver,
{
    let mut strings = Vec::default();
    for path in paths {
        // give_ok() is like ok() but will give the problem to the receiver;
        // Note that we still use "?" in order to support a fast fail
        if let Some(string) = std::fs::read_to_string(path)
            .via(LowLevelError)
            .give_ok(problems)?
        {
            strings.push(string);
        }
    }

    // If we had swallowed errors then this would be a partial result
    // (i.e. not all files were read)
    Ok(strings)
}

// We'll fake main() here to make sure this example does not actually fail

fn fake_main() -> Result<(), Problem> {
    // With NoProblem we can be sure that we never get an Err, making it safe to unwrap()

    let strings = read_files(&["non-existing1.txt", "non-existing2.txt"], &mut NoProblem).unwrap();

    println!("NoProblem: number of strings: {}", strings.len());

    // Now let's accumulate into Problems

    let mut problems = Problems::default();
    let strings = read_files(&["non-existing3.txt", "non-existing4.txt"], &mut problems)?;

    println!("\nProblems: number of strings: {}", strings.len());

    // We *must* call check() here if we want to fail

    println!("\nCheck:");

    // Note that Problems itself implements Error, so that the "?" here will
    // create a Problem with the Problems as its first and only cause;
    // see main() below on how to handle it
    problems = problems.check()?;

    // (We're never going to get to here because we had at least 1 error)

    // By contrast, we can trust that FailFast will *always* return Err on error

    let strings = read_files(&["non-existing5.txt", "non-existing6.txt"], &mut FailFast)?;

    println!("FailFast: number of strings: {}", strings.len());

    problems.check()?;
    Ok(())
}

fn main() {
    if let Err(problem) = fake_main() {
        // We can use flatten() to "unpack" the problems
        // (if no problems were packed in, it would return a Problems with just our problem)
        for problem in problem.flatten() {
            println!("• {}", problem);
        }
    }
}
