🏡 index : ~doyle/aoc.git

use std::{collections::HashMap, io::Read};

use anyhow::Result;
use nom::IResult;
use trie_hard::TrieHard;

fn main() -> Result<()> {
    let mut input = String::new();
    std::io::stdin().read_to_string(&mut input)?;

    let (_, input) = parse_input(&input).unwrap();

    let mut possible = 0;
    let mut rearrangements = 0;

    for wanted in &input.wanted {
        let res = get_towels_for_patterns(wanted, &input.towels, &mut HashMap::new());
        possible += res.min(1);
        rearrangements += res;
    }

    eprintln!("{possible}");
    eprintln!("{rearrangements}");

    Ok(())
}

fn get_towels_for_patterns<'b>(
    wanted: &'b str,
    towels: &TrieHard<'_, &'_ str>,
    cache: &mut HashMap<&'b str, u64>,
) -> u64 {
    if wanted.is_empty() {
        return 1;
    }

    let mut possibilities = 0;

    for i in (0..wanted.len()).rev() {
        let (wanted, rest) = wanted.split_at(i + 1);

        if towels.get(wanted).is_some() {
            if let Some(cached) = cache.get(rest) {
                possibilities += cached;
                continue;
            }

            let value = get_towels_for_patterns(rest, towels, cache);
            cache.insert(rest, value);
            possibilities += value;
        }
    }

    possibilities
}

#[derive(Debug)]
pub struct Input<'a> {
    towels: TrieHard<'a, &'a str>,
    wanted: Vec<&'a str>,
}

fn parse_input(input: &str) -> IResult<&str, Input> {
    use nom::{
        bytes::complete::tag, character::complete::alpha1, combinator::map, multi::separated_list1,
        sequence::separated_pair,
    };

    let parse_towels = map(separated_list1(tag(", "), alpha1), TrieHard::from_iter);
    let parse_wanted = separated_list1(tag("\n"), alpha1);

    map(
        separated_pair(parse_towels, tag("\n\n"), parse_wanted),
        |(towels, wanted)| Input { towels, wanted },
    )(input)
}