#pragma once

#include <Core/Common/NonNullPtr.h>
#include <Core/Memory/MemoryArena.h>

namespace Juliet
{
    template <typename Type, size_t ReserveSize = 16>
    struct VectorArena
    {
        void Create(NonNullPtr<Arena> arena JULIET_DEBUG_PARAM(const char* name = nullptr))
        {
            Assert(!Arena);

            JULIET_DEBUG_ONLY(Name = name ? name : Name;)

            DataFirst = DataLast = Data = nullptr;
            Count                       = 0;
            Capacity                    = 0;
            Arena                       = arena;

            Reserve(ReserveSize);
        }

        void Destroy()
        {
            DataFirst = DataLast = Data = nullptr;
            Count                       = 0;
            Capacity                    = 0;
            Arena                       = nullptr;
        }

        void Reserve(size_t newCapacity)
        {
            Assert(Arena);
            if (Data == nullptr)
            {
                Data     = ArenaPushArray<Type>(Arena, newCapacity JULIET_DEBUG_PARAM(Name));
                Capacity = newCapacity;
            }
            else
            {
                Assert(newCapacity <= Capacity && "VectorArena capacity exceeded! Reallocation is disabled.");
            }
        }

        void Resize(size_t newCount)
        {
            Assert(Arena);
            if (newCount == Count)
            {
                return;
            }

            if (Data == nullptr)
            {
                size_t initialCapacity = newCount > ReserveSize ? newCount : ReserveSize;
                Reserve(initialCapacity);
            }

            Assert(newCount <= Capacity && "VectorArena capacity exceeded!");
            Count = newCount;

            if (Count > 0)
            {
                DataFirst = Data;
                DataLast  = Data + Count - 1;
            }
            else
            {
                DataFirst = DataLast = nullptr;
            }
        }

        void PushBack(const Type* buffer, size_t amount)
        {
            Assert(Arena);
            Assert(buffer || amount == 0);

            if (amount == 0)
            {
                return;
            }

            if (Data == nullptr)
            {
                size_t initialCapacity = amount > ReserveSize ? amount : ReserveSize;
                Reserve(initialCapacity);
            }

            Assert(Count + amount <= Capacity && "VectorArena capacity exceeded!");

            Type* dst = Data + Count;
            MemCopy(dst, buffer, amount * sizeof(Type));

            if (Count == 0)
            {
                DataFirst = dst;
            }
            DataLast = dst + amount - 1;
            Count += amount;
        }

        void PushBack(const Type& value)
        {
            Assert(Arena);

            if (Data == nullptr)
            {
                Reserve(ReserveSize);
            }

            Assert(Count + 1 <= Capacity && "VectorArena capacity exceeded!");

            Type* entry = Data + Count;
            *entry      = value;

            if (Count == 0)
            {
                DataFirst = entry;
            }
            DataLast = entry;
            ++Count;
        }

        void PushBack(Type&& value)
        {
            Assert(Arena);

            if (Data == nullptr)
            {
                Reserve(ReserveSize);
            }

            Assert(Count + 1 <= Capacity && "VectorArena capacity exceeded!");

            Type* entry = Data + Count;
            *entry      = std::move(value);

            if (Count == 0)
            {
                DataFirst = entry;
            }
            DataLast = entry;
            ++Count;
        }

        void RemoveAtFast(index_t index)
        {
            Assert(Arena);
            Assert(index < Count);
            Assert(Count > 0);

            Type* elementAdr = DataFirst + index;

            // Swap DataLast and element
            if (DataLast != elementAdr)
            {
                Swap(DataLast, elementAdr);
            }

            --DataLast;
            --Count;

            if (Count == 0)
            {
                DataFirst = DataLast = nullptr;
            }
        }

        void Clear()
        {
            Assert(Arena);

            DataFirst = DataLast = nullptr;
            Count                = 0;
        }

        [[nodiscard]] bool IsEmpty() const { return Count == 0; }

        // C++ Accessors for loop supports and Index based access
        [[nodiscard]] Type&       operator[](size_t index) { return DataFirst[index]; }
        [[nodiscard]] const Type& operator[](size_t index) const { return DataFirst[index]; }

        [[nodiscard]] Type* begin() { return DataFirst; }
        [[nodiscard]] Type* end() { return DataFirst + Count; }

        [[nodiscard]] const Type* begin() const { return DataFirst; }
        [[nodiscard]] const Type* end() const { return DataFirst + Count; }

        [[nodiscard]] Type*       First() { return DataFirst; }
        [[nodiscard]] Type*       Front() { return DataFirst; }
        [[nodiscard]] Type*       Last() { return DataLast; }
        [[nodiscard]] Type*       Back() { return DataLast; }
        [[nodiscard]] Type*       DataPtr() { return Data; }
        [[nodiscard]] const Type* DataPtr() const { return Data; }

        [[nodiscard]] size_t Size() const { return Count; }

        Arena* Arena     = nullptr;
        Type*  DataFirst = nullptr;
        Type*  DataLast  = nullptr;
        Type*  Data      = nullptr;
        size_t Count     = 0;
        size_t Capacity  = 0;
        JULIET_DEBUG_ONLY(const char* Name = "VectorArena";)
    };
    static_assert(std::is_standard_layout_v<VectorArena<int>>,
                  "VectorArena must have a standard layout to remain POD-like.");
    static_assert(std::is_trivially_copyable_v<VectorArena<int>>,
                  "VectorArena must be trivially copyable (no custom destructors/assignment).");
} // namespace Juliet
