ExpandableArena* bootstrap_expandable_arena_internal (Arena_Reserve new_reserve, s32 commit_page_count,
        string file_path, string function_name, s32 line_number) {
  ExpandableArena* new_arena = (ExpandableArena*)bootstrap_arena_internal(
        new_reserve, 
        commit_page_count,
        file_path, 
        function_name, 
        line_number);
  // Note: beyond first 32 bytes (sizeof(Arena)) ExpandableArena will not be initialized, 
  // so we do it here:
  new_arena->current = (Arena*)new_arena;
  
  new_arena->current_point = expandable_arena_start(new_arena);
  new_arena->next_arenas = Array<Arena*>(); // next_arenas will be uninitialized, so we have to do this
  // We have to use malloc because if we reset this new arena, all the data will be lost
  // We don't want to tie the lifetime of next_arenas to this expandable arena.
  new_arena->next_arenas.allocator = default_allocator();
  
  array_reserve(new_arena->next_arenas, 8);

  return new_arena;
}

// force_inline ExpandableArena* bootstrap_expandable_arena (Arena_Reserve new_reserve, s32 commit_page_count) {
//   return bootstrap_expandable_arena_internal(new_reserve, commit_page_count, __FILE__, __FUNCTION__, __LINE__);
// }

void* expandable_arena_allocator_proc (Allocator_Mode mode, s64 requested_size, s64 old_size, void* old_memory, void* allocator_data) {
  ExpandableArena* arena = (ExpandableArena*)allocator_data;
  Assert(arena != nullptr);
  switch (mode) {
    case Allocator_Mode::ALLOCATE: {
      return expandable_arena_alloc(arena, requested_size);
    } break;
    case Allocator_Mode::RESIZE: {
      // See note :ArenaResizing
      void* new_memory = expandable_arena_alloc(arena, requested_size);
      memcpy(new_memory, old_memory, old_size);
      return new_memory;
    } break;
    case Allocator_Mode::DEALLOCATE: {
      return nullptr;
    } break;
    case Allocator_Mode::DETAILS: {
      if (allocator_data == nullptr) {
        return "expandable_arena_allocator_proc: data pointer is null!";
      }
      if (thread_context()->temp  == allocator_data) {
        return "expandable_arena_allocator_proc: temp arena";
      }
      if (thread_context()->arena == allocator_data) {
        return "expandable_arena_allocator_proc: main arena";
      }
      return "expandable_arena_allocator_proc: other arena";
    } break;
  }
  
  return nullptr;
}

bool is_valid (ExpandableArena* arena) {
  return (arena != nullptr) 
          && (arena->memory_base != nullptr) 
          && (arena->current != nullptr);
}

void* expandable_arena_alloc (ExpandableArena* arena_ex, s64 byte_count) {
  Assert(arena_ex != nullptr);
  Assert(arena_ex->memory_base != nullptr); // must be initialized before calling.
  Assert(is_valid(arena_ex));
  // Assert(arena_free_list->initialized);
  
  Arena* arena = (Arena*)arena_ex->current;
  
  u8* result = Align<u8*>(arena->current_point, arena->alignment);
  u8* result_end = result + byte_count;
  
  if (result_end > arena->first_uncommitted_page) {
    if (result_end > arena_address_limit(arena)) {
      // Pick an appropriate reserve size that will fit this allocation.
      Arena_Reserve new_min_reserve = next_reserve_size(byte_count + sizeof(Arena));
      if (arena->reserve_size > new_min_reserve) {
        new_min_reserve = arena->reserve_size;
      }
      
      Arena* new_arena = bootstrap_arena(new_min_reserve);
      
      new_arena->alignment  = arena_ex->alignment;
      new_arena->flags      = arena_ex->flags;
      
      arena_ex->current     = new_arena;
      array_add(arena_ex->next_arenas, new_arena);
      
      // Allocate from new arena, committing pages as required
      result     = Align<u8*>(new_arena->current_point, new_arena->alignment);
      result_end = result + byte_count;
      
      if (result_end > new_arena->first_uncommitted_page) {
        extend_committed_pages(new_arena, result_end);
      }
    } else {
      extend_committed_pages(arena, result_end);
    }
  }
  
  arena_ex->current->current_point = result_end;
  
  return result;
}

u8* expandable_arena_start (ExpandableArena* arena_ex) {
  return Align(arena_ex->memory_base + sizeof(ExpandableArena), ARENA_DEFAULT_ALIGNMENT);
}

Allocator allocator (ExpandableArena* arena_ex) {
  return { expandable_arena_allocator_proc, arena_ex };
}

// last arena is the arena we want to pop to. 
void arena_reset_to (ExpandableArena* arena_ex, Arena* last_arena, u8* starting_point) {
  // going backwards from end of arena list
  
  if (!arena_ex->next_arenas.count) {
    arena_ex->current_point = starting_point;
    return;
  }
  
  for_each_reverse(i, arena_ex->next_arenas) {
    Arena* arena = arena_ex->next_arenas[i];
    if (arena == last_arena) { // return to starting_point
      arena_ex->current = arena;
      arena_ex->current->current_point = starting_point;
      break;
    }
    arena_delete(arena);
    // We can just decrement the count because arenas are added and removed in order. see: pop()
    arena_ex->next_arenas.count -= 1;
  }
  
  if (last_arena == (Arena*)arena_ex) {
    arena_ex->current = (Arena*)arena_ex;
    arena_ex->current->current_point = starting_point;
  }
}

void arena_reset (ExpandableArena* arena_ex, bool free_extra_pages) {
  if (!is_valid(arena_ex)) return;
  
  // Free expansion arenas in `next_arenas`
  for (s64 i = 0; i < arena_ex->next_arenas.count; i += 1) {
    arena_delete(arena_ex->next_arenas[i]);
  }
  
  // Reset next_arenas
#if BUILD_DEBUG 
  array_zero(arena_ex->next_arenas);
#endif
  array_reset_keeping_memory(arena_ex->next_arenas);
  
  arena_ex->current = (Arena*)arena_ex;
  arena_ex->current_point = expandable_arena_start(arena_ex);
  
  if (free_extra_pages) {
    free_pages_down_to((Arena*)arena_ex, arena_ex->initial_commit_page_count);
  }
}

// #TODO: make an option to "FULL-DELETE" the expansion arenas as well.
force_inline void arena_delete (ExpandableArena* arena_ex) {
  arena_reset(arena_ex, true);
  array_free(arena_ex->next_arenas);
  arena_delete((Arena*)arena_ex);
}
