// #TODO: #Arena_Free_List #garbage_collection in `release_arena`
// [ ] Garbage collection if we have >> 64 in a particular table for a while.
//     There should be some parameters regarding what the upper limit for idle 
//     committed pages should be and a heuristic for maximum number of arenas waiting

struct Arena_Free_List {
  Mutex mutex;
  s32 in_flight_count[Arena_Reserve_Count];
  Array<Arena*> free_table[Arena_Reserve_Count];
  
#if ARENA_DEBUG
  Array<Arena*> in_flight[Arena_Reserve_Count];
#endif
  b32 initialized;
};

global Arena_Free_List* arena_free_list;

// Only call once from main thread!
void initialize_arena_free_list (Allocator allocator) {
  mutex_init(&arena_free_list->mutex);
  Assert(arena_free_list != nullptr);
  if (arena_free_list->initialized)
      return;
  
  for (s32 i = 0; i < Arena_Reserve_Count; i += 1) {
    arena_free_list->in_flight_count[i] = 0;
    arena_free_list->free_table[i].allocator = allocator;
    array_reserve(arena_free_list->free_table[i], 64);
#if ARENA_DEBUG
    arena_free_list->in_flight[i].allocator = allocator;
    array_reserve(arena_free_list->in_flight[i], 64);
#endif
  }
  
  arena_free_list->initialized = true;
}

Arena* next_arena (Arena_Reserve reserve_size) {
  Assert(arena_free_list != nullptr);
  Arena* arena;
  lock_guard(&arena_free_list->mutex);
  s64 reserve_index = (s64)reserve_size;
  
  if (!arena_free_list->free_table[reserve_index].count) {
    arena = bootstrap_arena(reserve_size, ARENA_DEFAULT_COMMIT_PAGE_COUNT);
  } else {
    arena = pop(arena_free_list->free_table[reserve_index]);
  }
#if ARENA_DEBUG 
  array_add(arena_free_list->in_flight[reserve_index], arena);
#endif  

  arena_free_list->in_flight_count[reserve_index] += 1;

  Assert(arena != nullptr);
  return arena;
}

void release_arena (Arena* arena, bool delete_extra_pages) {
  Assert(arena_free_list != nullptr);
  Assert(arena != nullptr);
  Assert(arena_is_bootstrapped(arena));
  // Only put into free table if arena is bootstrapped?
  lock_guard(&arena_free_list->mutex);
  s64 reserve_index = (s64)arena->reserve_size;
  
#if ARENA_DEBUG 
  array_unordered_remove_by_value(arena_free_list->in_flight[reserve_index], arena, 1); // BUILD_DEBUG!
#endif
  arena_reset_keeping_memory(arena);
  if (delete_extra_pages) {
    free_pages_down_to(arena, arena->initial_commit_page_count);
  }
  array_add(arena_free_list->free_table[reserve_index], arena);
  
  arena_free_list->in_flight_count[reserve_index] -= 1;
  // #TODO #garbage_collection
  // if (arena_free_table[reserve_index].count > 64) {
  //   s64 arenas_to_delete_count = arena_free_table[reserve_index].count - 64;
  //   while (arenas_to_delete_count > 0) {
  //     arena_delete(arena_free_table[arena_free_table.count-1]);
  //     array_unordered_remove_by_index(..);
  //     arenas_to_delete_count -= 1;
  //   }
  // }
}

s64 bytes_in_use (ArrayView<Arena*> arenas) {
  // does not include overhead from committed pages!
  s64 sum = 0;
  
  for (s64 i = 0; i < arenas.count; i += 1) {
    sum += arena_usage_bytes(arenas[i]);
  }
  
  return sum;
}

s64 committed_bytes (ArrayView<Arena*> arenas) {
  s64 sum = 0;
  
  for (s64 i = 0; i < arenas.count; i += 1) {
    sum += arena_usage_committed_bytes(arenas[i]);
  }
  
  return sum;
}
