#pragma once

#include <Core/Common/CoreTypes.h>
#include <Core/Common/CoreUtils.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Juliet.h>

namespace Juliet
{
    constexpr global uint64 g_Arena_Default_Reserve_Size = Megabytes(64);
    constexpr global uint64 g_Arena_Default_Commit_Size  = Kilobytes(64);
    constexpr global uint64 k_ArenaHeaderSize            = 128;

    struct ArenaFreeNode;

#if JULIET_DEBUG
    struct ArenaDebugInfo;
#endif

    struct Arena
    {
        Arena* Previous;
        Arena* Current;

        uint64 BasePosition;
        uint64 Position;
        uint64 Alignment;

        uint64 CommitSize;
        uint64 ReserveSize;

        uint64 Committed;
        uint64 Reserved;

        Arena* FreeBlockLast;

        ArenaFreeNode* FreeNodes;

        bool AllowRealloc : 1;

        JULIET_DEBUG_ONLY(uint16 LostNodeCount;)
        JULIET_DEBUG_ONLY(bool CanReserveMore : 1;)

        JULIET_DEBUG_ONLY(Arena* GlobalNext;)
        JULIET_DEBUG_ONLY(Arena* GlobalPrev;)
        JULIET_DEBUG_ONLY(ArenaDebugInfo* FirstDebugInfo;)
        JULIET_DEBUG_ONLY(const char* Name;)
    };
    static_assert(sizeof(Arena) <= k_ArenaHeaderSize);

    struct TempArena
    {
        Arena*  Arena;
        index_t Position;
    };

    struct ArenaParams
    {
        uint64 ReserveSize = g_Arena_Default_Reserve_Size;
        uint64 CommitSize  = g_Arena_Default_Commit_Size;

        // True: All push will be 32 bytes minimum
        bool AllowRealloc = false;

        // When false, will assert if a new block is reserved.
        JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;)
    };

    [[nodiscard]] JULIET_API Arena* ArenaAllocate(const ArenaParams& params   JULIET_DEBUG_ONLY(, const char* name),
                                                  const std::source_location& loc = std::source_location::current());
    JULIET_API void                 ArenaRelease(NonNullPtr<Arena> arena);

    // Raw Push, can be used but templated helpers exists below
    // Raw Push, can be used but templated helpers exists below
    [[nodiscard]] JULIET_API void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align,
                                             bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag));
    [[nodiscard]] JULIET_API void* ArenaReallocate(NonNullPtr<Arena> arena, void* oldPtr, size_t oldSize, size_t newSize,
                                                   size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag));
    JULIET_API void                 ArenaPopTo(NonNullPtr<Arena> arena, size_t position);
    JULIET_API void                 ArenaPop(NonNullPtr<Arena> arena, size_t amount);
    JULIET_API void                 ArenaClear(NonNullPtr<Arena> arena);
    [[nodiscard]] JULIET_API size_t ArenaPos(NonNullPtr<Arena> arena);

    template <typename Type>
    [[nodiscard]] Type* ArenaPushStruct(NonNullPtr<Arena> arena JULIET_DEBUG_PARAM(const char* tag = nullptr))
    {
        return static_cast<Type*>(
            ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true JULIET_DEBUG_PARAM(tag ? tag : GetTypeName<Type>())));
    }

    template <typename Type>
    [[nodiscard]] Type* ArenaPushArray(NonNullPtr<Arena> arena, size_t count JULIET_DEBUG_PARAM(const char* tag = nullptr))
    {
        return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)),
                                            true JULIET_DEBUG_PARAM(tag ? tag : GetTypeName<Type>())));
    }

    TempArena ArenaTempBegin(NonNullPtr<Arena> arena);
    void      ArenaTempEnd(TempArena temp);
} // namespace Juliet
