const std = @import("std");

const stdout = std.io.getStdOut().writer();

fn print(comptime str: []const u8, args: anytype) void {
    stdout.print(str, args) catch unreachable;
}

fn println(comptime str: []const u8, args: anytype) void {
    print(str ++ "\n", args);
}

//const n: u512 = 7405872386298001828045412304885395957447735855540402226273272018863616985100578690399814241980651881616439657049448993379923363875365701026162288146836853;

const n: u512 = 70666344586694209770041979947;

var sqrt: u512 = undefined;

const Pack = struct {
    const Self = @This();

    alloc: std.mem.Allocator,
    n1: u512,
    n2: u512,

    fn init(alloc: std.mem.Allocator, n1: u512, n2: u512) !*Self {
        const self = try alloc.create(Self);
        self.n1 = n1;
        self.n2 = n2;
        self.alloc = alloc;
        return self;
    }

    fn same(self: *Self, other: *Self) bool {
        return (self.n1 == other.n1 and self.n2 == other.n2) or (self.n1 == other.n2 and self.n2 == other.n1);
    }
    fn sameNumbers(self: *Self, n1: u512, n2: u512) bool {
        return (self.n1 == n1 and self.n2 == n2);
    }

    fn print(self: *Self) void {
        println("{} {}", .{ self.n1, self.n2 });
    }

    fn deinit(self: *Self) void {
        self.alloc.destroy(self);
    }
};

const PackList = std.ArrayList(*Pack);

fn addPackNumbers(self: *PackList, n1: u512, n2: u512) !void {
    for (self.items, 0..) |item, j| {
        var i = item;
        if (n1 == i.n1) {
            if (n2 == i.n2) {
                return;
            } else if (n2 > i.n2) {
                var pack = try Pack.init(self.allocator, n1, n2);
                try self.insert(j + 1, pack);
            }
        }
        if (n1 < i.n1) {
            var pack = try Pack.init(self.allocator, n1, n2);
            try self.insert(j, pack);
            return;
        }
    }
    var pack = try Pack.init(self.allocator, n1, n2);
    try self.append(pack);
}

fn base3Mask(val: u512, size: usize) u512 {
    return val % std.math.pow(u512, 10, size);
}

var nBase: u512 = undefined;

pub fn main() !void {
    var alloctor = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer alloctor.deinit();

    var alloc = alloctor.allocator();

    sqrt = std.math.sqrt(n);

    nBase = base3Mask(n, 1);
    println("BaseMask {}", .{nBase});
    var items = try searchAcc(alloc, 1);

    var toRemove = std.ArrayList(usize).init(alloc);
    defer toRemove.deinit();

    var cpuCount = try std.Thread.getCpuCount();
    var threads = try alloc.alloc(std.Thread, cpuCount);
    defer alloc.free(threads);
    var results = try alloc.alloc(PackList, cpuCount);
    defer alloc.free(results);

    for (2..512) |acc| {
        nBase = base3Mask(n, acc);
        toRemove.clearRetainingCapacity();

        println("Stared threaded work on {}", .{acc});
        for (0..cpuCount) |id| {
            threads[id] = try std.Thread.spawn(.{
                .stack_size = 1024 * 1024 * 1024,
                .allocator = alloc,
            }, threadSearch, .{ items, id, cpuCount, acc, results });
        }

        for (0..cpuCount) |id| {
            threads[id].join();
        }
        var total: u64 = 0;
        for (results) |result| {
            total += result.items.len;
        }
        println("Finish threaded work on {} total {}", .{ acc, total });

        for (items.items) |item| {
            item.deinit();
        }

        items.clearRetainingCapacity();

        for (results) |result| {
            //for (result.items) |toAdd| {
            //try addPack(&newToAdd, toAdd);
            //}
            try items.appendSlice(result.items);
            result.deinit();
        }

        // println("results for {}", .{acc});
        // for (items.items) |item| {
        //     item.print();
        // }
        if (acc == 10) {
            println("For items: {}", .{items.items.len});
            for (items.items) |item| {
                item.print();
            }
            break;
        } else {
            println("For finished {}; items: {}", .{ acc, items.items.len });
        }
    }
}

fn threadSearch(items: PackList, id: usize, threadCount: usize, acc: u64, results: []PackList) !void {
    const max = @max(items.items.len / threadCount, 1);
    //var returned = PackList.init(items.allocator);
    var returned = PackList.init(std.heap.page_allocator);
    const len = items.items.len;
    for (0..max) |_i| {
        var i = id + _i * threadCount;
        if (i >= len) {
            break;
        }
        var item = items.items[i];
        if (!verifyAccuracy(item, acc)) {
            //println("{} {} does not hold for acc {}", .{ item.n1, item.n2, acc });
            try searchPackAcc(&returned, acc, item);
        } else {
            try addPackNumbers(&returned, @min(item.n1, item.n2), @max(item.n1, item.n2));
        }
    }
    results[id] = returned;
    return;
}

fn searchPackAcc(packList: *PackList, acc: u64, pack: *Pack) !void {
    var size_n1: u512 = std.math.pow(u512, 10, std.math.log10_int(pack.n1) + 1);
    var size_n2: u512 = std.math.pow(u512, 10, std.math.log10_int(pack.n2) + 1);

    for (0..10) |_i| {
        var i = _i * size_n1 + pack.n1;
        for (0..10) |_j| {
            var j = _j * size_n2 + pack.n2;
            var mul: u512 = i * j;
            if (mul == n) {
                println("Found them {} {}!", .{ j, i });
                std.os.exit(1);
            }
            if (mul > n) break;
            var mulMask = base3Mask(mul, acc);

            if (mulMask == nBase) {
                try addPackNumbers(packList, @min(i, j), @max(i, j));
            }
        }
    }
}

fn searchAcc(alloc: std.mem.Allocator, acc: u64) !PackList {
    var packList = PackList.init(alloc);

    for (0..10) |i| {
        for (0..10) |j| {
            var mul: u512 = i * j;
            if (mul > n) break;
            var mulMask = base3Mask(mul, acc);

            if (mulMask == nBase) {
                try addPackNumbers(&packList, @min(i, j), @max(i, j));
            }
        }
    }

    return packList;
}

fn verifyAccuracy(pack: *Pack, acc: u64) bool {
    var mulMask = base3Mask(pack.n1 * pack.n2, acc);
    return mulMask == nBase;
}
