const std = @import("std");
const mem = std.mem;

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

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

const Cubes = []Cuboid;

const CoordList = std.ArrayList(isize);

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

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

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

pub fn second() !usize {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();

    var X = CoordList.init(allocator);
    defer X.deinit();
    var Y = CoordList.init(allocator);
    defer Y.deinit();
    var Z = CoordList.init(allocator);
    defer Z.deinit();

    const input = try parseInput(allocator, &X, &Y, &Z);
    defer allocator.free(input);

    std.sort.sort(isize, X.items, {}, comptime std.sort.asc(isize));
    std.sort.sort(isize, Y.items, {}, comptime std.sort.asc(isize));
    std.sort.sort(isize, Z.items, {}, comptime std.sort.asc(isize));

    const N: usize = X.items.len;

    // XXX: this one is _really_ sloooooow..., why?
    // const grid_size = 840;
    // var grid = [_][grid_size][grid_size]bool{[_][grid_size]bool{[_]bool{false} ** grid_size} ** grid_size} ** grid_size;
    var grid = try std.DynamicBitSet.initEmpty(allocator, N * N * N);
    defer grid.deinit();

    for (input) |cube| {
        const x0 = mem.indexOfScalar(isize, X.items, cube.xmin).?;
        const x1 = mem.indexOfScalar(isize, X.items, cube.xmax).?;
        const y0 = mem.indexOfScalar(isize, Y.items, cube.ymin).?;
        const y1 = mem.indexOfScalar(isize, Y.items, cube.ymax).?;
        const z0 = mem.indexOfScalar(isize, Z.items, cube.zmin).?;
        const z1 = mem.indexOfScalar(isize, Z.items, cube.zmax).?;

        var x = x0;
        while (x < x1) : (x += 1) {
            var y = y0;
            while (y < y1) : (y += 1) {
                var z = z0;
                while (z < z1) : (z += 1) {
                    grid.setValue(x * N * N + y * N + z, cube.state);
                }
            }
        }
    }

    var sum: isize = 0;

    var x: usize = 0;
    while (x < N - 1) : (x += 1) {
        var y: usize = 0;
        while (y < N - 1) : (y += 1) {
            var z: usize = 0;
            while (z < N - 1) : (z += 1) {
                if (grid.isSet(x * N * N + y * N + z)) {
                    sum += (X.items[x + 1] - X.items[x]) *
                        (Y.items[y + 1] - Y.items[y]) *
                        (Z.items[z + 1] - Z.items[z]);
                }
            }
        }
    }

    return @intCast(usize, sum);
}

fn parseInput(a: mem.Allocator, X: *CoordList, Y: *CoordList, Z: *CoordList) !Cubes {
    const input = @embedFile(path);
    var lines = 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 = mem.tokenize(u8, line, " ");

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

        var axes = mem.tokenize(u8, st_coord.next().?, ",");
        var cntr: u2 = 0;
        while (axes.next()) |ax| : (cntr += 1) {
            const axis = ax[2..]; // strip x=, y=, z=
            var min_max = 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);
                cube.xmax += 1;
                try X.append(cube.xmin);
                try X.append(cube.xmax);
            }
            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);
                cube.ymax += 1;
                try Y.append(cube.ymin);
                try Y.append(cube.ymax);
            }
            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);
                cube.zmax += 1;
                try Z.append(cube.zmin);
                try Z.append(cube.zmax);
            }
        }

        try ret.append(cube);
    }

    return ret.toOwnedSlice();
}

test "day22b" {
    try std.testing.expectEqual(@as(usize, 1267133912086024), try second());
}