const std = #import("std");
const linux = std.os.linux;
const macos = std.os.macos;
const builtin = #import("builtin");
const os = builtin.os;

const Error = switch (os) {
    .linux => linux.Error,
    .macos => macos.Error,
    else => #error("OS not supported"),
};

const unwrap_syscall = fn(syscall_result: ssize) Error!usize {
    if (syscall_result == -1) {
        const absolute_error: u64 = #cast(-syscall_result);
        const error_int: u32 = #cast(absolute_error);
        const err: Error = #cast(error_int);
        return err;
    } else {
        const result: usize = #cast(syscall_result);
        return result;
    }
}

const MapFlags = switch (os) {
    .macos => bitfield(u32){
        shared: bool = false,
        private: bool = false,
        reserved: u2 = 0,
        fixed: bool = false,
        reserved0: bool = 0,
        noreserve: bool = false,
        reserved1: u2 = 0,
        has_semaphore: bool = false,
        no_cache: bool = false,
        reserved2: u1 = 0,
        anonymous: bool = false,
        reserved3: u19 = 0,
    },
    .linux => linux.MapFlags,
    else => #error("OS not supported"),
};

const AccessMode = enum(u2) {
    read_only = 0,
    write_only = 1,
    read_write = 2,
};

const OpenFlags = switch (os) {
    .macos => bitfield(u32) {
        access_mode: AccessMode,
        non_block: bool = false,
        append: bool = false,
        shared_lock: bool = false,
        exclusive_lock: bool = false,
        async: bool = false,
        sync: bool = false,
        no_follow: bool = false,
        creat: bool = false,
        truncate: bool = false,
        exclusive: bool = false,
        _: u3 = 0,
        evt_only: bool = false,
        _: u1 = 0,
        no_ctty: bool = false,
        _: u2 = 0,
        directory: bool = false,
        symlink: bool = false,
        dsync: bool = false,
        _: u1 = 0,
        cloexec: bool = false,
        _: u4 = 0,
        alert: bool = false,
        _: u1 = 0,
        popup: bool = false,
    },
    .linux => linux.OpenFlags,
    else => #error("OS not supported"),
};

const TimeSpec = struct{
    seconds: s64,
    nanoseconds: s64,
};

const Stat = switch (os) {
    .macos => struct{
        dev: s32,
        mode: u16,
        nlink: u16,

        inode: u64,
        uid: u32,
        gid: u32,
        rdev: s32,

        a_timespec: TimeSpec,
        m_timespec: TimeSpec,
        c_timespec: TimeSpec,
        birth_timespec: TimeSpec,
        size: s64,
        blocks: s64,
        block_size: s32,
        flags: u32,
        gen: u32,
        lspare: s32,
        qspare: [2]s64,
    },
    .linux => linux.Stat,
    else => #error("OS not supported"),
};


const FileDescriptor = s32;
const ProcessId = s32;
const MAP_FAILED = 0xffffffffffffffff;

const ProtectionFlags = bitfield(u32) {
    read: bool,
    write: bool,
    execute: bool,
};

const get_protection_flags = fn(flags: std.os.ProtectionFlags) ProtectionFlags {
    return ProtectionFlags{
        .read = flags.read,
        .write = flags.write,
        .execute = flags.execute,
    };
}

const get_map_flags = fn(flags: std.os.MapFlags) MapFlags{
    return MapFlags{
        .shared = false,
        .private = true,
        .fixed = false,
        .noreserve = false,
        .has_semaphore = false,
        .no_cache = false,
        .anonymous = true,
    };
}

const open :: extern = fn cc(.c) (path: [&:0]const u8, flags: OpenFlags) s32;
const read :: extern = fn cc(.c) (file_descriptor: FileDescriptor, bytes_ptr: [&]const u8, byte_count: usize) ssize;
const write :: extern = fn cc(.c) (file_descriptor: FileDescriptor, bytes_ptr: [&]const u8, byte_count: usize) ssize;
const exit :: extern = fn cc(.c) (exit_code: s32) noreturn;
const fork :: extern = fn cc(.c) () ProcessId;
const mmap :: extern = fn cc(.c) (address: ?[&]const u8, length: usize, protection_flags: ProtectionFlags, map_flags: MapFlags, file_descriptor: FileDescriptor, offset: u64) usize;
const munmap :: extern = fn cc(.c) (address: [&]const u8, length: usize) s32;
const execve :: extern = fn cc(.c) (path: [&:0]const u8, argv: [&:null]const ?[&:0]const u8, env: [&:null]const ?[&:null]const u8) s32;
const realpath :: extern = fn cc(.c) (path: [&:0]const u8, resolved_path: [&:0]u8) ?[&:0]u8;
const waitpid :: extern = fn cc(.c) (pid: ProcessId, status: &s32, flags: s32) s32;
const mprotect :: extern = fn cc(.c) (address: &any, size: usize, flags: ProtectionFlags) s32;
const fstat :: extern = fn cc(.c) (file_descriptor: FileDescriptor, stat: &Stat) s32;

const _NSGetExecutablePath :: extern = fn cc(.c) (buffer: [&:0]u8, buffer_size: &u32) s32;
