/*
 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.

 */

#ifndef SHARE_NMT_ARRAYWITHFREELIST_HPP
#define SHARE_NMT_ARRAYWITHFREELIST_HPP

#include "utilities/growableArray.hpp"
#include <type_traits>

// A flat array of elements E, backed by C-heap, growing on-demand. It allows for
// returning arbitrary elements and keeps them in a freelist. Elements can be uniquely
// identified via array index.
template<typename E, MemTag MT>
class ArrayWithFreeList {

  // An E must be trivially copyable and destructible, but it may be constructed
  // however it likes.
  constexpr void static_assert_E_satisfies_type_requirements() const {
    static_assert(std::is_trivially_copyable<E>::value && std::is_trivially_destructible<E>::value, "must be");
  }

public:
  using I = int32_t;
  static constexpr const I nil = -1;

private:
  // A free list allocator element is either a link to the next free space
  // or an actual element.
  union BackingElement {
    I link;
    E e;
  };

  GrowableArrayCHeap<BackingElement, MT> _backing_storage;
  I _free_start;

  bool is_in_bounds(I i) {
    return i >= 0 && i < _backing_storage.length();
  }

public:
  NONCOPYABLE(ArrayWithFreeList);

  ArrayWithFreeList(int initial_capacity = 8)
    : _backing_storage(initial_capacity),
    _free_start(nil) {}

  template<typename... Args>
  I allocate(Args... args) {
    static_assert_E_satisfies_type_requirements();
    BackingElement* be;
    I i;
    if (_free_start != nil) {
      // Must point to already existing index
      be = &_backing_storage.at(_free_start);
      i = _free_start;
      _free_start = be->link;
    } else {
      // There are no free elements, allocate a new one.
      i = _backing_storage.append(BackingElement());
      be = _backing_storage.adr_at(i);
    }

    ::new (be) E{args...};
    return i;
  }

  void deallocate(I i) {
    static_assert_E_satisfies_type_requirements();
    assert(i == nil || is_in_bounds(i), "out of bounds free");
    if (i == nil) return;
    BackingElement& be_freed = _backing_storage.at(i);
    be_freed.link = _free_start;
    _free_start = i;
  }

  E& at(I i) {
    static_assert_E_satisfies_type_requirements();
    assert(i != nil, "null pointer dereference");
    assert(is_in_bounds(i), "out of bounds dereference");
    return _backing_storage.at(i).e;
  }
};

#endif // SHARE_NMT_ARRAYWITHFREELIST_HPP
