/*
 * Copyright (c) 2019, 2025, 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.
 */

#include "cppstdlib/new.hpp"
#include "memory/allocation.inline.hpp"
#include "runtime/atomic.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/lockFreeStack.hpp"
#include "threadHelper.inline.hpp"
#include "unittest.hpp"

class LockFreeStackTestElement {
  typedef LockFreeStackTestElement Element;

  Atomic<Element*> _entry;
  Atomic<Element*> _entry1;
  size_t _id;

  static Atomic<Element*>* entry_ptr(Element& e) { return &e._entry; }
  static Atomic<Element*>* entry1_ptr(Element& e) { return &e._entry1; }

public:
  LockFreeStackTestElement(size_t id = 0) : _entry(), _entry1(), _id(id) {}
  size_t id() const { return _id; }
  void set_id(size_t value) { _id = value; }

  typedef LockFreeStack<Element, &entry_ptr> TestStack;
  typedef LockFreeStack<Element, &entry1_ptr> TestStack1;
};

typedef LockFreeStackTestElement Element;
typedef Element::TestStack TestStack;
typedef Element::TestStack1 TestStack1;

static void initialize_ids(Element* elements, size_t size) {
  for (size_t i = 0; i < size; ++i) {
    elements[i].set_id(i);
  }
}

class LockFreeStackTestBasics : public ::testing::Test {
public:
  LockFreeStackTestBasics();

  static const size_t nelements = 10;
  Element elements[nelements];
  TestStack stack;

private:
  void initialize();
};

const size_t LockFreeStackTestBasics::nelements;

LockFreeStackTestBasics::LockFreeStackTestBasics() : stack() {
  initialize_ids(elements, nelements);
  initialize();
}

void LockFreeStackTestBasics::initialize() {
  ASSERT_TRUE(stack.empty());
  ASSERT_EQ(0u, stack.length());
  ASSERT_TRUE(stack.pop() == nullptr);
  ASSERT_TRUE(stack.top() == nullptr);

  for (size_t id = 0; id < nelements; ++id) {
    ASSERT_EQ(id, stack.length());
    Element* e = &elements[id];
    ASSERT_EQ(id, e->id());
    stack.push(*e);
    ASSERT_FALSE(stack.empty());
    ASSERT_EQ(e, stack.top());
  }
}

TEST_F(LockFreeStackTestBasics, push_pop) {
  for (size_t i = nelements; i > 0; ) {
    ASSERT_FALSE(stack.empty());
    ASSERT_EQ(i, stack.length());
    --i;
    Element* e = stack.pop();
    ASSERT_TRUE(e != nullptr);
    ASSERT_EQ(&elements[i], e);
    ASSERT_EQ(i, e->id());
  }
  ASSERT_TRUE(stack.empty());
  ASSERT_EQ(0u, stack.length());
  ASSERT_TRUE(stack.pop() == nullptr);
}

TEST_F(LockFreeStackTestBasics, prepend_one) {
  TestStack other_stack;
  ASSERT_TRUE(other_stack.empty());
  ASSERT_TRUE(other_stack.pop() == nullptr);
  ASSERT_EQ(0u, other_stack.length());
  ASSERT_TRUE(other_stack.top() == nullptr);
  ASSERT_TRUE(other_stack.pop() == nullptr);

  other_stack.prepend(*stack.pop_all());
  ASSERT_EQ(nelements, other_stack.length());
  ASSERT_TRUE(stack.empty());
  ASSERT_EQ(0u, stack.length());
  ASSERT_TRUE(stack.pop() == nullptr);
  ASSERT_TRUE(stack.top() == nullptr);

  for (size_t i = nelements; i > 0; ) {
    ASSERT_EQ(i, other_stack.length());
    --i;
    Element* e = other_stack.pop();
    ASSERT_TRUE(e != nullptr);
    ASSERT_EQ(&elements[i], e);
    ASSERT_EQ(i, e->id());
  }
  ASSERT_EQ(0u, other_stack.length());
  ASSERT_TRUE(other_stack.pop() == nullptr);
}

TEST_F(LockFreeStackTestBasics, prepend_two) {
  TestStack other_stack;
  ASSERT_TRUE(other_stack.empty());
  ASSERT_EQ(0u, other_stack.length());
  ASSERT_TRUE(other_stack.top() == nullptr);
  ASSERT_TRUE(other_stack.pop() == nullptr);

  Element* top = stack.pop_all();
  ASSERT_EQ(top, &elements[nelements - 1]);
  other_stack.prepend(*top, elements[0]);

  for (size_t i = nelements; i > 0; ) {
    ASSERT_EQ(i, other_stack.length());
    --i;
    Element* e = other_stack.pop();
    ASSERT_TRUE(e != nullptr);
    ASSERT_EQ(&elements[i], e);
    ASSERT_EQ(i, e->id());
  }
  ASSERT_EQ(0u, other_stack.length());
  ASSERT_TRUE(other_stack.pop() == nullptr);
}

TEST_F(LockFreeStackTestBasics, two_stacks) {
  TestStack1 stack1;
  ASSERT_TRUE(stack1.pop() == nullptr);

  for (size_t id = 0; id < nelements; ++id) {
    stack1.push(elements[id]);
  }
  ASSERT_EQ(nelements, stack1.length());
  Element* e0 = stack.top();
  Element* e1 = stack1.top();
  while (true) {
    ASSERT_EQ(e0, e1);
    if (e0 == nullptr) break;
    e0 = stack.next(*e0);
    e1 = stack1.next(*e1);
  }

  for (size_t i = nelements; i > 0; ) {
    ASSERT_EQ(i, stack.length());
    ASSERT_EQ(i, stack1.length());
    --i;
    Element* e = stack.pop();
    ASSERT_TRUE(e != nullptr);
    ASSERT_EQ(&elements[i], e);
    ASSERT_EQ(i, e->id());

    Element* e1 = stack1.pop();
    ASSERT_TRUE(e1 != nullptr);
    ASSERT_EQ(&elements[i], e1);
    ASSERT_EQ(i, e1->id());

    ASSERT_EQ(e, e1);
  }
  ASSERT_EQ(0u, stack.length());
  ASSERT_EQ(0u, stack1.length());
  ASSERT_TRUE(stack.pop() == nullptr);
  ASSERT_TRUE(stack1.pop() == nullptr);
}

class LockFreeStackTestThread : public JavaTestThread {
  uint _id;
  TestStack* _from;
  TestStack* _to;
  Atomic<size_t>* _processed;
  size_t _process_limit;
  size_t _local_processed;
  Atomic<bool> _ready;

public:
  LockFreeStackTestThread(Semaphore* post,
                          uint id,
                          TestStack* from,
                          TestStack* to,
                          Atomic<size_t>* processed,
                          size_t process_limit) :
    JavaTestThread(post),
    _id(id),
    _from(from),
    _to(to),
    _processed(processed),
    _process_limit(process_limit),
    _local_processed(0),
    _ready(false)
  {}

  virtual void main_run() {
    _ready.release_store_fence(true);
    while (true) {
      Element* e = _from->pop();
      if (e != nullptr) {
        _to->push(*e);
        _processed->fetch_then_add(1u);
        ++_local_processed;
      } else if (_processed->load_acquire() == _process_limit) {
        tty->print_cr("thread %u processed %zu", _id, _local_processed);
        return;
      }
    }
  }

  bool ready() const { return _ready.load_acquire(); }
};

TEST_VM(LockFreeStackTest, stress) {
  Semaphore post;
  TestStack initial_stack;
  TestStack start_stack;
  TestStack middle_stack;
  TestStack final_stack;
  Atomic<size_t> stage1_processed{0};
  Atomic<size_t> stage2_processed{0};

  const size_t nelements = 10000;
  Element* elements = NEW_C_HEAP_ARRAY(Element, nelements, mtOther);
  for (size_t id = 0; id < nelements; ++id) {
    ::new (&elements[id]) Element(id);
    initial_stack.push(elements[id]);
  }
  ASSERT_EQ(nelements, initial_stack.length());

  // - stage1 threads pop from start_stack and push to middle_stack.
  // - stage2 threads pop from middle_stack and push to final_stack.
  // - all threads in a stage count the number of elements processed in
  //   their corresponding stageN_processed counter.

  const uint stage1_threads = 2;
  const uint stage2_threads = 2;
  const uint nthreads = stage1_threads + stage2_threads;
  LockFreeStackTestThread* threads[nthreads] = {};

  for (uint i = 0; i < ARRAY_SIZE(threads); ++i) {
    TestStack* from = &start_stack;
    TestStack* to = &middle_stack;
    Atomic<size_t>* processed = &stage1_processed;
    if (i >= stage1_threads) {
      from = &middle_stack;
      to = &final_stack;
      processed = &stage2_processed;
    }
    threads[i] =
      new LockFreeStackTestThread(&post, i, from, to, processed, nelements);
    threads[i]->doit();
    while (!threads[i]->ready()) {} // Wait until ready to start test.
  }

  // Transfer elements to start_stack to start test.
  start_stack.prepend(*initial_stack.pop_all());

  // Wait for all threads to complete.
  for (uint i = 0; i < nthreads; ++i) {
    post.wait();
  }

  // Verify expected state.
  ASSERT_EQ(nelements, stage1_processed.load_relaxed());
  ASSERT_EQ(nelements, stage2_processed.load_relaxed());
  ASSERT_EQ(0u, initial_stack.length());
  ASSERT_EQ(0u, start_stack.length());
  ASSERT_EQ(0u, middle_stack.length());
  ASSERT_EQ(nelements, final_stack.length());
  while (final_stack.pop() != nullptr) {}

  FREE_C_HEAP_ARRAY(Element, elements);
}
