#pragma once

#include <cstddef>
#include <cstdint>

#include <Sched/Scheduler.hpp>
#include <Memory/Paging.hpp>

namespace Montauk::UserMemory {

    static constexpr uint64_t USER_SPACE_END = 0x0000800000000000ULL;

    inline bool IsUserPtr(uint64_t addr) {
        return addr == 0 || addr < USER_SPACE_END;
    }

    inline bool ValidUserPtr(uint64_t addr) {
        return addr != 0 && addr < USER_SPACE_END;
    }

    inline uint64_t CurrentPml4() {
        auto* proc = Sched::GetCurrentProcessPtr();
        return proc ? proc->pml4Phys : 0;
    }

    inline bool Range(uint64_t addr, uint64_t size, bool requireWrite = false) {
        if (size == 0) return true;
        if (!ValidUserPtr(addr)) return false;
        if (addr + size < addr) return false;
        if (addr + size > USER_SPACE_END) return false;

        uint64_t pml4Phys = CurrentPml4();
        if (pml4Phys == 0) return false;

        return Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, addr, size, requireWrite);
    }

    inline bool OptionalRange(uint64_t addr, uint64_t size, bool requireWrite = false) {
        return addr == 0 || Range(addr, size, requireWrite);
    }

    template<typename T>
    inline bool Readable(uint64_t addr) {
        return Range(addr, sizeof(T), false);
    }

    template<typename T>
    inline bool Writable(uint64_t addr) {
        return Range(addr, sizeof(T), true);
    }

    inline bool String(uint64_t addr, uint64_t maxLen) {
        if (!ValidUserPtr(addr) || maxLen == 0) return false;

        uint64_t pml4Phys = CurrentPml4();
        if (pml4Phys == 0) return false;

        const char* str = (const char*)addr;
        for (uint64_t i = 0; i < maxLen; i++) {
            if (!Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, addr + i, 1, false)) {
                return false;
            }
            if (str[i] == '\0') return true;
        }

        return false;
    }
}
