use std::cmp::min;

use aoc_2023::*;
use itertools::iproduct;

type Output1 = i32;
type Output2 = Output1;

struct Pattern {
    map: Vec<Vec<char>>,
}

impl From<&[String]> for Pattern {
    fn from(value: &[String]) -> Self {
        Self {
            map: value.iter().map(|l| l.chars().collect_vec()).collect_vec(),
        }
    }
}

impl Pattern {
    fn check_vertically(&self, allowed_error: usize) -> Option<i32> {
        (0..self.map[0].len() - 1)
            .find(|&x| {
                let d = min(x + 1, self.map[0].len() - x - 1);

                iproduct!(0..self.map.len(), (0..d))
                    .filter(|&(y, dx)| self.map[y][x - dx] != self.map[y][x + dx + 1])
                    .count()
                    == allowed_error
            })
            .map(|x| (x + 1) as i32)
    }

    fn check_horizontally(&self, allowed_error: usize) -> Option<i32> {
        (0..self.map.len() - 1)
            .find(|&y| {
                let d = min(y + 1, self.map.len() - y - 1);

                iproduct!(0..self.map[y].len(), (0..d))
                    .filter(|&(x, dy)| self.map[y - dy][x] != self.map[y + dy + 1][x])
                    .count()
                    == allowed_error
            })
            .map(|y| 100 * (y + 1) as i32)
    }

    fn score(&self, allowed_error: usize) -> i32 {
        if let Some(s) = self.check_vertically(allowed_error) {
            s
        } else if let Some(s) = self.check_horizontally(allowed_error) {
            s
        } else {
            unreachable!()
        }
    }
}

struct Day13 {
    patterns: Vec<Pattern>,
}
impl Solution<Output1, Output2> for Day13 {
    fn new<P: AsRef<Path>>(pathname: P) -> Self {
        let lines: Vec<String> = file_to_lines(pathname);

        let patterns = lines
            .split(|l| l.is_empty())
            .map(|m| m.into())
            .collect_vec();

        Self { patterns }
    }

    fn part_1(&mut self) -> Output1 {
        self.patterns.iter().map(|p| p.score(0)).sum()
    }

    fn part_2(&mut self) -> Output2 {
        self.patterns.iter().map(|p| p.score(1)).sum()
    }
}

fn main() -> Result<()> {
    // Day13::run("sample")
    Day13::main()
}

test_sample!(day_13, Day13, 405, 400);