const std = @import("std");

const PATH = "input/day06.txt";

const Str = []const u8;

const CoordType = u10;
const Coords = [2]CoordType;

const Actions = enum {
    on,
    off,
    toggle,
};

pub fn first(allocator: std.mem.Allocator) !usize {
    _ = allocator;
    return switchLights(@embedFile(PATH));
}

pub fn second(allocator: std.mem.Allocator) !usize {
    _ = allocator;
    return changeLights(@embedFile(PATH));
}

fn switchLights(commands: Str) !usize {
    const Lights = [1000]std.StaticBitSet(1000);

    var lights: Lights = undefined;
    for (lights) |*row| {
        row.* = (std.StaticBitSet(1000)).initEmpty();
    }

    var lines = std.mem.tokenize(u8, commands, "\n");

    while (lines.next()) |line| {
        var words = std.mem.tokenize(u8, line, " ");

        // parse action
        var action: Actions = undefined;
        // turn or toggle
        if (words.next().?[1] == 'u') {
            // on or off
            if (words.next().?[1] == 'n') action = .on else action = .off;
        } else {
            action = .toggle;
        }

        // parse coordinates
        const top = try parseCoords(words.next().?);
        _ = words.next(); // drop through
        const bottom = try parseCoords(words.next().?);

        const range = std.bit_set.Range{
            .start = top[0],
            .end = bottom[0] + 1,
        };

        // do the action
        switch (action) {
            .on => {
                var row: usize = top[1];
                while (row <= bottom[1]) : (row += 1) {
                    lights[row].setRangeValue(range, true);
                }
            },
            .off => {
                var row: usize = top[1];
                while (row <= bottom[1]) : (row += 1) {
                    lights[row].setRangeValue(range, false);
                }
            },
            .toggle => {
                var ts = std.StaticBitSet(1000).initEmpty();
                ts.setRangeValue(range, true);

                var row: usize = top[1];
                while (row <= bottom[1]) : (row += 1) {
                    lights[row].toggleSet(ts);
                }
            },
        }
    }

    var sum: usize = 0;
    for (lights) |row| {
        sum += row.count();
    }

    return sum;
}

fn changeLights(commands: Str) !usize {
    const LightType = u8;

    var lights = [_][1000]LightType{[_]LightType{0} ** 1000} ** 1000;

    var lines = std.mem.tokenize(u8, commands, "\n");

    while (lines.next()) |line| {
        var words = std.mem.tokenize(u8, line, " ");

        // parse action
        var action: Actions = undefined;
        // turn or toggle
        if (words.next().?[1] == 'u') {
            // on or off
            if (words.next().?[1] == 'n') action = .on else action = .off;
        } else {
            action = .toggle;
        }

        // parse coordinates
        const top = try parseCoords(words.next().?);
        _ = words.next(); // drop through
        const bottom = try parseCoords(words.next().?);

        // do the action
        switch (action) {
            .on => {
                var x: usize = top[0];
                while (x <= bottom[0]) : (x += 1) {
                    var y: usize = top[1];
                    while (y <= bottom[1]) : (y += 1) {
                        lights[x][y] += 1;
                    }
                }
            },
            .toggle => {
                var x: usize = top[0];
                while (x <= bottom[0]) : (x += 1) {
                    var y: usize = top[1];
                    while (y <= bottom[1]) : (y += 1) {
                        lights[x][y] += 2;
                    }
                }
            },
            .off => {
                var x: usize = top[0];
                while (x <= bottom[0]) : (x += 1) {
                    var y: usize = top[1];
                    while (y <= bottom[1]) : (y += 1) {
                        lights[x][y] -|= 1;
                    }
                }
            },
        }
    }

    var sum: usize = 0;
    for (lights) |row| {
        for (row) |item| {
            sum += item;
        }
    }

    return sum;
}

fn parseCoords(a: Str) !Coords {
    var crd: Coords = undefined;

    var parts = std.mem.tokenize(u8, a, ",");
    crd[0] = try std.fmt.parseUnsigned(CoordType, parts.next().?, 10);
    crd[1] = try std.fmt.parseUnsigned(CoordType, parts.next().?, 10);

    return crd;
}

test "switchLights" {
    try std.testing.expectEqual(@as(usize, 1000 * 1000), try switchLights("turn on 0,0 through 999,999"));
    try std.testing.expectEqual(@as(usize, 1000), try switchLights("toggle 0,0 through 999,0"));
}

test "changeLights" {
    try std.testing.expectEqual(@as(usize, 1), try changeLights("turn on 0,0 through 0,0"));
    try std.testing.expectEqual(@as(usize, 2 * 1000 * 1000), try changeLights("toggle 0,0 through 999,999"));
}

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

test "day06b" {
    try std.testing.expectEqual(@as(usize, 17836115), try second(std.testing.allocator));
}