const lib = @import("lib");
const Allocator = lib.Allocator;
const assert = lib.assert;

pub const arch = @import("birth/arch.zig");
pub const interface = @import("birth/interface.zig");

/// This struct is the shared part that the user and the cpu see
pub const Scheduler = extern struct {
    common: Common,
    current_thread: *Thread,
    thread_queue: ?*Thread = null,
    time_slice: u32,
    core_id: u32,
    core_state: CoreState,
    bootstrap_thread: Thread,
    fast_allocator: Allocator,

    pub fn initializeAllocator(scheduler: *Scheduler) void {
        scheduler.fast_allocator = Allocator{
            .callbacks = .{
                .allocate = callbackAllocate,
            },
        };
    }

    fn callbackAllocate(allocator: *Allocator, size: u64, alignment: u64) Allocator.Allocate.Error!Allocator.Allocate.Result {
        const scheduler = @fieldParentPtr(Scheduler, "fast_allocator", allocator);
        assert(scheduler.common.heap.address.isAligned(alignment));
        const result = scheduler.common.heap.takeSlice(size) catch return error.OutOfMemory;
        return @bitCast(result);
    }

    pub const Common = extern struct {
        self: *Common,
        disabled: bool,
        has_work: bool,
        core_id: u32,
        heap: lib.VirtualMemoryRegion,
        setup_stack: [lib.arch.valid_page_sizes[0] * 4]u8 align(lib.arch.stack_alignment),
        setup_stack_lock: lib.Atomic(bool),
        disabled_save_area: arch.RegisterArena,

        // pub fn heapAllocateFast(common: *Common, comptime T: type) !*T {
        //     const size = @sizeOf(T);
        //     const alignment = @alignOf(T);
        //     lib.log.debug("Heap: {}. Size: {}. Alignment: {}", .{ common.heap, size, alignment });
        //     const result = try common.heap.takeSlice(size);
        //     const ptr = &result.access(T)[0];
        //     assert(lib.isAligned(@intFromPtr(ptr), alignment));
        //
        //     return ptr;
        // }
    };

    pub fn enqueueThread(scheduler: *Scheduler, thread_to_queue: *Thread) void {
        // TODO: check queue
        // TODO: defer check queue
        if (scheduler.thread_queue) |thread_queue| {
            _ = thread_queue;
            @panic("TODO: enqueueThread");
        } else {
            scheduler.thread_queue = thread_to_queue;
            thread_to_queue.previous = thread_to_queue;
            thread_to_queue.next = thread_to_queue;
        }
    }

    pub noinline fn restore(scheduler: *Scheduler, register_arena: *const arch.RegisterArena) noreturn {
        assert(scheduler.common.generic.disabled);
        assert(scheduler.common.generic.has_work);

        assert(register_arena.registers.rip > lib.arch.valid_page_sizes[0]);
        assert(register_arena.registers.rflags.IF and register_arena.registers.rflags.reserved0);

        register_arena.contextSwitch();
    }
};

pub const Thread = extern struct {
    self: *Thread,
    previous: ?*Thread = null,
    next: ?*Thread = null,
    stack: [*]u8,
    stack_top: [*]align(lib.arch.stack_alignment) u8,
    register_arena: arch.RegisterArena align(arch.RegisterArena.alignment),
    core_id: u32,

    pub fn init(thread: *Thread, scheduler: *Scheduler) void {
        thread.* = Thread{
            .self = thread,
            .core_id = scheduler.generic.core_id,
            .stack = thread.stack,
            .stack_top = thread.stack_top,
            .register_arena = thread.register_arena,
        };
    }
};

pub const CoreState = extern struct {
    virtual_address_space: *VirtualAddressSpace,
};
pub const VirtualAddressSpace = extern struct {
    // TODO: physical map
    // TODO: layout
    regions: ?*VirtualMemoryRegion = null,
};

pub const VirtualMemoryRegion = extern struct {
    next: ?*VirtualMemoryRegion = null,
};
