#if GP_ALLOCATOR_TRACK_ALLOCATIONS
  #include <mutex> // #TODO: Replace with Mutex (see OS_Win32.cpp)
  global General_Allocator gAllocator; // @Shared
  global std::mutex allocator_mutex;
#endif

#if !COMPILER_MSVC
// Note: There is *no* std::aligned_realloc. Must implement manually if needed.
force_inline void* gp_aligned_realloc(u64 old_size, void* ptr, u64 new_size, u64 alignment) {
    if (!ptr || old_size == 0) return std::aligned_alloc(alignment, new_size);
    if (new_size == 0) { std::free(ptr); return nullptr; }

    // Allocate new block
    void* new_ptr = std::aligned_alloc(alignment, new_size);
    if (!new_ptr) return nullptr;

    u64 copy_size = old_size < new_size ? old_size : new_size;
    memcpy(new_ptr, ptr, copy_size);

    std::free(ptr);
    return new_ptr;
}
#endif


General_Allocator* get_general_allocator_data() {
#if GP_ALLOCATOR_TRACK_ALLOCATIONS
    return &gAllocator;
#else
    return (General_Allocator*)nullptr;
#endif
}

constexpr s64 Allocation_Tracking_Is_Enabled = GP_ALLOCATOR_TRACK_ALLOCATIONS;

bool GPAllocator_Tracking_Enabled () {
    return Allocation_Tracking_Is_Enabled != 0;
}

void GPAllocator_Initialize_Allocation_Tracker () {
#if GP_ALLOCATOR_TRACK_ALLOCATIONS
    constexpr s64 alignment = 64;
    s64 item_count_max = 64 * 4096;
    s64 total_allocation_size = item_count_max * sizeof(Allocation);
    auto memory = Aligned_Alloc(total_allocation_size, alignment); // @MemoryLeak (intentional)
    gAllocator.allocations = Array<Allocation>(item_count_max, memory, item_count_max, GPAllocator());
    gAllocator.allocations.count = 0; // Init to zero. 
#endif
}

bool GPAllocator_Is_This_Yours (void* old_memory) {
#if GP_ALLOCATOR_TRACK_ALLOCATIONS
    std::lock_guard<std::mutex> lock(allocator_mutex);

    s64 old_size = 0;
    for (s64 i = 0; i < gAllocator.allocations.count; i += 1) {
        if (gAllocator.allocations[i].memory != old_memory) 
            continue;
        return true;
    }
    
#endif
    return false;
}

void Add_Allocation(s64 new_size, void* new_memory_address, s32 alignment) {
#if GP_ALLOCATOR_TRACK_ALLOCATIONS
    if (new_memory_address == nullptr) return;
    
    std::lock_guard<std::mutex> lock(allocator_mutex);
    Allocation allocation = {new_size, new_memory_address, alignment};
    array_add(gAllocator.allocations, allocation);
    
    gAllocator.total_bytes_allocated += new_size;
#endif
}

void Remove_Allocation(void* old_memory) {
#if GP_ALLOCATOR_TRACK_ALLOCATIONS
    std::lock_guard<std::mutex> lock(allocator_mutex);

    s64 old_size = 0;
    for (s64 i = 0; i < gAllocator.allocations.count; i += 1) {
        if (gAllocator.allocations[i].memory != old_memory) 
            continue;
        old_size = gAllocator.allocations[i].size;
        array_unordered_remove_by_index(gAllocator.allocations, i);
        
        gAllocator.total_bytes_allocated -= old_size;
        return;
    }
    
    Assert(false); // "Did not find allocation in Array"
#endif
}

void* GPAllocator_New (s64 new_size, s64 alignment, bool initialize) {
    // Fallback allocator: _aligned_malloc, which is MSVC's version of std::aligned_alloc

    auto memory = Aligned_Alloc(new_size, alignment);
    // _aligned_malloc does not zero memory, so we can zero it here
    if (initialize && memory) { memset(memory, ALLOCATOR_INIT_VALUE, new_size); }
    Add_Allocation(new_size, memory, (s32)alignment);
    
    // printf("[GP] Allocating memory %p of size %llu\n", memory, new_size);
    
    return memory;        
}

void* GPAllocator_Resize (s64 old_size, void* old_memory, s64 new_size, s64 alignment, bool initialize) {
    Assert((alignment % 8) == 0 && (alignment != 0));
    
    if (old_memory == nullptr) {
        return GPAllocator_New(new_size, alignment);
    }
    
    // Debug version: _aligned_realloc_dbg
    auto new_memory_address = Aligned_Realloc(old_size, old_memory, new_size, alignment);

    if (initialize && new_memory_address && new_size > old_size) { 
        memset((u8*)new_memory_address + old_size, ALLOCATOR_INIT_VALUE, new_size - old_size);
    }
    Remove_Allocation(old_memory);
    Add_Allocation(new_size, new_memory_address, (s32)alignment);
    
    // printf("[GP] Rellocating memory %p of size %llu\n", new_memory_address, new_size);
    
    return new_memory_address;
}

void GPAllocator_Delete (void* memory) {
    if (memory == nullptr) return;
    Aligned_Free(memory);
    Remove_Allocation(memory);
    
    // printf("[GP] Deleting memory %p\n", memory);
}

Allocator GPAllocator () {
    return { GPAllocator_Proc, nullptr };
}

void* GPAllocator_Proc (Allocator_Mode mode, s64 requested_size, s64 old_size, void* old_memory, void* allocator_data) {
    u16 alignment = 16; // default alignment

    Thread_Context* tctx = thread_context();
    if (tctx) alignment = tctx->GPAllocator_alignment;

    switch (mode) {
    case Allocator_Mode::ALLOCATE: {
      return GPAllocator_New(requested_size, alignment);
    } break;
    case Allocator_Mode::RESIZE: {
      void* result = GPAllocator_Resize(old_size, old_memory, requested_size, alignment);
      s64 size_to_copy = old_size < requested_size ? old_size : requested_size;
      if (result && size_to_copy) {
        memcpy(result, old_memory, size_to_copy);
      }
      return result;
    } break;
    case Allocator_Mode::DEALLOCATE:
      GPAllocator_Delete(old_memory); // unused
    break;
  }
  
  return nullptr;
}