const std = @import("std");

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

const retSize = u24;
const lineSize = getLineSize();

pub fn second(allocator: ?std.mem.Allocator) anyerror!retSize {
    const input = @embedFile(path);
    var lines = std.mem.tokenize(u8, input, "\n");

    var lines_array = std.ArrayList(retSize).init(allocator.?);
    defer lines_array.deinit();
    var lines_array2 = std.ArrayList(retSize).init(allocator.?);
    defer lines_array2.deinit();

    while (lines.next()) |line| {
        try lines_array.append(try std.fmt.parseUnsigned(retSize, line, 2));
        try lines_array2.append(try std.fmt.parseUnsigned(retSize, line, 2));
    }

    const ox = try getOxigenRating(allocator.?, &lines_array);
    const co = try getCO2Rating(allocator.?, &lines_array2);

    return ox * co;
}

fn getOxigenRating(allocator: std.mem.Allocator, oxigen: *std.ArrayList(retSize)) anyerror!retSize {
    var to_remove = std.ArrayList(usize).init(allocator);
    defer to_remove.deinit();

    var idx: usize = lineSize;
    while (oxigen.items.len > 1) : (idx -= 1) {
        var ones: retSize = 0;
        var zeros: retSize = 0;

        // count ones and zeros
        for (oxigen.items) |line| {
            if ((line & (@as(retSize, 1) <<| idx - 1)) != 0) {
                ones += 1;
            } else {
                zeros += 1;
            }
        }

        // std.debug.print("{d} {d} {d}", .{ones, zeros, idx-1});

        for (oxigen.items) |line, i| {
            if (ones >= zeros) {
                if ((line & (@as(retSize, 1) <<| idx - 1)) == 0) {
                    try to_remove.append(i);
                }
            } else {
                if ((line & (@as(retSize, 1) <<| idx - 1)) != 0) {
                    try to_remove.append(i);
                }
            }
        }

        std.sort.sort(usize, to_remove.items, {}, comptime std.sort.desc(usize));
        for (to_remove.items) |i| {
            _ = oxigen.swapRemove(i);
        }
        try to_remove.resize(0);

        // std.debug.print("{d}\n", .{oxigen.items.len});
    }

    return oxigen.items[0];
}

fn getCO2Rating(allocator: std.mem.Allocator, co2: *std.ArrayList(retSize)) anyerror!retSize {
    var to_remove = std.ArrayList(usize).init(allocator);
    defer to_remove.deinit();

    var idx: usize = lineSize;
    while (co2.items.len > 1) : (idx -= 1) {
        var ones: retSize = 0;
        var zeros: retSize = 0;

        // count ones and zeros
        for (co2.items) |line| {
            if ((line & (@as(retSize, 1) <<| idx - 1)) != 0) {
                ones += 1;
            } else {
                zeros += 1;
            }
        }

        // std.debug.print("{d} {d} {d}", .{ones, zeros, idx-1});

        for (co2.items) |line, i| {
            if (zeros <= ones) {
                if ((line & (@as(retSize, 1) <<| idx - 1)) != 0) {
                    try to_remove.append(i);
                }
            } else {
                if ((line & (@as(retSize, 1) <<| idx - 1)) == 0) {
                    try to_remove.append(i);
                }
            }
        }

        std.sort.sort(usize, to_remove.items, {}, comptime std.sort.desc(usize));
        for (to_remove.items) |i| {
            _ = co2.swapRemove(i);
        }
        try to_remove.resize(0);

        // std.debug.print("{d}\n", .{co2.items.len});
    }

    return co2.items[0];
}

fn getLineSize() usize {
    const input = @embedFile(path);

    var ret: usize = 0;
    for (input) |bit, idx| {
        if (bit == '\n') {
            ret = idx;
            break;
        }
    }

    return ret;
}

pub fn main() anyerror!void {
    var buffer: [1000 * lineSize * 100]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);

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

    try std.testing.expectEqual(ret, @as(retSize, 4105235));

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

test "day03b" {
    try std.testing.expectEqual(@as(retSize, 4105235), try second(std.testing.allocator));
}