#if GP_ALLOCATOR_TRACK_ALLOCATIONS
  global General_Allocator gAllocator; // @Shared
  global Mutex allocator_mutex;
  global bool default_allocator_show_small_allocations = false;
#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 default_allocator_tracking_enabled () {
    return Allocation_Tracking_Is_Enabled != 0;
}

void default_allocator_initialize_tracking () {
#if GP_ALLOCATOR_TRACK_ALLOCATIONS
    mutex_init(&allocator_mutex);
    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>(default_allocator(), item_count_max, memory, item_count_max);
    gAllocator.allocations.count = 0; // Init to zero. 
#endif
}

bool default_allocator_is_this_yours (void* old_memory) {
#if GP_ALLOCATOR_TRACK_ALLOCATIONS
    lock_guard(&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;
    string label = {};
    if (!thread_context()) {
        label = copy_string_untracked("Uninitialized context");
    } else {
        if (is_valid(thread_context()->allocator_label)) { 
            label = copy_string_untracked(thread_context()->allocator_label);
        } else {
            label = copy_string_untracked("Unlabeled allocation");
        }
    }
    Allocation allocation = {new_size, new_memory_address, alignment, label};
    lock_guard(&allocator_mutex);
    array_add(gAllocator.allocations, allocation);
    
    gAllocator.total_bytes_allocated += new_size;
#endif
}

void Remove_Allocation(void* old_memory) {
#if GP_ALLOCATOR_TRACK_ALLOCATIONS
    lock_guard(&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;
        if (is_valid(gAllocator.allocations[i].label)) {
            string_free_untracked(gAllocator.allocations[i].label);
        }
        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* default_allocator_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* default_allocator_realloc (s64 old_size, void* old_memory, s64 new_size, s64 alignment, bool initialize) {
    Assert((alignment % 8) == 0 && (alignment != 0));
    
    if (old_memory == nullptr) {
        return default_allocator_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 default_allocator_free (void* memory) {
    if (memory == nullptr) return;
    Aligned_Free(memory);
    Remove_Allocation(memory);
    
    // printf("[GP] Deleting memory %p\n", memory);
}

Allocator default_allocator () {
    return { default_allocator_proc, nullptr };
}

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

    Thread_Context* context = thread_context();
    if (context) alignment = context->default_allocator_alignment;

    switch (mode) {
    case Allocator_Mode::ALLOCATE: {
      return default_allocator_new(requested_size, alignment);
    } break;
    case Allocator_Mode::RESIZE: {
      void* result = default_allocator_realloc(old_size, old_memory, requested_size, alignment);
      // NOTE: The _aligned_realloc function already copies the old memory, so there's 
      // no need to copy the old memory block here. 
      return result;
    } break;
    case Allocator_Mode::DEALLOCATE: {
      default_allocator_free(old_memory); // unused
    } break;
    case Allocator_Mode::DETAILS: {
      Assert(allocator_data == nullptr);
      return "default_allocator";
    } break;
  }
  
  return nullptr;
}
