const std = @import("std");

const Str = []const u8;
const PATH = "input/day12.txt";

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

pub fn second(allocator: std.mem.Allocator) !isize {
    const input = @embedFile(PATH);

    var sum = try sumNumbers(input);
    sum -= try ignoreObject(allocator, input);

    return sum;
}

test "day12a" {
    try std.testing.expectEqual(@as(isize, 156366), try first(std.testing.allocator));
}

test "day12b" {
    try std.testing.expectEqual(@as(isize, 96852), try second(std.testing.allocator));
}

fn sumNumbers(in: Str) !isize {
    var sum: isize = 0;

    var start_idx: usize = 0;
    var in_number: bool = false;

    var idx: usize = 0;
    while (idx < in.len) : (idx += 1) {
        switch (in[idx]) {
            '0'...'9' => {
                if (!in_number) {
                    start_idx = idx;
                    in_number = true;
                }
            },
            else => {
                if (in_number) {
                    if (in[start_idx - 1] == '-') {
                        sum += try std.fmt.parseInt(isize, in[start_idx - 1 .. idx], 10);
                    } else {
                        sum += try std.fmt.parseUnsigned(isize, in[start_idx..idx], 10);
                    }
                    in_number = false;
                }
            },
        }
    }

    return sum;
}

test "sumNumbers" {
    try std.testing.expectEqual(@as(isize, 6), try sumNumbers("[1,2,3]"));
    try std.testing.expectEqual(@as(isize, 6), try sumNumbers("{\"a\":2,\"b\":4}"));
    try std.testing.expectEqual(@as(isize, 3), try sumNumbers("{\"a\":{\"b\":4},\"c\":-1}"));
    try std.testing.expectEqual(@as(isize, 0), try sumNumbers("[-1,{\"a\":1}]"));
}

fn ignoreObject(allocator: std.mem.Allocator, in: Str) !isize {
    var ranges = std.ArrayList([2]usize).init(allocator);
    defer ranges.deinit();

    var idx: usize = 0;
    red: while (idx < in.len) : (idx += 1) {
        if (in[idx] == 'r' and in[idx + 1] == 'e' and in[idx + 2] == 'd') {
            var curly_left: usize = 0;
            var bracket: usize = 0;

            var rstart: usize = idx - 1; // red
            while (rstart >= 0) : (rstart -= 1) {
                switch (in[rstart]) {
                    // handle arrays (brackets)
                    ']' => bracket += 1,
                    '[' => {
                        if (bracket == 0) break;
                        bracket -= 1;
                    },

                    // handle objects
                    '{' => {
                        if (curly_left == 0) {
                            var curly_right: usize = 0;

                            var rstop: usize = idx + 3; // red
                            while (rstop < in.len) : (rstop += 1) {
                                switch (in[rstop]) {
                                    '}' => {
                                        if (curly_right == 0) {
                                            // std.debug.print("{s}\n", .{in[rstart .. rstop + 1]});

                                            // remove overlapping ranges
                                            var i: usize = ranges.items.len;
                                            while (i > 0) : (i -= 1) {
                                                if (ranges.items[i - 1][0] > rstart and ranges.items[i - 1][1] < rstop + 1) {
                                                    // std.debug.print("remove: {}-{}\n", .{ ranges.items[i - 1][0], ranges.items[i - 1][1] });
                                                    _ = ranges.swapRemove(i - 1);
                                                }
                                            }

                                            try ranges.append(.{ rstart, rstop + 1 });
                                            idx = rstop + 1;
                                            continue :red;
                                        } else curly_right -= 1;
                                    },
                                    '{' => curly_right += 1,
                                    else => {},
                                }
                            }
                        } else curly_left -= 1;
                    },
                    '}' => curly_left += 1,
                    else => {},
                }
            }
        }
    }

    var sum: isize = 0;
    for (ranges.items) |item| {
        sum += try sumNumbers(in[item[0]..item[1]]);
    }

    return sum;
}