const std = @import("std");

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

const RetType = u7;

const PointType = u11;
const Point = [2]PointType;

const Axis = enum(u1) { x, y };

const FoldInstruction = struct {
    axis: Axis,
    value: PointType,
};

const Dots = std.AutoArrayHashMap(Point, void);
const Folds = std.ArrayList(FoldInstruction);

const Paper = struct {
    dots: Dots,
    folds: Folds,
    pub fn Fold(self: *@This(), a: std.mem.Allocator, axis: Axis, value: PointType) !void {
        var to_remove = std.ArrayList(Point).init(a);
        defer to_remove.deinit();
        switch (axis) {
            .x => {
                for (self.dots.keys()) |item| {
                    if (item[0] > value) {
                        try to_remove.append(item);
                        try self.dots.put(Point{ value - (item[0] - value), item[1] }, {});
                    }
                }
            },
            .y => {
                for (self.dots.keys()) |item| {
                    if (item[1] > value) {
                        try to_remove.append(item);
                        try self.dots.put(Point{ item[0], value - (item[1] - value) }, {});
                    }
                }
            },
        }
        for (to_remove.items) |item| {
            _ = self.dots.swapRemove(item);
        }
    }
};

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

    var ret = Paper{
        .dots = Dots.init(a),
        .folds = Folds.init(a),
    };

    // read in coordinates
    while (lines.next()) |line| {
        if (line.len == 0) break;

        var coord = std.mem.tokenize(u8, line, ",");
        const x = try std.fmt.parseUnsigned(PointType, coord.next().?, 10);
        const y = try std.fmt.parseUnsigned(PointType, coord.next().?, 10);
        // std.debug.print("{s} {d} {d}\n", .{ line, x, y });

        try ret.dots.put(Point{ x, y }, {});
    }

    // parse fold instructions
    while (lines.next()) |foldline| {
        if (foldline.len == 0) break;

        var fold = std.mem.tokenize(u8, foldline, " ");
        _ = fold.next();
        _ = fold.next();

        var f = std.mem.tokenize(u8, fold.next().?, "=");
        const axis: Axis = if (std.mem.eql(u8, f.next().?, "x")) .x else .y;
        const value = try std.fmt.parseUnsigned(PointType, f.next().?, 10);
        // std.debug.print("{s} {d}\n", .{axis, value});

        try ret.folds.append(FoldInstruction{ .axis = axis, .value = value });
    }

    return ret;
}

pub fn second(allocator: ?std.mem.Allocator) anyerror!RetType {
    var p = try parseInput(allocator.?);
    defer {
        p.dots.deinit();
        p.folds.deinit();
    }

    for (p.folds.items) |fold| {
        try p.Fold(allocator.?, fold.axis, fold.value);
    }

    // var row: PointType = 0;
    // while (row < 7) : (row += 1) {
    //     std.debug.print("\n", .{});
    //     var col: PointType = 0;
    //     while (col < 50) : (col += 1) {
    //         if (p.dots.contains(Point{ col, row })) {
    //             std.debug.print("#", .{});
    //         } else {
    //             std.debug.print(" ", .{});
    //         }
    //     }
    // }

    return @intCast(RetType, p.dots.count());
}

pub fn main() anyerror!void {
    var buf: [75_000]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buf);

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

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

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

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