const std = @import("std");

const Md5 = std.crypto.hash.Md5;
const Digest = [Md5.digest_length]u8;
const HexDigest = [2 * Md5.digest_length]u8;

pub const ModIndex = struct {
    const Mod = struct {
        libidx: u16,
        realpath: []const u8,
    };

    const ModLib = struct {
        lib: std.DynLib,
        hexdigest: HexDigest,
    };

    alloc: std.mem.Allocator,
    root: std.fs.Dir,
    mods: std.ArrayListUnmanaged(Mod),
    libs: std.ArrayListUnmanaged(ModLib),

    pub fn init(alloc: std.mem.Allocator, root: std.fs.Dir) !@This() {
        return .{
            .alloc = alloc,
            .root = root,
            .mods = .{},
            .libs = .{},
        };
    }

    pub fn load(self: *@This(), mods_dir: std.fs.Dir) !void {
        const buffer = try self.alloc.alloc(u8, std.heap.pageSize());
        defer self.alloc.free(buffer);

        var it = mods_dir.iterate();
        while (try it.next()) |entry| {
            if (entry.kind != .file) continue;

            var random: Digest = undefined;
            std.crypto.random.bytes(&random);
            const tempdigest = std.fmt.bytesToHex(random, .lower);

            try mods_dir.copyFile(
                entry.name,
                self.root,
                &tempdigest,
                .{},
            );

            var md5 = Md5.init(.{});

            {
                const f = try self.root.openFile(
                    &tempdigest,
                    .{},
                );
                defer f.close();

                while (true) {
                    const n = try f.read(buffer);
                    if (n == 0) break;
                    md5.update(buffer[0..n]);
                }
            }

            var digest: Digest = undefined;
            md5.final(&digest);

            const hexdigest: HexDigest = std.fmt.bytesToHex(&digest, .lower);
            try self.root.rename(&tempdigest, &hexdigest);

            var realpath_buf: [std.fs.max_path_bytes]u8 = undefined;
            const realpath = try self.root.realpath(&hexdigest, &realpath_buf);

            var lib = try std.DynLib.open(realpath);
            errdefer lib.close();

            try self.libs.append(self.alloc, .{ .lib = lib, .hexdigest = hexdigest });
        }
    }

    pub fn deinit(self: *@This()) void {
        for (self.mods.items) |*mod| {
            _ = mod;
            // TODO unload
            // TODO shutdown
        }

        for (self.libs.items) |*lib| {
            lib.lib.close();
            self.root.deleteFile(&lib.hexdigest) catch {
                std.log.err(
                    "Failed to delete mod lib: {s}",
                    .{lib.hexdigest},
                );
            };
        }

        self.libs.deinit(self.alloc);
    }
};
