use std::fs;

type SeatCode = [char; 10];

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input = fs::read_to_string("input")?;

    // Part 1
    let max_id: Result<usize, String> = input
        .lines()
        .map(|code| {
            let (row, col) = find_seat(parse_seatcode(code)?);
            Ok(row * 8 + col)
        })
        .max()
        .ok_or("No seat codes set")?;

    println!("{}", max_id?);

    // Part 2
    let mut ids = input
        .lines()
        .map(|code| {
            let (row, col) = find_seat(parse_seatcode(code)?);
            Ok(row * 8 + col)
        })
        .collect::<Result<Vec<usize>, String>>()?;
    ids.sort();

    let mut previous = ids[0];
    for id in &ids[1..] {
        if id - 1 != previous {
            println!("{}", id);
            break;
        } else {
            previous = *id;
        }
    }

    Ok(())
}

fn parse_seatcode(input: &str) -> Result<SeatCode, String> {
    let mut output = ['0'; 10];

    for (i, c) in input.chars().enumerate() {
        if i > 10 {
            Err(format!("Seat code too long: '{}'", input))?;
        }

        if !['F', 'B', 'L', 'R'].iter().any(|valid| &c != valid) {
            Err(format!("Invalid character in seat code: '{}'", input))?;
        }

        output[i] = c;
    }

    Ok(output)
}

fn find_seat(code: SeatCode) -> (usize, usize) {
    let midpoint = |(start, end): (usize, usize)| (start + end) / 2;
    let binary_follow = |range: (usize, usize), direction: &char| match direction {
        'F' | 'L' => (range.0, midpoint(range)),
        'B' | 'R' => (midpoint(range) + 1, range.1),
        _ => unreachable!("We already checked all characters are valid"),
    };

    let row = code[..7].iter().fold((0, 127), binary_follow).0;
    let col = code[7..].iter().fold((0, 7), binary_follow).0;

    (row, col)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_simple() -> Result<(), Box<dyn std::error::Error>> {
        let input = "FBFBBFFRLR";
        let seat = find_seat(parse_seatcode(input)?);

        assert_eq!(seat, (44, 5));
        Ok(())
    }

    #[test]
    fn test_simple2() -> Result<(), Box<dyn std::error::Error>> {
        let input = "BFFFBBFRRR";
        let seat = find_seat(parse_seatcode(input)?);

        assert_eq!(seat, (70, 7));
        Ok(())
    }

    #[test]
    fn test_simple3() -> Result<(), Box<dyn std::error::Error>> {
        let input = "FFFBBBFRRR";
        let seat = find_seat(parse_seatcode(input)?);

        assert_eq!(seat, (14, 7));
        Ok(())
    }

    #[test]
    fn test_simple4() -> Result<(), Box<dyn std::error::Error>> {
        let input = "BBFFBBFRLL";
        let seat = find_seat(parse_seatcode(input)?);

        assert_eq!(seat, (102, 4));
        Ok(())
    }
}