#[macro_use]
extern crate lazy_static;
extern crate regex;

use regex::Regex;
use std::fs;

const FABRIC_SIZE: usize = 1000;

#[derive(Clone)]
struct Rect {
    x: u32,
    y: u32,
    width: u32,
    height: u32,
}

#[derive(Clone)]
struct Claim {
    id: u32,
    rect: Rect,
}

fn main() {
    let input = fs::read_to_string("input/day3.txt").expect("input");

    let claims = input
        .lines()
        .map(parse_claim)
        .collect::<Option<Vec<_>>>()
        .expect("input error");

    let fabric = claim_counts(&claims);

    part1(&fabric);
    part2(&fabric);
}

fn part1<'a>(input: &[Vec<&'a Claim>]) {
    let over_two_claims = input.into_iter().filter(|claims| claims.len() >= 2).count();

    println!("{}", over_two_claims);
}

fn part2<'a>(input: &[Vec<&'a Claim>]) {
    let non_overlapping_claim = input
        .into_iter()
        .filter_map(|claims| {
            if claims.len() == 1 {
                Some(claims[0])
            } else {
                None
            }
        }).find(|candidate| winner(input, candidate))
        .expect("did not find non-overlapping claim");

    println!("{}", non_overlapping_claim.id);
}

fn parse_claim(line: &str) -> Option<Claim> {
    lazy_static! {
        static ref RE: Regex = Regex::new(r#"\A#(\d+) @ (\d+),(\d+): (\d+)x(\d+)\z"#).unwrap();
    }
    let captures = RE.captures(line)?;
    let id = captures[1].parse().ok()?;
    let x = captures[2].parse().ok()?;
    let y = captures[3].parse().ok()?;
    let width = captures[4].parse().ok()?;
    let height = captures[5].parse().ok()?;

    Some(Claim {
        id,
        rect: Rect {
            x,
            y,
            width,
            height,
        },
    })
}

fn claim_counts<'a>(claims: &'a [Claim]) -> Vec<Vec<&'a Claim>> {
    let mut claim_counts = vec![Vec::new(); FABRIC_SIZE * FABRIC_SIZE];

    for claim in claims {
        for y in claim.rect.y..claim.rect.max_y() {
            for x in claim.rect.x..claim.rect.max_x() {
                let existing = claim_counts
                    .get_mut(y as usize * FABRIC_SIZE + x as usize)
                    .unwrap();
                existing.push(claim)
            }
        }
    }

    claim_counts
}

fn winner<'a>(claims: &[Vec<&'a Claim>], candidate: &Claim) -> bool {
    for y in candidate.rect.y..candidate.rect.max_y() {
        for x in candidate.rect.x..candidate.rect.max_x() {
            if claims[y as usize * FABRIC_SIZE + x as usize].len() != 1 {
                return false;
            }
        }
    }

    true
}

impl Rect {
    fn max_x(&self) -> u32 {
        self.x + self.width
    }

    fn max_y(&self) -> u32 {
        self.y + self.height
    }
}