const std = @import("std");

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

const grid_min = -50;
const grid_max = 50;

const Cuboid = struct {
    state: u1,
    xmin: isize,
    xmax: isize,
    ymin: isize,
    ymax: isize,
    zmin: isize,
    zmax: isize,
};

const Cubes = []Cuboid;

pub fn main() !void {
    var buf: [100_000]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buf);
    var arena = std.heap.ArenaAllocator.init(fba.allocator());
    defer arena.deinit();

    var timer = try std.time.Timer.start();
    const ret = try first(arena.allocator());
    const t = timer.lap() / 1000;

    try std.testing.expectEqual(@as(usize, 607573), ret);

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

const Grid = [101][101][101]u1;

pub fn first(allocator: ?std.mem.Allocator) !usize {
    const input = try parseInput(allocator.?);
    defer allocator.?.free(input);

    var grid: Grid = [_][101][101]u1{[_][101]u1{[_]u1{0} ** 101} ** 101} ** 101;

    for (input) |cube| {
        var x = std.math.max(cube.xmin, grid_min);
        while (x <= std.math.min(cube.xmax, grid_max)) : (x += 1) {
            var y = std.math.max(cube.ymin, grid_min);
            while (y <= std.math.min(cube.ymax, grid_max)) : (y += 1) {
                var z = std.math.max(cube.zmin, grid_min);
                while (z <= std.math.min(cube.zmax, grid_max)) : (z += 1) {
                    std.debug.assert(x >= -50);
                    std.debug.assert(x <= 50);
                    std.debug.assert(y >= -50);
                    std.debug.assert(y <= 50);
                    std.debug.assert(z >= -50);
                    std.debug.assert(z <= 50);
                    grid[@intCast(usize, x + 50)][@intCast(usize, y + 50)][@intCast(usize, z + 50)] = cube.state;
                }
            }
        }
    }

    var sum: usize = 0;

    var x: usize = 0;
    while (x < 101) : (x += 1) {
        var y: usize = 0;
        while (y < 101) : (y += 1) {
            var z: usize = 0;
            while (z < 101) : (z += 1) {
                sum += grid[x][y][z];
            }
        }
    }

    return sum;
}

fn parseInput(a: std.mem.Allocator) !Cubes {
    const input = @embedFile(path);
    var lines = std.mem.split(u8, input, "\n");

    var ret = std.ArrayList(Cuboid).init(a);

    while (lines.next()) |line| {
        if (line.len == 0) break;
        var cube: Cuboid = undefined;
        var st_coord = std.mem.tokenize(u8, line, " ");

        // set cuboid state
        if (std.mem.eql(u8, st_coord.next().?, "on")) cube.state = 1 else cube.state = 0;

        var axes = std.mem.tokenize(u8, st_coord.next().?, ",");
        var cntr: u2 = 0;
        while (axes.next()) |ax| : (cntr += 1) {
            const axis = ax[2..]; // strip x=, y=, z=
            std.debug.assert(cntr < 3);
            var min_max = std.mem.tokenize(u8, axis, "..");

            // coordinates
            if (cntr == 0) {
                cube.xmin = try std.fmt.parseInt(isize, min_max.next().?, 10);
                cube.xmax = try std.fmt.parseInt(isize, min_max.next().?, 10);
            }
            if (cntr == 1) {
                cube.ymin = try std.fmt.parseInt(isize, min_max.next().?, 10);
                cube.ymax = try std.fmt.parseInt(isize, min_max.next().?, 10);
            }
            if (cntr == 2) {
                cube.zmin = try std.fmt.parseInt(isize, min_max.next().?, 10);
                cube.zmax = try std.fmt.parseInt(isize, min_max.next().?, 10);
            }
        }

        try ret.append(cube);
    }

    return ret.toOwnedSlice();
}

test "day22a" {
    try std.testing.expectEqual(@as(usize, 607573), try first(std.testing.allocator));
}