use std::num::ParseIntError;

fn main() {
    use std::io::BufRead;
    let filename = std::env::args().nth(1).expect("Expected filename");
    let file = std::io::BufReader::new(
        std::fs::File::open(<String as AsRef<std::path::Path>>::as_ref(
            &filename,
        ))
        .unwrap(),
    );
    let mut lines: Vec<VentLine> = file
        .lines()
        .flat_map(Result::ok)
        .map(|s| s.parse())
        .flat_map(Result::ok)
        .collect();
    let full_overlapping = count_intersections(&lines);
    lines.retain(VentLine::orthogonal);
    let orth_overlapping = count_intersections(&lines);
    println!("Overlapping points (orthogonal): {}", orth_overlapping);
    println!("Overlapping points (all): {}", full_overlapping);
}

#[derive(Clone, Copy)]
struct VentLine {
    x1: i64,
    y1: i64,
    x2: i64,
    y2: i64,
}

fn count_intersections(lines: &[VentLine]) -> usize {
    use std::collections::HashMap;
    let mut points: HashMap<_, usize> = HashMap::new();
    for point in lines.iter().flat_map(VentLine::points) {
        points.entry(point).and_modify(|c| *c += 1).or_insert(1);
    }
    points.values().filter(|c| *c > &1).count()
}

impl VentLine {
    fn orthogonal(&self) -> bool {
        self.x1 == self.x2 || self.y1 == self.y2
    }

    fn points(&self) -> Box<dyn Iterator<Item = (i64, i64)>> {
        if self.x1 == self.x2 {
            let x = self.x1;
            let (y1, y2) = if self.y1 <= self.y2 {
                (self.y1, self.y2)
            } else {
                (self.y2, self.y1)
            };
            Box::new((y1..=y2).map(move |y| (x, y)))
        } else if self.y1 == self.y2 {
            let y = self.y1;
            let (x1, x2) = if self.x1 <= self.x2 {
                (self.x1, self.x2)
            } else {
                (self.x2, self.x1)
            };
            Box::new((x1..=x2).map(move |x| (x, y)))
        } else if (self.x2 - self.x1).abs() == (self.y2 - self.y1).abs() {
            let xs: Box<dyn Iterator<Item = i64>> = if self.x1 <= self.x2 {
                Box::new(self.x1..=self.x2)
            } else {
                Box::new((self.x2..=self.x1).rev())
            };
            let ys: Box<dyn Iterator<Item = i64>> = if self.y1 <= self.y2 {
                Box::new(self.y1..=self.y2)
            } else {
                Box::new((self.y2..=self.y1).rev())
            };
            Box::new(xs.zip(ys))
        } else {
            Box::new(std::iter::empty())
        }
    }
}

#[derive(Debug)]
enum LineParseError {
    Numeric(u8, ParseIntError),
    General,
}

impl std::str::FromStr for VentLine {
    type Err = LineParseError;
    fn from_str(src: &str) -> Result<Self, Self::Err> {
        let mut halves =
            src.split("->").map(|h| h.trim().split(',').map(str::parse));
        let mut half = halves.next().ok_or(LineParseError::General)?;
        let x1 = half
            .next()
            .ok_or(LineParseError::General)?
            .map_err(|n| LineParseError::Numeric(0, n))?;
        let y1 = half
            .next()
            .ok_or(LineParseError::General)?
            .map_err(|n| LineParseError::Numeric(1, n))?;
        match half.next() {
            None => Ok(()),
            Some(_) => Err(LineParseError::General),
        }?;
        half = halves.next().ok_or(LineParseError::General)?;
        let x2 = half
            .next()
            .ok_or(LineParseError::General)?
            .map_err(|n| LineParseError::Numeric(2, n))?;
        let y2 = half
            .next()
            .ok_or(LineParseError::General)?
            .map_err(|n| LineParseError::Numeric(3, n))?;
        Ok(Self { x1, y1, x2, y2 })
    }
}

impl std::fmt::Debug for VentLine {
    fn fmt(
        &self,
        f: &mut std::fmt::Formatter<'_>,
    ) -> Result<(), std::fmt::Error> {
        write!(f, "{},{} -> {},{}", self.x1, self.y1, self.x2, self.y2)
    }
}

impl std::fmt::Display for VentLine {
    fn fmt(
        &self,
        f: &mut std::fmt::Formatter<'_>,
    ) -> Result<(), std::fmt::Error> {
        <VentLine as std::fmt::Debug>::fmt(self, f)
    }
}