const std = @import("std");

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

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

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

fn parse(comptime str: []const u8) [10]u8 {
    var new = std.mem.zeroes([10]u8);
    for (0..10) |i| {
        new[i] = str[i] - 65;
    }
    return new;
}

var p1 = parse("VRDTOUAPZX");
var p2 = parse("JSXCKEAGDE");

var count: u64 = 0;

const ArrayListU8 = std.ArrayList(u8);

const Node = struct {
    value: u8,
    depth: u8,
    tree: *Tree,
};

const Tree = struct {
    alloc: std.mem.Allocator,
    nodes: std.AutoHashMap(u8, *Node),

    const Self = @This();

    fn init(alloc: std.mem.Allocator) !*Self {
        var new = try alloc.create(Tree);
        new.alloc = alloc;
        new.nodes = std.AutoHashMap(u8, *Node).init(alloc);
        return new;
    }

    fn add_word(self: *Self, word: []const u8) !void {
        var tree_ptr: *Self = self;
        var depth: u8 = 0;
        for (word) |cu| {
            const c = cu - 65;
            if (tree_ptr.*.nodes.get(c)) |node| {
                tree_ptr = node.tree;
            } else {
                var new_node = try self.alloc.create(Node);
                new_node.value = c;
                new_node.depth = depth;
                new_node.tree = try Self.init(self.alloc);
                try tree_ptr.*.nodes.put(c, new_node);
                tree_ptr = new_node.tree;
            }
            depth += 1;
        }
    }
};

pub fn main() !void {
    var allocator = std.heap.GeneralPurposeAllocator(.{}){};
    var alloc = allocator.allocator();

    const args = try std.process.argsAlloc(alloc);

    if (args.len == 3) {
        if (args[1].len != 10 and args[2].len != 10) {
            println("Invalid args", .{});
            return;
        }

        println("Using c1={s} c2={s}", .{ args[1], args[2] });

        for (0..10) |i| {
            p1[i] = args[1][i] - 65;
            p2[i] = args[2][i] - 65;
        }
    }

    var words = @embedFile("./10letterwordslist.txt");

    var root = try Tree.init(alloc);

    var split = std.mem.split(u8, words, "\r\n");

    while (split.next()) |item| {
        if (item.len == 10) {
            try root.add_word(item);
        }
    }

    var sr = try search3(0, root, root);

    if (sr.items.len == 0) {
        println("No results found \n", .{});
        return;
    }

    println("items: {}", .{sr.items.len});

    var r = sr.items[0];

    var r1 = [10]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    var r2 = [10]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

    for (p1, p2, r, 0..) |p_1, p_2, k, i| {
        r1[i] = ((p_1 + (26 - k)) % 26) + 65;
        r2[i] = ((p_2 + (26 - k)) % 26) + 65;
        r[i] = k + 65;
    }

    println("k: {s}", .{r});
    println("r1: {s}", .{r1});
    println("r2: {s}", .{r2});
    println("Checked: {}", .{count});
}

fn search3(depth: u8, tree1: *Tree, tree2: *Tree) !std.ArrayList([]u8) {
    var list = std.ArrayList([]u8).init(tree1.alloc);
    for (0..26) |validChari| {
        var validChar: u8 = @intCast(validChari);
        count += 1;
        var inv = 26 - validChar;
        var v1 = (p1[depth] + inv) % 26;
        var v2 = (p2[depth] + inv) % 26;
        var new_tree1 = tree1.nodes.get(v1);
        var new_tree2 = tree2.nodes.get(v2);
        if (new_tree1 == null or new_tree2 == null) {
            continue;
        }

        var sr = try search2(depth + 1, new_tree1.?.tree, new_tree2.?.tree);
        if (sr == null) {
            continue;
        }
        var r = sr.?;
        r[depth] = validChar;
        try list.append(r);
    }
    return list;
}

fn search2(depth: u8, tree1: *Tree, tree2: *Tree) !?[]u8 {
    if (depth == 10) {
        return try tree1.alloc.alloc(u8, 10);
    }
    for (0..26) |validChari| {
        var validChar: u8 = @intCast(validChari);
        count += 1;
        var inv = 26 - validChar;
        var v1 = (p1[depth] + inv) % 26;
        var v2 = (p2[depth] + inv) % 26;
        var new_tree1 = tree1.nodes.get(v1);
        var new_tree2 = tree2.nodes.get(v2);
        if (new_tree1 == null or new_tree2 == null) {
            continue;
        }

        var sr = try search2(depth + 1, new_tree1.?.tree, new_tree2.?.tree);
        if (sr == null) {
            continue;
        }
        var r = sr.?;
        r[depth] = validChar;
        return r;
    }
    return null;
}
