/*
 * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2020 SAP SE. 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 GTEST_METASPACE_METASPACEGTESTSPARSEARRAY_HPP
#define GTEST_METASPACE_METASPACEGTESTSPARSEARRAY_HPP

#include "memory/allocation.hpp"
#include "runtime/os.hpp"
#include "utilities/debug.hpp"
#include "metaspaceGtestCommon.hpp"
#include "metaspaceGtestRangeHelpers.hpp"

/////// SparseArray<T> ////////////////

// Throughout these tests we need to keep track of allocated items (ranges of metaspace memory, metachunks, ..)
//  and be able to random-access them. Makes sense to have a helper for that.
template <class T>
class SparseArray : public StackObj {

  T* const _slots;
  const int _num;

  // For convenience: a range covering all possible slot indices.
  const IntRange _index_range;

  bool contains(int index) const {
    return _index_range.contains(index);
  }

  // Check slot intex for oob
  void check_index(int i) const {
    assert(contains(i), "Sanity");
  }

  // Swap the content of two slots.
  void swap(int i1, int i2) {
    check_index(i1);
    check_index(i2);
    T tmp = _slots[i1];
    _slots[i1] = _slots[i2];
    _slots[i2] = tmp;
  }

  enum condition_t { cond_null = 0, cond_non_null = 1, cond_dontcare = 2 };

  // Helper for next_matching_slot
  bool slot_matches(int slot, condition_t c) const {
    switch(c) {
    case cond_null:     return _slots[slot] == nullptr;
    case cond_non_null: return _slots[slot] != nullptr;
    case cond_dontcare: return true;
    }
    ShouldNotReachHere();
    return false;
  }

  // Starting at (including) index, find the next matching slot. Returns index or -1 if none found.
  int next_matching_slot(int slot, condition_t c) const {
    while(slot < _num) {
      if (slot_matches(slot, c)) {
        return slot;
      }
      slot++;
    }
    return -1;
  }

public:

  SparseArray(int num) :
    _slots(NEW_C_HEAP_ARRAY(T, num, mtInternal)),
    _num(num),
    _index_range(num)
  {
    for (int i = 0; i < _num; i++) {
      _slots[i] = nullptr;
    }
  }

  ~SparseArray() {
    FREE_C_HEAP_ARRAY(T, _slots);
  }

  T at(int i)              { return _slots[i]; }
  const T at(int i) const  { return _slots[i]; }
  void set_at(int i, T e)  { _slots[i] = e; }

  int size() const         { return _num; }

  bool slot_is_null(int i) const                      { check_index(i); return _slots[i] == nullptr; }

  DEBUG_ONLY(void check_slot_is_null(int i) const     { assert(slot_is_null(i), "Slot %d is not null", i); })
  DEBUG_ONLY(void check_slot_is_not_null(int i) const { assert(!slot_is_null(i), "Slot %d is null", i); })

  // Shuffle all elements randomly
  void shuffle() {
    for (int i = 0; i < _num; i++) {
      swap(i, random_slot_index());
    }
  }

  // Reverse elements
  void reverse() {
    for (int i = 0; i < _num / 2; i++) {
      swap(i, _num - i);
    }
  }

  int first_slot() const            { return 0; }
  int next_slot(int index) const    { return index == _index_range.highest() ? -1 : index + 1; }

  int first_non_null_slot() const         { return next_matching_slot(0, cond_non_null); }
  int next_non_null_slot(int index) const { return next_matching_slot(index + 1, cond_non_null); }

  int first_null_slot() const             { return next_matching_slot(0, cond_null); }
  int next_null_slot(int index) const     { return next_matching_slot(index + 1, cond_null); }

  // Return a random slot index.
  int random_slot_index() const {
    return _index_range.random_value();
  }

  int random_non_null_slot_index() const {
    int i = next_non_null_slot(_index_range.random_value());
    if (i == -1) {
      i = first_non_null_slot();
    }
    return i;
  }

  int random_null_slot_index() const {
    int i = next_null_slot(_index_range.random_value());
    if (i == -1) {
      i = first_null_slot();
    }
    return i;
  }

  IntRange random_slot_range() const {
    return _index_range.random_subrange();
  }

};

#endif // GTEST_METASPACE_METASPACEGTESTSPARSEARRAY_HPP

