#pragma once
#include "array.hxx"
#include "offsets.hxx"
#include <bit>
#include <cstdint>

namespace FN::Decryption
{
    inline auto get_world() -> std::uintptr_t
    {
        try
        {
            if (!Kernel::Object || !Kernel::Object->valid() || !FN::O::Engine::UWorld)
                return 0;
            const auto encoded = Kernel::Object->Read<std::uint64_t>(
                Kernel::Object->Base + FN::O::Engine::UWorld);
            const auto world = static_cast<std::uintptr_t>(std::rotl(encoded - 25199075ULL, 13) ^ 0x30A8E859ULL);
            return world ? world : 0;
        }
        catch (...)
        {
            return 0;
        }
    }

    inline auto get_objects_count() -> std::uint32_t
    {
        try
        {
            if (!Kernel::Object || !Kernel::Object->valid() ||
                !FN::O::Engine::GObjectCount)
                return 0;
            const auto encoded = Kernel::Object->Read<std::uint32_t>(
                Kernel::Object->Base + FN::O::Engine::GObjectCount);
            return std::rotl(encoded - 21958213U, 29) ^ 0x2F0C5215U;
        }
        catch (...)
        {
            return 0;
        }
    }

    inline auto get_object(const std::uint32_t index) -> std::uintptr_t
    {
        try
        {
            if (!Kernel::Object || !Kernel::Object->valid() ||
                !FN::O::Engine::GObjects)
                return 0;
            const auto count = get_objects_count();
            if (!count || index >= count) return 0;

            const auto encoded_table = Kernel::Object->Read<std::uint64_t>(
                Kernel::Object->Base + FN::O::Engine::GObjects);
            const auto chunk_table = static_cast<std::uintptr_t>(std::rotl(
                encoded_table - 25199075ULL, 13) ^ 0x29A7FEFDULL);
            if (!chunk_table) return 0;

            const auto chunk = Kernel::Object->Read<std::uintptr_t>(
                chunk_table + static_cast<std::uintptr_t>(index >> 16) *
                sizeof(std::uintptr_t));
            if (!chunk) return 0;

            const auto item = chunk +
                static_cast<std::uintptr_t>(index & 0xFFFFU) * 24U;
            const auto item_flags = Kernel::Object->Read<std::uint64_t>(item);
            if ((item_flags & 0x1020000000000000ULL) != 0) return 0;

            const auto encoded_low = Kernel::Object->Read<std::uint32_t>(item + 0x10);
            const auto decoded_low = static_cast<std::uint64_t>(std::rotl(
                encoded_low - 21958213U, 29) ^ 0xDA8BE09EU);
            const auto upper = item_flags & 0x3FFF00000000ULL;
            return static_cast<std::uintptr_t>((decoded_low | upper) << 3);
        }
        catch (...)
        {
            return 0;
        }
    }

    inline auto fname(const std::uint32_t encoded) -> std::uint32_t
    {
        if (!encoded) return 0;
        const auto decoded = (std::rotl(encoded - 21958214U, 29) ^ 0x53A51631U) + 1U;
        return decoded ? decoded : 208865287U;
    }
}
