const std = @import("std");

const path = "data/day11/input.txt";

const RetType = u9;

const grid_row = 10;
const grid_col = 10;
const GridType = u4;
const Grid = [grid_row][grid_col]GridType;

const Seen = [grid_row][grid_col]bool;

const directions = [8][2]i2{
    .{ -1, 0 },
    .{ 1, 0 },
    .{ 0, -1 },
    .{ 0, 1 },

    .{ -1, -1 },
    .{ 1, 1 },
    .{ 1, -1 },
    .{ -1, 1 },
};

fn parseInput() anyerror!Grid {
    const input = @embedFile(path);
    var lines = std.mem.tokenize(u8, input, "\n");

    var g: Grid = undefined;

    var row: usize = 0;
    while (lines.next()) |line| : (row += 1) {
        for (line) |_, col| {
            g[row][col] = try std.fmt.parseUnsigned(GridType, line[col .. col + 1], 10);
        }
    }

    return g;
}

pub fn second(allocator: ?std.mem.Allocator) anyerror!RetType {
    _ = allocator;

    var grid = try parseInput();

    var step: RetType = 0;
    while (true) : (step += 1) {
        // increase each octupus energy by 1
        for (grid) |line, row| {
            for (line) |_, col| {
                grid[row][col] +|= 1;
            }
        }

        var seen = [_][grid_col]bool{[_]bool{false} ** grid_col} ** grid_row;
        // flash every octupus > 9
        for (grid) |line, row| {
            for (line) |energy, col| {
                if (energy > 9) {
                    _ = flash(&seen, &grid, row, col);
                }
            }
        }

        // reset flashed
        for (grid) |line, row| {
            for (line) |energy, col| {
                if (energy > 9) {
                    grid[row][col] = 0;
                }
            }
        }

        if (fullFlash(&grid)) {
            break;
        }
    }

    return step + 1;
}

fn fullFlash(grid: *Grid) bool {
    for (grid) |line| {
        for (line) |energy| {
            if (energy != 0) {
                return false;
            }
        }
    }
    return true;
}

fn flash(seen: *Seen, grid: *Grid, row: usize, col: usize) RetType {
    var ret: RetType = 0;
    if ((seen[row][col]) or (grid[row][col] <= 9)) {
        return ret;
    }

    seen[row][col] = true;
    ret += 1;

    for (directions) |dir| {
        const diffrow = @intCast(i8, row) + dir[0];
        if ((diffrow < 0) or (diffrow >= grid_row)) continue;
        const diffcol = @intCast(i8, col) + dir[1];
        if ((diffcol < 0) or (diffcol >= grid_col)) continue;

        grid[@intCast(usize, diffrow)][@intCast(usize, diffcol)] +|= 1;
        ret += flash(seen, grid, @intCast(usize, diffrow), @intCast(usize, diffcol));
    }

    return ret;
}

pub fn main() anyerror!void {
    var timer = try std.time.Timer.start();
    const ret = try second(null);
    const t = timer.lap() / 1000;

    try std.testing.expectEqual(@as(RetType, 273), ret);

    std.debug.print("Day 11b result: {d} \t\ttime: {d}us\n", .{ ret, t });
}

test "day11a" {
    try std.testing.expectEqual(@as(RetType, 273), try second(std.testing.allocator));
}