const std = @import("std");

const Part = struct {
    x: u32,
    m: u32,
    a: u32,
    s: u32,

    fn parse(p: []const u8) !Part {
        var split = std.mem.splitScalar(u8, p, ',');

        var result = Part{
            .x = try std.fmt.parseInt(u32, split.next().?[2..], 10),
            .m = try std.fmt.parseInt(u32, split.next().?[2..], 10),
            .a = try std.fmt.parseInt(u32, split.next().?[2..], 10),
            .s = try std.fmt.parseInt(u32, split.next().?[2..], 10),
        };

        return result;
    }

    fn len_inclusive(self: Part) u64 {
        return @as(u64, self.x - self.x_min + 1) * @as(u64, self.m - self.m_min + 1) * @as(u64, self.a - self.a_min + 1) * @as(u64, self.s - self.s_min + 1);
    }
};

const Range = struct {
    start: u32,
    stop: u32,

    fn new(start: u32, stop: u32) Range {
        return .{ .start = start, .stop = stop };
    }

    fn len_inclusive(self: Range) u64 {
        if (self.stop < self.start) return 0 else return self.stop - self.start + 1;
    }
};

const PartRange = struct {
    x: Range,
    m: Range,
    a: Range,
    s: Range,

    fn len(self: PartRange) u64 {
        return self.x.len_inclusive() * self.m.len_inclusive() * self.a.len_inclusive() * self.s.len_inclusive();
    }
};

const QueuePart = struct {
    part: PartRange,
    workflow: []const u8,
};

const empty = "";

const Rule = struct {
    part: u8,
    comparator: u8,
    amount: u32,
    workflow: []const u8,

    fn parse(p: []const u8) !Rule {
        var number_split = std.mem.splitScalar(u8, p, ':');

        var part = number_split.next().?;
        var workflow = number_split.next();

        var rule = Rule{
            .part = part[0],
            .comparator = 0,
            .amount = 0,
            .workflow = empty,
        };

        if (workflow) |w| {
            rule.comparator = part[1];
            var amount = try std.fmt.parseInt(u32, part[2..], 10);

            rule.amount = amount;
            rule.workflow = w;
        } else {
            rule.part = 'd';
            rule.workflow = part;
        }
        return rule;
    }
};

pub fn solve(allocator: std.mem.Allocator, file_name: []const u8) !void {
    var inputs = try std.fs.cwd().openFile(file_name, .{});

    var contents = try inputs.readToEndAlloc(allocator, 100 * 1024);

    var buffer = try allocator.alloc(u8, 100 * 1024);
    _ = buffer;

    var sections = std.mem.splitSequence(u8, contents[0 .. contents.len - 1], "\n\n");

    var debug_it = false;

    var workflows = std.StringHashMap(std.ArrayList(Rule)).init(allocator);
    var workflow_order = std.ArrayList([]const u8).init(allocator);

    var line_buffer: [1024]u8 = undefined;
    _ = line_buffer;

    var workflow_section = std.mem.splitScalar(u8, sections.next().?, '\n');

    while (workflow_section.next()) |workflow| {
        var a = std.mem.splitScalar(u8, workflow, '{');
        var workflow_name = a.next().?;
        var rules_string = a.next().?;
        var rules = std.mem.splitScalar(u8, rules_string[0 .. rules_string.len - 1], ',');

        var workflow_rules = std.ArrayList(Rule).init(allocator);

        while (rules.next()) |rule_part| {
            try workflow_rules.append(try Rule.parse(rule_part));
        }

        try workflows.put(workflow_name, workflow_rules);
        try workflow_order.append(workflow_name);
    }

    var run_section = std.mem.splitScalar(u8, sections.next().?, '\n');

    var part1: u64 = 0;

    while (run_section.next()) |section| {
        var part = try Part.parse(section[1 .. section.len - 1]);

        var is_rejected = false;
        var is_accepted = false;

        var workflow = std.mem.zeroes([4]u8);
        _ = workflow;

        var current = workflows.get("in").?;

        while (!is_rejected and !is_accepted) {
            for (current.items) |item| {
                var a: u32 = 0;
                switch (item.part) {
                    'd' => {
                        if (item.workflow[0] == 'A') {
                            is_accepted = true;
                            break;
                        } else if (item.workflow[0] == 'R') {
                            is_rejected = true;
                            break;
                        } else {
                            current = workflows.get(item.workflow).?;
                        }
                    },
                    'x' => a = part.x,
                    'm' => a = part.m,
                    'a' => a = part.a,
                    's' => a = part.s,
                    else => {
                        std.log.info("Bad part: {c}", .{item.part});
                        std.os.exit(1);
                    },
                }

                if (item.comparator == '>') {
                    if (a > item.amount) {
                        if (item.workflow[0] == 'A') {
                            is_accepted = true;
                            break;
                        } else if (item.workflow[0] == 'R') {
                            is_rejected = true;
                            break;
                        }
                        current = workflows.get(item.workflow).?;
                        break;
                    }
                } else if (item.comparator == '<') {
                    if (a < item.amount) {
                        if (item.workflow[0] == 'A') {
                            is_accepted = true;
                            break;
                        } else if (item.workflow[0] == 'R') {
                            is_rejected = true;
                            break;
                        }
                        current = workflows.get(item.workflow).?;
                        break;
                    }
                }
            }
        }

        if (is_accepted) {
            part1 += part.x + part.m + part.a + part.s;
        }
    }

    std.log.info("Part1: {d}", .{part1});

    var queue = std.ArrayList(QueuePart).init(allocator);

    try queue.append(.{
        .part = .{ .x = Range.new(1, 4000), .m = Range.new(1, 4000), .a = Range.new(1, 4000), .s = Range.new(1, 4000) },
        .workflow = "in",
    });

    var part2: u64 = 0;

    while (queue.popOrNull()) |*ppart| {
        var part = @constCast(ppart);
        if (part.workflow[0] == 'A') {
            part2 += part.part.len();
            continue;
        } else if (part.workflow[0] == 'R') {
            continue;
        }

        var workflow = workflows.get(part.workflow).?;

        for (workflow.items[0 .. workflow.items.len - 1]) |rule| {
            var part_b = part.*;
            part_b.workflow = rule.workflow;

            var compare_amount: Range = undefined;
            var xmas_a: Range = undefined;
            var xmas_b: Range = undefined;

            switch (rule.part) {
                'x' => compare_amount = part.part.x,
                'm' => compare_amount = part.part.m,
                'a' => compare_amount = part.part.a,
                's' => compare_amount = part.part.s,
                else => {
                    std.log.err("Bad part in rule: {c}", .{rule.part});
                    std.os.exit(1);
                },
            }

            if (rule.comparator == '>') {
                if (compare_amount.stop <= rule.amount) continue;

                if (compare_amount.start > rule.amount) {
                    part.*.workflow = rule.workflow;
                    try queue.append(part.*);
                    break;
                }

                xmas_a = .{ .start = compare_amount.start, .stop = rule.amount };
                xmas_b = .{ .start = rule.amount + 1, .stop = compare_amount.stop };
            } else if (rule.comparator == '<') {
                if (compare_amount.start >= rule.amount) continue;

                if (compare_amount.stop < rule.amount) {
                    part.workflow = rule.workflow;
                    try queue.append(part.*);
                    break;
                }

                xmas_a = .{ .start = rule.amount, .stop = compare_amount.stop };
                xmas_b = .{ .start = compare_amount.start, .stop = rule.amount - 1 };
            } else {
                std.log.info("Invalid comparator: {c}", .{rule.comparator});
                std.os.exit(1);
            }

            switch (rule.part) {
                'x' => {
                    part.part.x = xmas_a;
                    part_b.part.x = xmas_b;
                },
                'm' => {
                    part.part.m = xmas_a;
                    part_b.part.m = xmas_b;
                },
                'a' => {
                    part.part.a = xmas_a;
                    part_b.part.a = xmas_b;
                },
                's' => {
                    part.part.s = xmas_a;
                    part_b.part.s = xmas_b;
                },
                else => std.os.exit(1),
            }

            if (debug_it) {
                std.log.info("range({d}, {d})", .{ xmas_a.start, xmas_a.stop });
                std.log.info("range({d}, {d})", .{ xmas_b.start, xmas_b.stop });
            }
            try queue.append(part_b);
        }

        part.workflow = workflow.items[workflow.items.len - 1].workflow;

        try queue.append(part.*);
    }

    std.log.info("Part 2: {d}", .{part2});
}