const std = @import("std");
const mem = std.mem;
const math = std.math;
const native_endian = @import("builtin").target.cpu.arch.endian();
const ptr_size = @import("Group.zig").ptr_size;

const max_shells = @import("shell.zig").max_shells;
const magic = [4]u8{ 0xf0, 0x9f, 0xa4, 0xb7 };
const version = 0;

const Endian = enum(u4) {
    big,
    little,

    fn native() Endian {
        return switch (native_endian) {
            .Little => Endian.little,
            .Big => Endian.big,
        };
    }
};

pub const Host = packed struct {
    endian: Endian = Endian.native(),
    ptr_size: u4 = ptr_size,

    pub fn new() Host {
        return Host{};
    }
};

pub const section_length_bits = 6;
pub const section_length = 1 << section_length_bits;

pub const Invalid = error{
    InvalidMagic,
    InvalidVersion,
    InvalidEndianess,
    InvalidPointerSize,
};

pub const Header = extern struct {
    magic: [8]u8 = magic ++ [1]u8{0} ** 4,
    nblocks_groups: u64,
    nblocks_users: u64,
    nblocks_groupmembers: u64,
    nblocks_additional_gids: u64,
    getgr_bufsize: u64,
    getpw_bufsize: u64,

    num_groups: u32,
    num_users: u32,
    nblocks_bdz_gid: u32,
    nblocks_bdz_groupname: u32,
    nblocks_bdz_uid: u32,
    nblocks_bdz_username: u32,

    nblocks_shell_blob: u8,
    num_shells: u8,
    version: u8 = version,
    host: Host = Host.new(),
    padding: [40]u8 = [1]u8{0} ** 40,

    pub fn fromBytes(blob: *align(8) const [@sizeOf(Header)]u8) Invalid!*const Header {
        const self = mem.bytesAsValue(Header, blob);

        if (!mem.eql(u8, magic[0..4], blob[0..4]))
            return error.InvalidMagic;

        if (self.version != 0)
            return error.InvalidVersion;

        if (self.host.endian != Endian.native())
            return error.InvalidEndianess;

        // when ptr size is larger than on the host that constructed it the DB,
        // getgr_bufsize/getpw_bufsize may return insufficient values, causing
        // OutOfMemory for getgr* and getpw* calls.
        if (self.host.ptr_size < ptr_size)
            return error.InvalidPointerSize;

        return self;
    }
};

const testing = std.testing;

test "header Section length is a power of two" {
    try testing.expect(std.math.isPowerOfTwo(section_length));
}

test "header fits into two sections" {
    try testing.expectEqual(2 * section_length, @sizeOf(Header));
}

test "header bit header size is equal to @sizeOf(Header)" {
    try testing.expectEqual(@sizeOf(Header) * 8, @bitSizeOf(Header));
}

test "header pack and unpack" {
    const header1 = Header{
        .nblocks_shell_blob = 0,
        .num_shells = 0,
        .num_groups = 0,
        .num_users = 0,
        .nblocks_bdz_gid = 0,
        .nblocks_bdz_groupname = 0,
        .nblocks_bdz_uid = 0,
        .nblocks_bdz_username = 0,
        .nblocks_groups = 0,
        .nblocks_users = 0,
        .nblocks_groupmembers = 0,
        .nblocks_additional_gids = 1,
        .getgr_bufsize = 16,
        .getpw_bufsize = 32,
    };

    const bytes = mem.asBytes(&header1);
    const header = try Header.fromBytes(bytes);
    try testing.expectEqual(header1, header.*);
}
