const std = @import("std");

const ArrayList = std.ArrayList;

const Lens = struct {
    name: []u8,
    value: u32,
};

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

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

    var part1_result: u64 = 0;

    var iter = std.mem.tokenizeScalar(u8, contents[0 .. contents.len - 1], ',');

    var part2_result: u64 = 0;

    var buffer: [128]u8 = undefined;
    _ = buffer;

    var boxes: []ArrayList(Lens) = try allocator.alloc(ArrayList(Lens), 256);

    for (0..256) |box| {
        boxes[box] = ArrayList(Lens).init(allocator);
    }

    while (iter.next()) |token| {
        part1_result += compute_hash(token);

        var i: u32 = 0;
        while (token[i] != '-' and token[i] != '=') {
            i += 1;
        }

        var lens_name = token[0..i];
        var hash = compute_hash(lens_name);
        if (token[i] == '=') {
            i += 1;

            var lens_strength = try std.fmt.parseInt(u32, token[i..], 10);

            var exists = false;

            for (boxes[hash].items) |*box| {
                if (std.mem.eql(u8, box.name, lens_name)) {
                    box.value = lens_strength;
                    exists = true;
                    break;
                }
            }

            if (!exists) {
                try boxes[hash].append(.{ .name = try allocator.dupe(u8, lens_name), .value = lens_strength });
            }
        } else if (token[i] == '-') {
            std.log.debug("Removing: {s} from Box({d})", .{ lens_name, hash });
            var j: i64 = -1;

            for (boxes[hash].items, 0..) |box, item| {
                if (std.mem.eql(u8, box.name, lens_name)) {
                    j = @intCast(item);
                    break;
                }
            }
            if (j >= 0) {
                _ = boxes[hash].orderedRemove(@intCast(j));
            }
        }
    }

    for (boxes, 0..) |box, i| {
        if (box.items.len > 0) {
            std.log.info("Box({d}) = {d}", .{ i, box.items.len });
        }
        for (box.items, 1..) |lens, slot| {
            std.log.info("{s} = {d}, {d}", .{ lens.name, lens.value, (i + 1) * lens.value * slot });
            part2_result += (i + 1) * lens.value * slot;
        }
    }

    std.log.info("Part 1 result: {d}", .{part1_result});
    std.log.info("Part 2 result: {d}", .{part2_result});
}

fn compute_hash(chars: []const u8) u8 {
    var hash: u64 = 0;
    for (0..chars.len) |i| {
        if (chars[i] == ',') {
            std.log.info("D: {d}", .{hash});
            hash = 0;
            continue;
        }

        hash += chars[i];
        hash *= 17;
        hash %= 256;
    }

    return @intCast(hash);
}