enum PairsState<N> {
    Fresh,
    Running(N),
    Finished,
}

struct Pairs<I, N> {
    inner: I,
    state: PairsState<N>,
}

impl<I: Iterator<Item = N>, N> Pairs<I, N> {
    fn new(inner: I) -> Self {
        Self {
            inner,
            state: PairsState::Fresh,
        }
    }
}

impl<I: Iterator<Item = N>, N: Copy> Iterator for Pairs<I, N> {
    type Item = (N, N);

    fn next(&mut self) -> Option<Self::Item> {
        match self.state {
            PairsState::Fresh => match self.inner.next() {
                None => {
                    self.state = PairsState::Finished;
                    None
                }
                Some(x) => {
                    self.state = PairsState::Running(x);
                    self.next()
                }
            },
            PairsState::Running(last) => match self.inner.next() {
                None => {
                    self.state = PairsState::Finished;
                    None
                }
                Some(x) => {
                    self.state = PairsState::Running(x);
                    Some((last, x))
                }
            },
            PairsState::Finished => None,
        }
    }
}

struct SlidingWindow<I, N> {
    inner: I,
    size: usize,
    window: std::collections::VecDeque<N>,
}

impl<I, N> SlidingWindow<I, N> {
    fn new(inner: I, size: usize) -> Self {
        Self {
            inner,
            size,
            window: std::collections::VecDeque::new(),
        }
    }
}

impl<I: Iterator<Item = N>, N: Copy> Iterator for SlidingWindow<I, N> {
    type Item = Vec<N>;

    fn next(&mut self) -> Option<Self::Item> {
        match self.inner.next() {
            None => None,
            Some(x) => {
                self.window.push_back(x);
                if self.window.len() < self.size {
                    self.next()
                } else {
                    let result = self.window.iter().copied().collect();
                    self.window.pop_front();
                    Some(result)
                }
            }
        }
    }
}

fn count_increments<I: Iterator<Item = N>, N: Copy + std::cmp::PartialOrd>(
    source: I,
) -> usize {
    Pairs::new(source)
        .map(|(last, current)| if current > last { 1 } else { 0 })
        .sum()
}

fn main() {
    use std::fs::File;
    use std::io::BufRead;
    use std::io::BufReader;
    use std::path::Path;
    let args: Vec<_> = std::env::args().collect();
    let file = BufReader::new(
        File::open(<String as AsRef<Path>>::as_ref(&args[1])).unwrap(),
    );
    let measurements: Vec<u64> = file
        .lines()
        .flat_map(|line| {
            line.ok().and_then(|line| str::parse(line.as_ref()).ok())
        })
        .collect();
    println!(
        "Depth measurement increases {} times (measurements); {} times (moving window)",
        count_increments(measurements.iter()),
        count_increments(
            SlidingWindow::new(measurements.iter(), 3).map(|w| w.into_iter().sum::<u64>())
        )
    );
}