const std = @import("std");
const os = std.os;
const fs = std.fs;
const Allocator = std.mem.Allocator;

const Corpus = @import("Corpus.zig");
const DB = @import("DB.zig");
const ErrCtx = @import("ErrCtx.zig");
const InvalidHeader = @import("header.zig").Invalid;

const File = @This();

db: DB,
ptr: []align(4096) const u8,

pub const Error = os.OpenError || os.FStatError || os.MMapError || InvalidHeader;
pub fn open(fname: []const u8) Error!File {
    const fd = try os.open(fname, os.O.RDONLY, 0);
    var fd_open = true;
    errdefer if (fd_open) os.close(fd);

    const st = try os.fstat(fd);
    const size = @intCast(usize, st.size);
    const ptr = try os.mmap(null, size, os.PROT.READ, os.MAP.SHARED, fd, 0);
    errdefer os.munmap(ptr);

    os.close(fd);
    fd_open = false;

    const db = try DB.fromBytes(ptr);
    return File{ .db = db, .ptr = ptr };
}

pub fn close(self: *File) void {
    os.munmap(self.ptr);
    self.* = undefined;
}

const testing = std.testing;

pub const TestDB = struct {
    file: File,
    dir: testing.TmpDir,
    path: [:0]const u8,
    allocator: Allocator,

    pub fn init(allocator: Allocator) !TestDB {
        var errc = ErrCtx{};
        var corpus = try Corpus.testCorpus(allocator);
        defer corpus.deinit();

        var db = try DB.fromCorpus(allocator, &corpus, &errc);
        defer db.deinit(allocator);

        var tmp = testing.tmpDir(.{});
        errdefer tmp.cleanup();

        const base_path = blk: {
            const relative_path = try fs.path.join(allocator, &[_][]const u8{
                "zig-cache",
                "tmp",
                tmp.sub_path[0..],
            });
            const real_path = try fs.realpathAlloc(allocator, relative_path);
            allocator.free(relative_path);
            break :blk real_path;
        };

        const full_path = try fs.path.joinZ(allocator, &[_][]const u8{
            base_path,
            "db.turbo",
        });
        allocator.free(base_path);

        const mode = os.O.RDWR | os.O.CREAT | os.O.EXCL;
        const fd = try os.openat(tmp.dir.fd, "db.turbo", mode, 0o666);
        defer os.close(fd);

        _ = try os.writev(fd, db.iov().constSlice());

        return TestDB{
            .file = try open(full_path),
            .dir = tmp,
            .path = full_path,
            .allocator = allocator,
        };
    }

    pub fn deinit(self: *TestDB) void {
        self.dir.cleanup();
        self.allocator.free(self.path);
    }
};
