const std = @import("std");

const PATH = "inputs/day01.txt";

const InputType = i24;

pub fn first(allocator: std.mem.Allocator) !usize {
    // Get input
    const INPUT_SIZE = 1000 * 14; // lines*line_length
    var buf: [INPUT_SIZE]u8 = undefined;
    const input = try std.fs.cwd().readFile(PATH, &buf);

    const lists = try parseInput(allocator, input);
    defer allocator.free(lists.left);
    defer allocator.free(lists.right);

    // Sort both lists
    std.sort.pdq(InputType, lists.left, {}, std.sort.asc(InputType));
    std.sort.pdq(InputType, lists.right, {}, std.sort.asc(InputType));

    // Calculate total distance
    var total_distance: usize = 0;
    for (lists.left, lists.right) |left, right| {
        const distance = @abs(left - right);
        total_distance += distance;
    }

    return total_distance;
}

pub fn second(allocator: std.mem.Allocator) !usize {
    // Get input
    const INPUT_SIZE = 1000 * 14; // lines*line_length
    var buf: [INPUT_SIZE]u8 = undefined;
    const input = try std.fs.cwd().readFile(PATH, &buf);

    const lists = try parseInput(allocator, input);
    defer allocator.free(lists.left);
    defer allocator.free(lists.right);

    // Sort right list for efficient matching
    std.sort.pdq(InputType, lists.right, {}, comptime std.sort.asc(InputType));

    // Calculate similarity score
    var total_score: usize = 0;
    for (lists.left) |left_num| {
        var matches: usize = 0;
        for (lists.right) |right_num| {
            if (right_num > left_num) break; // No more possible matches
            if (right_num == left_num) matches += 1;
        }
        total_score += @as(usize, @intCast(left_num)) * matches;
    }

    return total_score;
}

fn parseInput(allocator: std.mem.Allocator, input: []const u8) !struct {
    left: []InputType,
    right: []InputType,
} {
    var left_list = std.ArrayList(InputType).init(allocator);
    errdefer left_list.deinit();
    var right_list = std.ArrayList(InputType).init(allocator);
    errdefer right_list.deinit();

    var lines = std.mem.tokenize(u8, input, "\n");
    while (lines.next()) |line| {
        var numbers = std.mem.tokenize(u8, line, " \t");
        if (numbers.next()) |left| {
            try left_list.append(try std.fmt.parseInt(InputType, left, 10));
        }
        if (numbers.next()) |right| {
            try right_list.append(try std.fmt.parseInt(InputType, right, 10));
        }
    }

    std.debug.assert(left_list.items.len == right_list.items.len);

    return .{
        .left = try left_list.toOwnedSlice(),
        .right = try right_list.toOwnedSlice(),
    };
}

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

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