use std::fs;

type PasswordPolicy = (usize, usize, char);

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input = fs::read_to_string("input")?;
    println!("{}", count_valid_passwords(&input, &validate_password_new));
    Ok(())
}

fn parse_password_line(line: &str) -> Result<(PasswordPolicy, &str), Box<dyn std::error::Error>> {
    let mut sections = line.split(' ');
    let range = sections
        .next()
        .ok_or("Password line too short")?
        .split('-')
        .map(|c| c.parse::<usize>())
        .collect::<Result<Vec<usize>, _>>()?;

    let character = sections
        .next()
        .ok_or("Password line too short")?
        .chars()
        .next()
        .ok_or("Password line too short")?;
    let password = sections.next().ok_or("Password line too short")?;

    Ok(((range[0], range[1], character), password))
}

fn validate_password_old(policy: &PasswordPolicy, password: &str) -> bool {
    let (start, end, character) = policy;

    let occurrences = password.chars().filter(|c| c == character).count();
    *start <= occurrences && occurrences <= *end
}

fn validate_password_new(policy: &PasswordPolicy, password: &str) -> bool {
    let (position1, position2, character) = policy;
    let position1_is_character = password
        .chars()
        .nth(position1 - 1)
        .expect("Password too short")
        == *character;
    let position2_is_character = password
        .chars()
        .nth(position2 - 1)
        .expect("Password too short")
        == *character;

    (position1_is_character || position2_is_character)
        && (!position1_is_character || !position2_is_character)
}

fn count_valid_passwords(input: &str, validate: &dyn Fn(&PasswordPolicy, &str) -> bool) -> usize {
    input
        .lines()
        .filter(|line| {
            if let Ok((policy, password)) = parse_password_line(line) {
                validate(&policy, password)
            } else {
                false
            }
        })
        .count()
}

#[cfg(test)]
mod tests {
    use super::*;
    use indoc::indoc;

    #[test]
    fn test_simple() {
        let example = indoc!(
            "1-3 a: abcde
             1-3 b: cdefg
             2-9 c: ccccccccc"
        );

        assert_eq!(count_valid_passwords(&example, &validate_password_old), 2);
    }

    #[test]
    fn test_simple2() {
        let example = indoc!(
            "1-3 a: abcde
             1-3 b: cdefg
             2-9 c: ccccccccc"
        );

        assert_eq!(count_valid_passwords(&example, &validate_password_new), 1);
    }
}