#[derive(Clone, Copy, Debug)]
struct BitCount {
    ones: u64,
    zeros: u64,
}

impl BitCount {
    fn new() -> Self {
        Self { ones: 0, zeros: 0 }
    }

    fn count_char(&mut self, c: char) {
        match c {
            '1' => self.count_one(),
            '0' => self.count_zero(),
            _ => (),
        }
    }

    fn count_bit(&mut self, b: bool) {
        if b {
            self.count_one()
        } else {
            self.count_zero()
        }
    }

    fn count_one(&mut self) {
        self.ones += 1;
    }

    fn count_zero(&mut self) {
        self.zeros += 1;
    }

    fn gamma(&self) -> bool {
        self.ones >= self.zeros
    }

    fn epsilon(&self) -> bool {
        self.zeros > self.ones
    }
}

impl Default for BitCount {
    fn default() -> Self {
        Self::new()
    }
}

struct StretchZip<'a, Item, Rhs> {
    lhs: &'a mut Vec<Item>,
    ix: usize,
    rhs: Rhs,
}

impl<'a, Item: Default, Rhs: Iterator> StretchZip<'a, Item, Rhs> {
    fn new(lhs: &'a mut Vec<Item>, rhs: Rhs) -> Self {
        Self { lhs, ix: 0, rhs }
    }
}

impl<'a, Item: Default, Rhs: Iterator> Iterator for StretchZip<'a, Item, Rhs> {
    type Item = (&'a mut Item, <Rhs as Iterator>::Item);

    fn next(&mut self) -> Option<(&'a mut Item, <Rhs as Iterator>::Item)> {
        let r = self.rhs.next()?;
        if self.ix >= self.lhs.len() {
            self.lhs.push(<Item as Default>::default());
        }
        let l = unsafe {
            std::mem::transmute::<&mut Item, &'a mut Item>(
                self.lhs.get_mut(self.ix)?,
            )
        };
        self.ix += 1;
        Some((l, r))
    }
}

trait CounterList<C> {
    fn count<I: Iterator<Item = C>>(&mut self, item: I);
}

impl CounterList<char> for Vec<BitCount> {
    fn count<I: Iterator<Item = char>>(&mut self, item: I) {
        for (counter, bit) in StretchZip::new(self, item) {
            counter.count_char(bit);
        }
    }
}

fn life_support(field: bool, readings: &[Vec<bool>]) -> u64 {
    let mut remaining_1 = Vec::new();
    let mut remaining_2 = Vec::new();
    for ix in 0..readings[0].len() {
        let (source, sink) = if ix == 0 {
            (readings, &mut remaining_1)
        } else if ix % 2 == 0 {
            (remaining_2.as_slice(), &mut remaining_1)
        } else {
            (remaining_1.as_slice(), &mut remaining_2)
        };
        let mut counter = BitCount::new();
        for reading in source.iter() {
            counter.count_bit(reading[ix]);
        }
        let target = if field {
            counter.gamma()
        } else {
            counter.epsilon()
        };
        sink.clear();
        sink.extend(
            source
                .iter()
                .filter(|reading| reading[ix] == target)
                .cloned(),
        );
        match sink.len() {
            0 => {
                sink.extend(source.iter().cloned());
            }
            1 => {
                return from_bits(sink[0].iter().copied());
            }
            _ => {}
        }
    }
    from_bits(
        if remaining_1.len() <= remaining_2.len() {
            &remaining_1[0]
        } else {
            &remaining_2[0]
        }
        .iter()
        .copied(),
    )
}

fn from_bits<I: Iterator<Item = bool>>(source: I) -> u64 {
    let mut result = 0;
    for b in source {
        result = result * 2 + (if b { 1 } else { 0 });
    }
    result
}

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 counters = Vec::new();
    let mut record = Vec::new();
    for line in file.lines().filter_map(Result::ok) {
        counters.count(line.chars());
        record.push(
            line.chars()
                .filter_map(|c| match c {
                    '0' => Some(false),
                    '1' => Some(true),
                    _ => None,
                })
                .collect::<Vec<_>>(),
        )
    }
    let gamma = from_bits(counters.iter().map(BitCount::gamma));
    println!("Gamma: {}", gamma);
    let epsilon = from_bits(counters.iter().map(BitCount::epsilon));
    println!("Epsilon: {}", epsilon);
    println!("Product: {}", gamma * epsilon);
    let oxygen = life_support(true, &record);
    println!("Oxygen: {}", oxygen);
    let scrubber = life_support(false, &record);
    println!("CO2 Scrubber: {}", scrubber);
    println!("Product: {}", oxygen * scrubber);
}