use super::character_recognition::recognize;
use std::str;

const PIXELS_WIDE: u32 = 25;
const PIXELS_TALL: u32 = 6;
const LAYER_SIZE: usize = (PIXELS_WIDE * PIXELS_TALL) as usize;

pub fn part1(input_string: &str) -> Result<usize, String> {
    fn count(slice: &[u8], needle: u8) -> usize {
        slice
            .iter()
            .fold(0, |acc, &b| acc + if b == needle { 1 } else { 0 })
    }

    if input_string.len() % LAYER_SIZE != 0 {
        return Err(format!(
            "Invalid input - expected to be multiple of layer size ({})",
            LAYER_SIZE
        ));
    }

    input_string
        .as_bytes()
        .chunks(LAYER_SIZE)
        .map(|layer| (layer, count(layer, b'0')))
        .min_by_key(|(_, num_zeros)| *num_zeros)
        .map(|(layer, _)| count(layer, b'1') * count(layer, b'2'))
        .ok_or_else(|| "Internal error: No layer".to_string())
}

pub fn part2(input_string: &str) -> Result<String, String> {
    let layer_size = (PIXELS_WIDE * PIXELS_TALL) as usize;
    let mut image = vec![b'2'; layer_size];

    input_string
        .as_bytes()
        .chunks(layer_size)
        .for_each(|layer| {
            image
                .iter_mut()
                .zip(layer.iter())
                .for_each(|(image_pixel, &layer_pixel)| {
                    if *image_pixel == b'2' {
                        *image_pixel = layer_pixel;
                    }
                });
        });

    let mut result = String::new();
    let mut start_of_current_char_x = 0;
    for x in 0..PIXELS_WIDE {
        let mut empty_column = true;
        for y in 0..PIXELS_TALL {
            if image[(x + PIXELS_WIDE * y) as usize] == b'1' {
                empty_column = false;
            }
        }

        // 'Y' is rendered with width 5, so might not be followed by empty column:
        let y_detected = (x - start_of_current_char_x) == 5
            && image[start_of_current_char_x as usize] == b'1'
            && image[(start_of_current_char_x + 1) as usize] != b'1'
            && image[(start_of_current_char_x + 2) as usize] != b'1'
            && image[(start_of_current_char_x + 3) as usize] != b'1'
            && image[(start_of_current_char_x + 4) as usize] == b'1';

        if empty_column || y_detected || x == PIXELS_WIDE - 1 {
            let mut this_char_string = String::new();
            for y in 0..PIXELS_TALL {
                let end_index = x + if x == PIXELS_WIDE - 1 && !empty_column {
                    1
                } else {
                    0
                };
                for char_x in start_of_current_char_x..end_index {
                    this_char_string.push(if image[(char_x + PIXELS_WIDE * y) as usize] == b'1' {
                        '█'
                    } else {
                        ' '
                    });
                }
                if y != PIXELS_TALL - 1 {
                    this_char_string.push('\n');
                }
            }
            start_of_current_char_x = x + if y_detected { 0 } else { 1 };
            result.push(
                recognize(&this_char_string)
                    .ok_or(format!("Unrecognized character: {}", this_char_string))?,
            );
        }
    }
    Ok(result)
}

#[test]
pub fn tests_part1() {
    assert_eq!(part1(include_str!("day08_input.txt")), Ok(2413));
}

#[test]
fn tests_part2() {
    assert_eq!(
        part2(include_str!("day08_input.txt")),
        Ok("BCPZB".to_string())
    );
    assert_eq!(part2("[encoded data, 15000 characters]"),
    Ok("CFCUG".to_string()));
    assert_eq!(part2("[encoded data, 15000 characters]"),
        Ok("JYZHF".to_string()));
}
